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