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