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 plural
6
7import (
8	"fmt"
9	"io/ioutil"
10	"reflect"
11	"strconv"
12
13	"golang.org/x/text/internal/catmsg"
14	"golang.org/x/text/internal/number"
15	"golang.org/x/text/language"
16	"golang.org/x/text/message/catalog"
17)
18
19// TODO: consider deleting this interface. Maybe VisibleDigits is always
20// sufficient and practical.
21
22// Interface is used for types that can determine their own plural form.
23type Interface interface {
24	// PluralForm reports the plural form for the given language of the
25	// underlying value. It also returns the integer value. If the integer value
26	// is larger than fits in n, PluralForm may return a value modulo
27	// 10,000,000.
28	PluralForm(t language.Tag, scale int) (f Form, n int)
29}
30
31// Selectf returns the first case for which its selector is a match for the
32// arg-th substitution argument to a formatting call, formatting it as indicated
33// by format.
34//
35// The cases argument are pairs of selectors and messages. Selectors are of type
36// string or Form. Messages are of type string or catalog.Message. A selector
37// matches an argument if:
38//    - it is "other" or Other
39//    - it matches the plural form of the argument: "zero", "one", "two", "few",
40//      or "many", or the equivalent Form
41//    - it is of the form "=x" where x is an integer that matches the value of
42//      the argument.
43//    - it is of the form "<x" where x is an integer that is larger than the
44//      argument.
45//
46// The format argument determines the formatting parameters for which to
47// determine the plural form. This is especially relevant for non-integer
48// values.
49//
50// The format string may be "", in which case a best-effort attempt is made to
51// find a reasonable representation on which to base the plural form. Examples
52// of format strings are:
53//   - %.2f   decimal with scale 2
54//   - %.2e   scientific notation with precision 3 (scale + 1)
55//   - %d     integer
56func Selectf(arg int, format string, cases ...interface{}) catalog.Message {
57	var p parser
58	// Intercept the formatting parameters of format by doing a dummy print.
59	fmt.Fprintf(ioutil.Discard, format, &p)
60	m := &message{arg, kindDefault, 0, cases}
61	switch p.verb {
62	case 'g':
63		m.kind = kindPrecision
64		m.scale = p.scale
65	case 'f':
66		m.kind = kindScale
67		m.scale = p.scale
68	case 'e':
69		m.kind = kindScientific
70		m.scale = p.scale
71	case 'd':
72		m.kind = kindScale
73		m.scale = 0
74	default:
75		// TODO: do we need to handle errors?
76	}
77	return m
78}
79
80type parser struct {
81	verb  rune
82	scale int
83}
84
85func (p *parser) Format(s fmt.State, verb rune) {
86	p.verb = verb
87	p.scale = -1
88	if prec, ok := s.Precision(); ok {
89		p.scale = prec
90	}
91}
92
93type message struct {
94	arg   int
95	kind  int
96	scale int
97	cases []interface{}
98}
99
100const (
101	// Start with non-ASCII to allow skipping values.
102	kindDefault    = 0x80 + iota
103	kindScale      // verb f, number of fraction digits follows
104	kindScientific // verb e, number of fraction digits follows
105	kindPrecision  // verb g, number of significant digits follows
106)
107
108var handle = catmsg.Register("golang.org/x/text/feature/plural:plural", execute)
109
110func (m *message) Compile(e *catmsg.Encoder) error {
111	e.EncodeMessageType(handle)
112
113	e.EncodeUint(uint64(m.arg))
114
115	e.EncodeUint(uint64(m.kind))
116	if m.kind > kindDefault {
117		e.EncodeUint(uint64(m.scale))
118	}
119
120	forms := validForms(cardinal, e.Language())
121
122	for i := 0; i < len(m.cases); {
123		if err := compileSelector(e, forms, m.cases[i]); err != nil {
124			return err
125		}
126		if i++; i >= len(m.cases) {
127			return fmt.Errorf("plural: no message defined for selector %v", m.cases[i-1])
128		}
129		var msg catalog.Message
130		switch x := m.cases[i].(type) {
131		case string:
132			msg = catalog.String(x)
133		case catalog.Message:
134			msg = x
135		default:
136			return fmt.Errorf("plural: message of type %T; must be string or catalog.Message", x)
137		}
138		if err := e.EncodeMessage(msg); err != nil {
139			return err
140		}
141		i++
142	}
143	return nil
144}
145
146func compileSelector(e *catmsg.Encoder, valid []Form, selector interface{}) error {
147	form := Other
148	switch x := selector.(type) {
149	case string:
150		if x == "" {
151			return fmt.Errorf("plural: empty selector")
152		}
153		if c := x[0]; c == '=' || c == '<' {
154			val, err := strconv.ParseUint(x[1:], 10, 16)
155			if err != nil {
156				return fmt.Errorf("plural: invalid number in selector %q: %v", selector, err)
157			}
158			e.EncodeUint(uint64(c))
159			e.EncodeUint(val)
160			return nil
161		}
162		var ok bool
163		form, ok = countMap[x]
164		if !ok {
165			return fmt.Errorf("plural: invalid plural form %q", selector)
166		}
167	case Form:
168		form = x
169	default:
170		return fmt.Errorf("plural: selector of type %T; want string or Form", selector)
171	}
172
173	ok := false
174	for _, f := range valid {
175		if f == form {
176			ok = true
177			break
178		}
179	}
180	if !ok {
181		return fmt.Errorf("plural: form %q not supported for language %q", selector, e.Language())
182	}
183	e.EncodeUint(uint64(form))
184	return nil
185}
186
187func execute(d *catmsg.Decoder) bool {
188	lang := d.Language()
189	argN := int(d.DecodeUint())
190	kind := int(d.DecodeUint())
191	scale := -1 // default
192	if kind > kindDefault {
193		scale = int(d.DecodeUint())
194	}
195	form := Other
196	n := -1
197	if arg := d.Arg(argN); arg == nil {
198		// Default to Other.
199	} else if x, ok := arg.(number.VisibleDigits); ok {
200		d := x.Digits(nil, lang, scale)
201		form, n = cardinal.matchDisplayDigits(lang, &d)
202	} else if x, ok := arg.(Interface); ok {
203		// This covers lists and formatters from the number package.
204		form, n = x.PluralForm(lang, scale)
205	} else {
206		var f number.Formatter
207		switch kind {
208		case kindScale:
209			f.InitDecimal(lang)
210			f.SetScale(scale)
211		case kindScientific:
212			f.InitScientific(lang)
213			f.SetScale(scale)
214		case kindPrecision:
215			f.InitDecimal(lang)
216			f.SetPrecision(scale)
217		case kindDefault:
218			// sensible default
219			f.InitDecimal(lang)
220			if k := reflect.TypeOf(arg).Kind(); reflect.Int <= k && k <= reflect.Uintptr {
221				f.SetScale(0)
222			} else {
223				f.SetScale(2)
224			}
225		}
226		var dec number.Decimal // TODO: buffer in Printer
227		dec.Convert(f.RoundingContext, arg)
228		v := number.FormatDigits(&dec, f.RoundingContext)
229		if !v.NaN && !v.Inf {
230			form, n = cardinal.matchDisplayDigits(d.Language(), &v)
231		}
232	}
233	for !d.Done() {
234		f := d.DecodeUint()
235		if (f == '=' && n == int(d.DecodeUint())) ||
236			(f == '<' && 0 <= n && n < int(d.DecodeUint())) ||
237			form == Form(f) ||
238			Other == Form(f) {
239			return d.ExecuteMessage()
240		}
241		d.SkipMessage()
242	}
243	return false
244}
245