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