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