You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

402 lines
10 KiB

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