golang.org/x/text@v0.14.0/currency/gen.go (about) 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 }