blob: 4a2c8554971ca160429a99ac5274289c98e80a0b [file] [log] [blame]
Marcel van Lohuizen63681312015-08-24 11:51:03 +02001// 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 Cox8f690f22021-02-19 18:54:44 -05005//go:build ignore
Marcel van Lohuizen63681312015-08-24 11:51:03 +02006// +build ignore
7
8// Generator for currency-related data.
9
10package main
11
12import (
Marcel van Lohuizen63681312015-08-24 11:51:03 +020013 "flag"
14 "fmt"
Marcel van Lohuizen63681312015-08-24 11:51:03 +020015 "log"
16 "os"
17 "sort"
18 "strconv"
19 "strings"
Marcel van Lohuizenf773ec02016-05-05 15:32:54 -070020 "time"
Marcel van Lohuizen63681312015-08-24 11:51:03 +020021
Marcel van Lohuizenab488422018-03-01 22:29:26 +010022 "golang.org/x/text/internal/language/compact"
23
Marcel van Lohuizen63681312015-08-24 11:51:03 +020024 "golang.org/x/text/internal/gen"
25 "golang.org/x/text/internal/tag"
Marcel van Lohuizen601048a2015-09-21 21:52:40 +020026 "golang.org/x/text/language"
Marcel van Lohuizenedeeb432015-12-05 13:06:26 +010027 "golang.org/x/text/unicode/cldr"
Marcel van Lohuizen63681312015-08-24 11:51:03 +020028)
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
Marcel van Lohuizen54db2312015-11-24 19:16:13 +010043 gen.Repackage("gen_common.go", "common.go", "currency")
Marcel van Lohuizen63681312015-08-24 11:51:03 +020044
45 // Read the CLDR zip file.
46 r := gen.OpenCLDRCoreZip()
47 defer r.Close()
48
49 d := &cldr.Decoder{}
Marcel van Lohuizen7f6d0242015-10-01 17:15:28 +020050 d.SetDirFilter("supplemental", "main")
51 d.SetSectionFilter("numbers")
Marcel van Lohuizen63681312015-08-24 11:51:03 +020052 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 Lohuizen7f6d0242015-10-01 17:15:28 +020063 b := &builder{}
64 b.genCurrencies(w, data.Supplemental())
65 b.genSymbols(w, data)
Marcel van Lohuizen63681312015-08-24 11:51:03 +020066}
67
Marcel van Lohuizen63681312015-08-24 11:51:03 +020068var 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 Lohuizen7f6d0242015-10-01 17:15:28 +020081type builder struct {
82 currencies tag.Index
83 numCurrencies int
84}
85
86func (b *builder) genCurrencies(w *gen.CodeWriter, data *cldr.SupplementalData) {
Marcel van Lohuizen63681312015-08-24 11:51:03 +020087 // 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 Lohuizenf79ed802015-12-14 18:19:08 +010097 // Not included in the list for some reasons:
98 currencies = append(currencies, "MVP")
Marcel van Lohuizen63681312015-08-24 11:51:03 +020099
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 Lohuizenf024ad82015-10-15 10:52:00 +0200127 standard := getRoundingIndex(info.Digits, info.Rounding, 0)
128 cash := getRoundingIndex(info.CashDigits, info.CashRounding, standard)
Marcel van Lohuizen63681312015-08-24 11:51:03 +0200129
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 Lohuizen7f6d0242015-10-01 17:15:28 +0200141 b.currencies = tag.Index(strings.Join(currencies, ""))
Marcel van Lohuizen63681312015-08-24 11:51:03 +0200142 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 Lohuizen7f6d0242015-10-01 17:15:28 +0200146 w.WriteConst("currency", b.currencies)
Marcel van Lohuizen63681312015-08-24 11:51:03 +0200147
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 Lohuizen7f6d0242015-10-01 17:15:28 +0200150 b.numCurrencies = (len(b.currencies) / 4) - 2
151 w.WriteConst("numCurrencies", b.numCurrencies)
Marcel van Lohuizen601048a2015-09-21 21:52:40 +0200152
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 Lohuizen7f6d0242015-10-01 17:15:28 +0200169 code: uint16(b.currencies.Index([]byte(cur.Iso4217))),
Marcel van Lohuizen601048a2015-09-21 21:52:40 +0200170 })
171 }
172 sort.Sort(byRegion(regionToCurrency))
173
174 w.WriteType(toCurrency{})
175 w.WriteVar("regionToCurrency", regionToCurrency)
Marcel van Lohuizenf773ec02016-05-05 15:32:54 -0700176
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 Lohuizen63681312015-08-24 11:51:03 +0200203}
204
Marcel van Lohuizenf773ec02016-05-05 15:32:54 -0700205type 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
Marcel van Lohuizen601048a2015-09-21 21:52:40 +0200218type 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
Marcel van Lohuizen63681312015-08-24 11:51:03 +0200229func mkCurrencyInfo(standard, cash int) string {
230 return string([]byte{byte(cash<<cashShift | standard)})
231}
232
Marcel van Lohuizenf024ad82015-10-15 10:52:00 +0200233func getRoundingIndex(digits, rounding string, defIndex int) int {
234 round := roundings[defIndex] // default
Marcel van Lohuizen63681312015-08-24 11:51:03 +0200235
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 Lohuizen7f6d0242015-10-01 17:15:28 +0200253// 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
Marcel van Lohuizenab488422018-03-01 22:29:26 +0100270 var symbols [compact.NumCompactTags][][numTypes]*string
Marcel van Lohuizen7f6d0242015-10-01 17:15:28 +0200271
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 Lohuizenab488422018-03-01 22:29:26 +0100279 langIndex, ok := compact.LanguageID(compact.Tag(language.MustParse(lang)))
Marcel van Lohuizen7f6d0242015-10-01 17:15:28 +0200280 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 Lohuizenf79ed802015-12-14 18:19:08 +0100297 // XXX gets reassigned to 0 in the package's code.
298 if c.Type == "XXX" {
299 cur = 0
300 }
Marcel van Lohuizen7f6d0242015-10-01 17:15:28 +0200301 if cur == -1 {
Marcel van Lohuizenf79ed802015-12-14 18:19:08 +0100302 fmt.Println("Unsupported:", c.Type)
Marcel van Lohuizen7f6d0242015-10-01 17:15:28 +0200303 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 Lohuizenab488422018-03-01 22:29:26 +0100323 for p := compact.ID(langIndex); p != 0; {
324 p = p.Parent()
Marcel van Lohuizen7f6d0242015-10-01 17:15:28 +0200325 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 Lohuizen63681312015-08-24 11:51:03 +0200394func 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}