1// Copyright 2017 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
5package cldrtree
6
7import (
8	"bytes"
9	"fmt"
10	"io"
11	"reflect"
12	"strconv"
13	"strings"
14
15	"golang.org/x/text/internal/gen"
16)
17
18func generate(b *Builder, t *Tree, w *gen.CodeWriter) error {
19	fmt.Fprintln(w, `import "golang.org/x/text/internal/cldrtree"`)
20	fmt.Fprintln(w)
21
22	fmt.Fprintf(w, "var tree = &cldrtree.Tree{locales, indices, buckets}\n\n")
23
24	w.WriteComment("Path values:\n" + b.stats())
25	fmt.Fprintln(w)
26
27	// Generate enum types.
28	for _, e := range b.enums {
29		// Build enum types.
30		w.WriteComment("%s specifies a property of a CLDR field.", e.name)
31		fmt.Fprintf(w, "type %s uint16\n", e.name)
32	}
33
34	d, err := getEnumData(b)
35	if err != nil {
36		return err
37	}
38	fmt.Fprintln(w, "const (")
39	for i, k := range d.keys {
40		fmt.Fprintf(w, "%s %s = %d // %s\n", toCamel(k), d.enums[i], d.m[k], k)
41	}
42	fmt.Fprintln(w, ")")
43
44	w.WriteVar("locales", t.Locales)
45	w.WriteVar("indices", t.Indices)
46
47	// Generate string buckets.
48	fmt.Fprintln(w, "var buckets = []string{")
49	for i := range t.Buckets {
50		fmt.Fprintf(w, "bucket%d,\n", i)
51	}
52	fmt.Fprint(w, "}\n\n")
53	w.Size += int(reflect.TypeOf("").Size()) * len(t.Buckets)
54
55	// Generate string buckets.
56	for i, bucket := range t.Buckets {
57		w.WriteVar(fmt.Sprint("bucket", i), bucket)
58	}
59	return nil
60}
61
62func generateTestData(b *Builder, w *gen.CodeWriter) error {
63	d, err := getEnumData(b)
64	if err != nil {
65		return err
66	}
67
68	fmt.Fprintln(w)
69	fmt.Fprintln(w, "var enumMap = map[string]uint16{")
70	fmt.Fprintln(w, `"": 0,`)
71	for _, k := range d.keys {
72		fmt.Fprintf(w, "%q: %d,\n", k, d.m[k])
73	}
74	fmt.Fprintln(w, "}")
75	return nil
76}
77
78func toCamel(s string) string {
79	p := strings.Split(s, "-")
80	for i, s := range p[1:] {
81		p[i+1] = strings.Title(s)
82	}
83	return strings.Replace(strings.Join(p, ""), "/", "", -1)
84}
85
86func (b *Builder) stats() string {
87	w := &bytes.Buffer{}
88
89	b.rootMeta.validate()
90	for _, es := range b.enums {
91		fmt.Fprintf(w, "<%s>\n", es.name)
92		printEnumValues(w, es, 1, nil)
93	}
94	fmt.Fprintln(w)
95	printEnums(w, b.rootMeta.typeInfo, 0)
96	fmt.Fprintln(w)
97	fmt.Fprintln(w, "Nr elem:           ", len(b.strToBucket))
98	fmt.Fprintln(w, "uniqued size:      ", b.size)
99	fmt.Fprintln(w, "total string size: ", b.sizeAll)
100	fmt.Fprintln(w, "bucket waste:      ", b.bucketWaste)
101
102	return w.String()
103}
104
105func printEnums(w io.Writer, s *typeInfo, indent int) {
106	idStr := strings.Repeat("  ", indent) + "- "
107	e := s.enum
108	if e == nil {
109		if len(s.entries) > 0 {
110			panic(fmt.Errorf("has entries but no enum values: %#v", s.entries))
111		}
112		return
113	}
114	if e.name != "" {
115		fmt.Fprintf(w, "%s<%s>\n", idStr, e.name)
116	} else {
117		printEnumValues(w, e, indent, s)
118	}
119	if s.sharedKeys() {
120		for _, v := range s.entries {
121			printEnums(w, v, indent+1)
122			break
123		}
124	}
125}
126
127func printEnumValues(w io.Writer, e *enum, indent int, info *typeInfo) {
128	idStr := strings.Repeat("  ", indent) + "- "
129	for i := 0; i < len(e.keys); i++ {
130		fmt.Fprint(w, idStr)
131		k := e.keys[i]
132		if u, err := strconv.ParseUint(k, 10, 16); err == nil {
133			fmt.Fprintf(w, "%s", k)
134			// Skip contiguous integers
135			var v, last uint64
136			for i++; i < len(e.keys); i++ {
137				k = e.keys[i]
138				if v, err = strconv.ParseUint(k, 10, 16); err != nil {
139					break
140				}
141				last = v
142			}
143			if u < last {
144				fmt.Fprintf(w, `..%d`, last)
145			}
146			fmt.Fprintln(w)
147			if err != nil {
148				fmt.Fprintf(w, "%s%s\n", idStr, k)
149			}
150		} else if k == "" {
151			fmt.Fprintln(w, `""`)
152		} else {
153			fmt.Fprintf(w, "%s\n", k)
154		}
155		if info != nil && !info.sharedKeys() {
156			if e := info.entries[enumIndex(i)]; e != nil {
157				printEnums(w, e, indent+1)
158			}
159		}
160	}
161}
162
163func getEnumData(b *Builder) (*enumData, error) {
164	d := &enumData{m: map[string]int{}}
165	if errStr := d.insert(b.rootMeta.typeInfo); errStr != "" {
166		// TODO: consider returning the error.
167		return nil, fmt.Errorf("cldrtree: %s", errStr)
168	}
169	return d, nil
170}
171
172type enumData struct {
173	m     map[string]int
174	keys  []string
175	enums []string
176}
177
178func (d *enumData) insert(t *typeInfo) (errStr string) {
179	e := t.enum
180	if e == nil {
181		return ""
182	}
183	for i, k := range e.keys {
184		if _, err := strconv.ParseUint(k, 10, 16); err == nil {
185			// We don't include any enum that has integer values.
186			break
187		}
188		if v, ok := d.m[k]; ok {
189			if v != i {
190				return fmt.Sprintf("%q has value %d and %d", k, i, v)
191			}
192		} else {
193			d.m[k] = i
194			if k != "" {
195				d.keys = append(d.keys, k)
196				d.enums = append(d.enums, e.name)
197			}
198		}
199	}
200	for i := range t.enum.keys {
201		if e := t.entries[enumIndex(i)]; e != nil {
202			if errStr := d.insert(e); errStr != "" {
203				return fmt.Sprintf("%q>%v", t.enum.keys[i], errStr)
204			}
205		}
206	}
207	return ""
208}
209