Marcel van Lohuizen | 6368131 | 2015-08-24 11:51:03 +0200 | [diff] [blame] | 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 | |
Russ Cox | 8f690f2 | 2021-02-19 18:54:44 -0500 | [diff] [blame] | 5 | //go:build ignore |
Marcel van Lohuizen | 6368131 | 2015-08-24 11:51:03 +0200 | [diff] [blame] | 6 | // +build ignore |
| 7 | |
| 8 | // Generator for currency-related data. |
| 9 | |
| 10 | package main |
| 11 | |
| 12 | import ( |
Marcel van Lohuizen | 6368131 | 2015-08-24 11:51:03 +0200 | [diff] [blame] | 13 | "flag" |
| 14 | "fmt" |
Marcel van Lohuizen | 6368131 | 2015-08-24 11:51:03 +0200 | [diff] [blame] | 15 | "log" |
| 16 | "os" |
| 17 | "sort" |
| 18 | "strconv" |
| 19 | "strings" |
Marcel van Lohuizen | f773ec0 | 2016-05-05 15:32:54 -0700 | [diff] [blame] | 20 | "time" |
Marcel van Lohuizen | 6368131 | 2015-08-24 11:51:03 +0200 | [diff] [blame] | 21 | |
Marcel van Lohuizen | ab48842 | 2018-03-01 22:29:26 +0100 | [diff] [blame] | 22 | "golang.org/x/text/internal/language/compact" |
| 23 | |
Marcel van Lohuizen | 6368131 | 2015-08-24 11:51:03 +0200 | [diff] [blame] | 24 | "golang.org/x/text/internal/gen" |
| 25 | "golang.org/x/text/internal/tag" |
Marcel van Lohuizen | 601048a | 2015-09-21 21:52:40 +0200 | [diff] [blame] | 26 | "golang.org/x/text/language" |
Marcel van Lohuizen | edeeb43 | 2015-12-05 13:06:26 +0100 | [diff] [blame] | 27 | "golang.org/x/text/unicode/cldr" |
Marcel van Lohuizen | 6368131 | 2015-08-24 11:51:03 +0200 | [diff] [blame] | 28 | ) |
| 29 | |
| 30 | var ( |
| 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 | |
| 40 | func main() { |
| 41 | gen.Init() |
| 42 | |
Marcel van Lohuizen | 54db231 | 2015-11-24 19:16:13 +0100 | [diff] [blame] | 43 | gen.Repackage("gen_common.go", "common.go", "currency") |
Marcel van Lohuizen | 6368131 | 2015-08-24 11:51:03 +0200 | [diff] [blame] | 44 | |
| 45 | // Read the CLDR zip file. |
| 46 | r := gen.OpenCLDRCoreZip() |
| 47 | defer r.Close() |
| 48 | |
| 49 | d := &cldr.Decoder{} |
Marcel van Lohuizen | 7f6d024 | 2015-10-01 17:15:28 +0200 | [diff] [blame] | 50 | d.SetDirFilter("supplemental", "main") |
| 51 | d.SetSectionFilter("numbers") |
Marcel van Lohuizen | 6368131 | 2015-08-24 11:51:03 +0200 | [diff] [blame] | 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) |
Marcel van Lohuizen | 7f6d024 | 2015-10-01 17:15:28 +0200 | [diff] [blame] | 63 | b := &builder{} |
| 64 | b.genCurrencies(w, data.Supplemental()) |
| 65 | b.genSymbols(w, data) |
Marcel van Lohuizen | 6368131 | 2015-08-24 11:51:03 +0200 | [diff] [blame] | 66 | } |
| 67 | |
Marcel van Lohuizen | 6368131 | 2015-08-24 11:51:03 +0200 | [diff] [blame] | 68 | var 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 | |
Marcel van Lohuizen | 7f6d024 | 2015-10-01 17:15:28 +0200 | [diff] [blame] | 81 | type builder struct { |
| 82 | currencies tag.Index |
| 83 | numCurrencies int |
| 84 | } |
| 85 | |
| 86 | func (b *builder) genCurrencies(w *gen.CodeWriter, data *cldr.SupplementalData) { |
Marcel van Lohuizen | 6368131 | 2015-08-24 11:51:03 +0200 | [diff] [blame] | 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 | } |
Marcel van Lohuizen | f79ed80 | 2015-12-14 18:19:08 +0100 | [diff] [blame] | 97 | // Not included in the list for some reasons: |
| 98 | currencies = append(currencies, "MVP") |
Marcel van Lohuizen | 6368131 | 2015-08-24 11:51:03 +0200 | [diff] [blame] | 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 | } |
Marcel van Lohuizen | f024ad8 | 2015-10-15 10:52:00 +0200 | [diff] [blame] | 127 | standard := getRoundingIndex(info.Digits, info.Rounding, 0) |
| 128 | cash := getRoundingIndex(info.CashDigits, info.CashRounding, standard) |
Marcel van Lohuizen | 6368131 | 2015-08-24 11:51:03 +0200 | [diff] [blame] | 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 | |
Marcel van Lohuizen | 7f6d024 | 2015-10-01 17:15:28 +0200 | [diff] [blame] | 141 | b.currencies = tag.Index(strings.Join(currencies, "")) |
Marcel van Lohuizen | 6368131 | 2015-08-24 11:51:03 +0200 | [diff] [blame] | 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.`) |
Marcel van Lohuizen | 7f6d024 | 2015-10-01 17:15:28 +0200 | [diff] [blame] | 146 | w.WriteConst("currency", b.currencies) |
Marcel van Lohuizen | 6368131 | 2015-08-24 11:51:03 +0200 | [diff] [blame] | 147 | |
| 148 | // Hack alert: gofmt indents a trailing comment after an indented string. |
| 149 | // Ensure that the next thing written is not a comment. |
Marcel van Lohuizen | 7f6d024 | 2015-10-01 17:15:28 +0200 | [diff] [blame] | 150 | b.numCurrencies = (len(b.currencies) / 4) - 2 |
| 151 | w.WriteConst("numCurrencies", b.numCurrencies) |
Marcel van Lohuizen | 601048a | 2015-09-21 21:52:40 +0200 | [diff] [blame] | 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)), |
Marcel van Lohuizen | 7f6d024 | 2015-10-01 17:15:28 +0200 | [diff] [blame] | 169 | code: uint16(b.currencies.Index([]byte(cur.Iso4217))), |
Marcel van Lohuizen | 601048a | 2015-09-21 21:52:40 +0200 | [diff] [blame] | 170 | }) |
| 171 | } |
| 172 | sort.Sort(byRegion(regionToCurrency)) |
| 173 | |
| 174 | w.WriteType(toCurrency{}) |
| 175 | w.WriteVar("regionToCurrency", regionToCurrency) |
Marcel van Lohuizen | f773ec0 | 2016-05-05 15:32:54 -0700 | [diff] [blame] | 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) |
Marcel van Lohuizen | 6368131 | 2015-08-24 11:51:03 +0200 | [diff] [blame] | 203 | } |
| 204 | |
Marcel van Lohuizen | f773ec0 | 2016-05-05 15:32:54 -0700 | [diff] [blame] | 205 | type regionInfo struct { |
| 206 | region uint16 |
| 207 | code uint16 // 0x8000 not legal tender |
| 208 | from uint32 |
| 209 | to uint32 |
| 210 | } |
| 211 | |
| 212 | type byRegionCode []regionInfo |
| 213 | |
| 214 | func (a byRegionCode) Len() int { return len(a) } |
| 215 | func (a byRegionCode) Swap(i, j int) { a[i], a[j] = a[j], a[i] } |
| 216 | func (a byRegionCode) Less(i, j int) bool { return a[i].region < a[j].region } |
| 217 | |
Marcel van Lohuizen | 601048a | 2015-09-21 21:52:40 +0200 | [diff] [blame] | 218 | type toCurrency struct { |
| 219 | region uint16 |
| 220 | code uint16 |
| 221 | } |
| 222 | |
| 223 | type byRegion []toCurrency |
| 224 | |
| 225 | func (a byRegion) Len() int { return len(a) } |
| 226 | func (a byRegion) Swap(i, j int) { a[i], a[j] = a[j], a[i] } |
| 227 | func (a byRegion) Less(i, j int) bool { return a[i].region < a[j].region } |
| 228 | |
Marcel van Lohuizen | 6368131 | 2015-08-24 11:51:03 +0200 | [diff] [blame] | 229 | func mkCurrencyInfo(standard, cash int) string { |
| 230 | return string([]byte{byte(cash<<cashShift | standard)}) |
| 231 | } |
| 232 | |
Marcel van Lohuizen | f024ad8 | 2015-10-15 10:52:00 +0200 | [diff] [blame] | 233 | func getRoundingIndex(digits, rounding string, defIndex int) int { |
| 234 | round := roundings[defIndex] // default |
Marcel van Lohuizen | 6368131 | 2015-08-24 11:51:03 +0200 | [diff] [blame] | 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 | |
Marcel van Lohuizen | 7f6d024 | 2015-10-01 17:15:28 +0200 | [diff] [blame] | 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. |
| 258 | func (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 |
Marcel van Lohuizen | ab48842 | 2018-03-01 22:29:26 +0100 | [diff] [blame] | 270 | var symbols [compact.NumCompactTags][][numTypes]*string |
Marcel van Lohuizen | 7f6d024 | 2015-10-01 17:15:28 +0200 | [diff] [blame] | 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 | |
Marcel van Lohuizen | ab48842 | 2018-03-01 22:29:26 +0100 | [diff] [blame] | 279 | langIndex, ok := compact.LanguageID(compact.Tag(language.MustParse(lang))) |
Marcel van Lohuizen | 7f6d024 | 2015-10-01 17:15:28 +0200 | [diff] [blame] | 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)) |
Marcel van Lohuizen | f79ed80 | 2015-12-14 18:19:08 +0100 | [diff] [blame] | 297 | // XXX gets reassigned to 0 in the package's code. |
| 298 | if c.Type == "XXX" { |
| 299 | cur = 0 |
| 300 | } |
Marcel van Lohuizen | 7f6d024 | 2015-10-01 17:15:28 +0200 | [diff] [blame] | 301 | if cur == -1 { |
Marcel van Lohuizen | f79ed80 | 2015-12-14 18:19:08 +0100 | [diff] [blame] | 302 | fmt.Println("Unsupported:", c.Type) |
Marcel van Lohuizen | 7f6d024 | 2015-10-01 17:15:28 +0200 | [diff] [blame] | 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 | } |
Marcel van Lohuizen | ab48842 | 2018-03-01 22:29:26 +0100 | [diff] [blame] | 323 | for p := compact.ID(langIndex); p != 0; { |
| 324 | p = p.Parent() |
Marcel van Lohuizen | 7f6d024 | 2015-10-01 17:15:28 +0200 | [diff] [blame] | 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 | } |
Marcel van Lohuizen | 6368131 | 2015-08-24 11:51:03 +0200 | [diff] [blame] | 394 | func 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 | } |