gen.go 10 KB

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