...

Source file src/golang.org/x/text/currency/gen.go

Documentation: golang.org/x/text/currency

     1  // Copyright 2015 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  //go:build ignore
     6  
     7  // Generator for currency-related data.
     8  
     9  package main
    10  
    11  import (
    12  	"flag"
    13  	"fmt"
    14  	"log"
    15  	"os"
    16  	"sort"
    17  	"strconv"
    18  	"strings"
    19  	"time"
    20  
    21  	"golang.org/x/text/internal/language/compact"
    22  
    23  	"golang.org/x/text/internal/gen"
    24  	"golang.org/x/text/internal/tag"
    25  	"golang.org/x/text/language"
    26  	"golang.org/x/text/unicode/cldr"
    27  )
    28  
    29  var (
    30  	test = flag.Bool("test", false,
    31  		"test existing tables; can be used to compare web data with package data.")
    32  	outputFile = flag.String("output", "tables.go", "output file")
    33  
    34  	draft = flag.String("draft",
    35  		"contributed",
    36  		`Minimal draft requirements (approved, contributed, provisional, unconfirmed).`)
    37  )
    38  
    39  func main() {
    40  	gen.Init()
    41  
    42  	gen.Repackage("gen_common.go", "common.go", "currency")
    43  
    44  	// Read the CLDR zip file.
    45  	r := gen.OpenCLDRCoreZip()
    46  	defer r.Close()
    47  
    48  	d := &cldr.Decoder{}
    49  	d.SetDirFilter("supplemental", "main")
    50  	d.SetSectionFilter("numbers")
    51  	data, err := d.DecodeZip(r)
    52  	if err != nil {
    53  		log.Fatalf("DecodeZip: %v", err)
    54  	}
    55  
    56  	w := gen.NewCodeWriter()
    57  	defer w.WriteGoFile(*outputFile, "currency")
    58  
    59  	fmt.Fprintln(w, `import "golang.org/x/text/internal/tag"`)
    60  
    61  	gen.WriteCLDRVersion(w)
    62  	b := &builder{}
    63  	b.genCurrencies(w, data.Supplemental())
    64  	b.genSymbols(w, data)
    65  }
    66  
    67  var constants = []string{
    68  	// Undefined and testing.
    69  	"XXX", "XTS",
    70  	// G11 currencies https://en.wikipedia.org/wiki/G10_currencies.
    71  	"USD", "EUR", "JPY", "GBP", "CHF", "AUD", "NZD", "CAD", "SEK", "NOK", "DKK",
    72  	// Precious metals.
    73  	"XAG", "XAU", "XPT", "XPD",
    74  
    75  	// Additional common currencies as defined by CLDR.
    76  	"BRL", "CNY", "INR", "RUB", "HKD", "IDR", "KRW", "MXN", "PLN", "SAR",
    77  	"THB", "TRY", "TWD", "ZAR",
    78  }
    79  
    80  type builder struct {
    81  	currencies    tag.Index
    82  	numCurrencies int
    83  }
    84  
    85  func (b *builder) genCurrencies(w *gen.CodeWriter, data *cldr.SupplementalData) {
    86  	// 3-letter ISO currency codes
    87  	// Start with dummy to let index start at 1.
    88  	currencies := []string{"\x00\x00\x00\x00"}
    89  
    90  	// currency codes
    91  	for _, reg := range data.CurrencyData.Region {
    92  		for _, cur := range reg.Currency {
    93  			currencies = append(currencies, cur.Iso4217)
    94  		}
    95  	}
    96  	// Not included in the list for some reasons:
    97  	currencies = append(currencies, "MVP")
    98  
    99  	sort.Strings(currencies)
   100  	// Unique the elements.
   101  	k := 0
   102  	for i := 1; i < len(currencies); i++ {
   103  		if currencies[k] != currencies[i] {
   104  			currencies[k+1] = currencies[i]
   105  			k++
   106  		}
   107  	}
   108  	currencies = currencies[:k+1]
   109  
   110  	// Close with dummy for simpler and faster searching.
   111  	currencies = append(currencies, "\xff\xff\xff\xff")
   112  
   113  	// Write currency values.
   114  	fmt.Fprintln(w, "const (")
   115  	for _, c := range constants {
   116  		index := sort.SearchStrings(currencies, c)
   117  		fmt.Fprintf(w, "\t%s = %d\n", strings.ToLower(c), index)
   118  	}
   119  	fmt.Fprint(w, ")")
   120  
   121  	// Compute currency-related data that we merge into the table.
   122  	for _, info := range data.CurrencyData.Fractions[0].Info {
   123  		if info.Iso4217 == "DEFAULT" {
   124  			continue
   125  		}
   126  		standard := getRoundingIndex(info.Digits, info.Rounding, 0)
   127  		cash := getRoundingIndex(info.CashDigits, info.CashRounding, standard)
   128  
   129  		index := sort.SearchStrings(currencies, info.Iso4217)
   130  		currencies[index] += mkCurrencyInfo(standard, cash)
   131  	}
   132  
   133  	// Set default values for entries that weren't touched.
   134  	for i, c := range currencies {
   135  		if len(c) == 3 {
   136  			currencies[i] += mkCurrencyInfo(0, 0)
   137  		}
   138  	}
   139  
   140  	b.currencies = tag.Index(strings.Join(currencies, ""))
   141  	w.WriteComment(`
   142  	currency holds an alphabetically sorted list of canonical 3-letter currency
   143  	identifiers. Each identifier is followed by a byte of type currencyInfo,
   144  	defined in gen_common.go.`)
   145  	w.WriteConst("currency", b.currencies)
   146  
   147  	// Hack alert: gofmt indents a trailing comment after an indented string.
   148  	// Ensure that the next thing written is not a comment.
   149  	b.numCurrencies = (len(b.currencies) / 4) - 2
   150  	w.WriteConst("numCurrencies", b.numCurrencies)
   151  
   152  	// Create a table that maps regions to currencies.
   153  	regionToCurrency := []toCurrency{}
   154  
   155  	for _, reg := range data.CurrencyData.Region {
   156  		if len(reg.Iso3166) != 2 {
   157  			log.Fatalf("Unexpected group %q in region data", reg.Iso3166)
   158  		}
   159  		if len(reg.Currency) == 0 {
   160  			continue
   161  		}
   162  		cur := reg.Currency[0]
   163  		if cur.To != "" || cur.Tender == "false" {
   164  			continue
   165  		}
   166  		regionToCurrency = append(regionToCurrency, toCurrency{
   167  			region: regionToCode(language.MustParseRegion(reg.Iso3166)),
   168  			code:   uint16(b.currencies.Index([]byte(cur.Iso4217))),
   169  		})
   170  	}
   171  	sort.Sort(byRegion(regionToCurrency))
   172  
   173  	w.WriteType(toCurrency{})
   174  	w.WriteVar("regionToCurrency", regionToCurrency)
   175  
   176  	// Create a table that maps regions to currencies.
   177  	regionData := []regionInfo{}
   178  
   179  	for _, reg := range data.CurrencyData.Region {
   180  		if len(reg.Iso3166) != 2 {
   181  			log.Fatalf("Unexpected group %q in region data", reg.Iso3166)
   182  		}
   183  		for _, cur := range reg.Currency {
   184  			from, _ := time.Parse("2006-01-02", cur.From)
   185  			to, _ := time.Parse("2006-01-02", cur.To)
   186  			code := uint16(b.currencies.Index([]byte(cur.Iso4217)))
   187  			if cur.Tender == "false" {
   188  				code |= nonTenderBit
   189  			}
   190  			regionData = append(regionData, regionInfo{
   191  				region: regionToCode(language.MustParseRegion(reg.Iso3166)),
   192  				code:   code,
   193  				from:   toDate(from),
   194  				to:     toDate(to),
   195  			})
   196  		}
   197  	}
   198  	sort.Stable(byRegionCode(regionData))
   199  
   200  	w.WriteType(regionInfo{})
   201  	w.WriteVar("regionData", regionData)
   202  }
   203  
   204  type regionInfo struct {
   205  	region uint16
   206  	code   uint16 // 0x8000 not legal tender
   207  	from   uint32
   208  	to     uint32
   209  }
   210  
   211  type byRegionCode []regionInfo
   212  
   213  func (a byRegionCode) Len() int           { return len(a) }
   214  func (a byRegionCode) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
   215  func (a byRegionCode) Less(i, j int) bool { return a[i].region < a[j].region }
   216  
   217  type toCurrency struct {
   218  	region uint16
   219  	code   uint16
   220  }
   221  
   222  type byRegion []toCurrency
   223  
   224  func (a byRegion) Len() int           { return len(a) }
   225  func (a byRegion) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
   226  func (a byRegion) Less(i, j int) bool { return a[i].region < a[j].region }
   227  
   228  func mkCurrencyInfo(standard, cash int) string {
   229  	return string([]byte{byte(cash<<cashShift | standard)})
   230  }
   231  
   232  func getRoundingIndex(digits, rounding string, defIndex int) int {
   233  	round := roundings[defIndex] // default
   234  
   235  	if digits != "" {
   236  		round.scale = parseUint8(digits)
   237  	}
   238  	if rounding != "" && rounding != "0" { // 0 means 1 here in CLDR
   239  		round.increment = parseUint8(rounding)
   240  	}
   241  
   242  	// Will panic if the entry doesn't exist:
   243  	for i, r := range roundings {
   244  		if r == round {
   245  			return i
   246  		}
   247  	}
   248  	log.Fatalf("Rounding entry %#v does not exist.", round)
   249  	panic("unreachable")
   250  }
   251  
   252  // genSymbols generates the symbols used for currencies. Most symbols are
   253  // defined in root and there is only very small variation per language.
   254  // The following rules apply:
   255  // - A symbol can be requested as normal or narrow.
   256  // - If a symbol is not defined for a currency, it defaults to its ISO code.
   257  func (b *builder) genSymbols(w *gen.CodeWriter, data *cldr.CLDR) {
   258  	d, err := cldr.ParseDraft(*draft)
   259  	if err != nil {
   260  		log.Fatalf("filter: %v", err)
   261  	}
   262  
   263  	const (
   264  		normal = iota
   265  		narrow
   266  		numTypes
   267  	)
   268  	// language -> currency -> type ->  symbol
   269  	var symbols [compact.NumCompactTags][][numTypes]*string
   270  
   271  	// Collect symbol information per language.
   272  	for _, lang := range data.Locales() {
   273  		ldml := data.RawLDML(lang)
   274  		if ldml.Numbers == nil || ldml.Numbers.Currencies == nil {
   275  			continue
   276  		}
   277  
   278  		langIndex, ok := compact.LanguageID(compact.Tag(language.MustParse(lang)))
   279  		if !ok {
   280  			log.Fatalf("No compact index for language %s", lang)
   281  		}
   282  
   283  		symbols[langIndex] = make([][numTypes]*string, b.numCurrencies+1)
   284  
   285  		for _, c := range ldml.Numbers.Currencies.Currency {
   286  			syms := cldr.MakeSlice(&c.Symbol)
   287  			syms.SelectDraft(d)
   288  
   289  			for _, sym := range c.Symbol {
   290  				v := sym.Data()
   291  				if v == c.Type {
   292  					// We define "" to mean the ISO symbol.
   293  					v = ""
   294  				}
   295  				cur := b.currencies.Index([]byte(c.Type))
   296  				// XXX gets reassigned to 0 in the package's code.
   297  				if c.Type == "XXX" {
   298  					cur = 0
   299  				}
   300  				if cur == -1 {
   301  					fmt.Println("Unsupported:", c.Type)
   302  					continue
   303  				}
   304  
   305  				switch sym.Alt {
   306  				case "":
   307  					symbols[langIndex][cur][normal] = &v
   308  				case "narrow":
   309  					symbols[langIndex][cur][narrow] = &v
   310  				}
   311  			}
   312  		}
   313  	}
   314  
   315  	// Remove values identical to the parent.
   316  	for langIndex, data := range symbols {
   317  		for curIndex, curs := range data {
   318  			for typ, sym := range curs {
   319  				if sym == nil {
   320  					continue
   321  				}
   322  				for p := compact.ID(langIndex); p != 0; {
   323  					p = p.Parent()
   324  					x := symbols[p]
   325  					if x == nil {
   326  						continue
   327  					}
   328  					if v := x[curIndex][typ]; v != nil || p == 0 {
   329  						// Value is equal to the default value root value is undefined.
   330  						parentSym := ""
   331  						if v != nil {
   332  							parentSym = *v
   333  						}
   334  						if parentSym == *sym {
   335  							// Value is the same as parent.
   336  							data[curIndex][typ] = nil
   337  						}
   338  						break
   339  					}
   340  				}
   341  			}
   342  		}
   343  	}
   344  
   345  	// Create symbol index.
   346  	symbolData := []byte{0}
   347  	symbolLookup := map[string]uint16{"": 0} // 0 means default, so block that value.
   348  	for _, data := range symbols {
   349  		for _, curs := range data {
   350  			for _, sym := range curs {
   351  				if sym == nil {
   352  					continue
   353  				}
   354  				if _, ok := symbolLookup[*sym]; !ok {
   355  					symbolLookup[*sym] = uint16(len(symbolData))
   356  					symbolData = append(symbolData, byte(len(*sym)))
   357  					symbolData = append(symbolData, *sym...)
   358  				}
   359  			}
   360  		}
   361  	}
   362  	w.WriteComment(`
   363  	symbols holds symbol data of the form <n> <str>, where n is the length of
   364  	the symbol string str.`)
   365  	w.WriteConst("symbols", string(symbolData))
   366  
   367  	// Create index from language to currency lookup to symbol.
   368  	type curToIndex struct{ cur, idx uint16 }
   369  	w.WriteType(curToIndex{})
   370  
   371  	prefix := []string{"normal", "narrow"}
   372  	// Create data for regular and narrow symbol data.
   373  	for typ := normal; typ <= narrow; typ++ {
   374  
   375  		indexes := []curToIndex{} // maps currency to symbol index
   376  		languages := []uint16{}
   377  
   378  		for _, data := range symbols {
   379  			languages = append(languages, uint16(len(indexes)))
   380  			for curIndex, curs := range data {
   381  
   382  				if sym := curs[typ]; sym != nil {
   383  					indexes = append(indexes, curToIndex{uint16(curIndex), symbolLookup[*sym]})
   384  				}
   385  			}
   386  		}
   387  		languages = append(languages, uint16(len(indexes)))
   388  
   389  		w.WriteVar(prefix[typ]+"LangIndex", languages)
   390  		w.WriteVar(prefix[typ]+"SymIndex", indexes)
   391  	}
   392  }
   393  func parseUint8(str string) uint8 {
   394  	x, err := strconv.ParseUint(str, 10, 8)
   395  	if err != nil {
   396  		// Show line number of where this function was called.
   397  		log.New(os.Stderr, "", log.Lshortfile).Output(2, err.Error())
   398  		os.Exit(1)
   399  	}
   400  	return uint8(x)
   401  }
   402  

View as plain text