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