| // Copyright 2017 The Go Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style | |
| // license that can be found in the LICENSE file. | |
| package plural | |
| import ( | |
| "fmt" | |
| "io" | |
| "reflect" | |
| "strconv" | |
| "golang.org/x/text/internal/catmsg" | |
| "golang.org/x/text/internal/number" | |
| "golang.org/x/text/language" | |
| "golang.org/x/text/message/catalog" | |
| ) | |
| // TODO: consider deleting this interface. Maybe VisibleDigits is always | |
| // sufficient and practical. | |
| // Interface is used for types that can determine their own plural form. | |
| type Interface interface { | |
| // PluralForm reports the plural form for the given language of the | |
| // underlying value. It also returns the integer value. If the integer value | |
| // is larger than fits in n, PluralForm may return a value modulo | |
| // 10,000,000. | |
| PluralForm(t language.Tag, scale int) (f Form, n int) | |
| } | |
| // Selectf returns the first case for which its selector is a match for the | |
| // arg-th substitution argument to a formatting call, formatting it as indicated | |
| // by format. | |
| // | |
| // The cases argument are pairs of selectors and messages. Selectors are of type | |
| // string or Form. Messages are of type string or catalog.Message. A selector | |
| // matches an argument if: | |
| // - it is "other" or Other | |
| // - it matches the plural form of the argument: "zero", "one", "two", "few", | |
| // or "many", or the equivalent Form | |
| // - it is of the form "=x" where x is an integer that matches the value of | |
| // the argument. | |
| // - it is of the form "<x" where x is an integer that is larger than the | |
| // argument. | |
| // | |
| // The format argument determines the formatting parameters for which to | |
| // determine the plural form. This is especially relevant for non-integer | |
| // values. | |
| // | |
| // The format string may be "", in which case a best-effort attempt is made to | |
| // find a reasonable representation on which to base the plural form. Examples | |
| // of format strings are: | |
| // - %.2f decimal with scale 2 | |
| // - %.2e scientific notation with precision 3 (scale + 1) | |
| // - %d integer | |
| func Selectf(arg int, format string, cases ...interface{}) catalog.Message { | |
| var p parser | |
| // Intercept the formatting parameters of format by doing a dummy print. | |
| fmt.Fprintf(io.Discard, format, &p) | |
| m := &message{arg, kindDefault, 0, cases} | |
| switch p.verb { | |
| case 'g': | |
| m.kind = kindPrecision | |
| m.scale = p.scale | |
| case 'f': | |
| m.kind = kindScale | |
| m.scale = p.scale | |
| case 'e': | |
| m.kind = kindScientific | |
| m.scale = p.scale | |
| case 'd': | |
| m.kind = kindScale | |
| m.scale = 0 | |
| default: | |
| // TODO: do we need to handle errors? | |
| } | |
| return m | |
| } | |
| type parser struct { | |
| verb rune | |
| scale int | |
| } | |
| func (p *parser) Format(s fmt.State, verb rune) { | |
| p.verb = verb | |
| p.scale = -1 | |
| if prec, ok := s.Precision(); ok { | |
| p.scale = prec | |
| } | |
| } | |
| type message struct { | |
| arg int | |
| kind int | |
| scale int | |
| cases []interface{} | |
| } | |
| const ( | |
| // Start with non-ASCII to allow skipping values. | |
| kindDefault = 0x80 + iota | |
| kindScale // verb f, number of fraction digits follows | |
| kindScientific // verb e, number of fraction digits follows | |
| kindPrecision // verb g, number of significant digits follows | |
| ) | |
| var handle = catmsg.Register("golang.org/x/text/feature/plural:plural", execute) | |
| func (m *message) Compile(e *catmsg.Encoder) error { | |
| e.EncodeMessageType(handle) | |
| e.EncodeUint(uint64(m.arg)) | |
| e.EncodeUint(uint64(m.kind)) | |
| if m.kind > kindDefault { | |
| e.EncodeUint(uint64(m.scale)) | |
| } | |
| forms := validForms(cardinal, e.Language()) | |
| for i := 0; i < len(m.cases); { | |
| if err := compileSelector(e, forms, m.cases[i]); err != nil { | |
| return err | |
| } | |
| if i++; i >= len(m.cases) { | |
| return fmt.Errorf("plural: no message defined for selector %v", m.cases[i-1]) | |
| } | |
| var msg catalog.Message | |
| switch x := m.cases[i].(type) { | |
| case string: | |
| msg = catalog.String(x) | |
| case catalog.Message: | |
| msg = x | |
| default: | |
| return fmt.Errorf("plural: message of type %T; must be string or catalog.Message", x) | |
| } | |
| if err := e.EncodeMessage(msg); err != nil { | |
| return err | |
| } | |
| i++ | |
| } | |
| return nil | |
| } | |
| func compileSelector(e *catmsg.Encoder, valid []Form, selector interface{}) error { | |
| form := Other | |
| switch x := selector.(type) { | |
| case string: | |
| if x == "" { | |
| return fmt.Errorf("plural: empty selector") | |
| } | |
| if c := x[0]; c == '=' || c == '<' { | |
| val, err := strconv.ParseUint(x[1:], 10, 16) | |
| if err != nil { | |
| return fmt.Errorf("plural: invalid number in selector %q: %v", selector, err) | |
| } | |
| e.EncodeUint(uint64(c)) | |
| e.EncodeUint(val) | |
| return nil | |
| } | |
| var ok bool | |
| form, ok = countMap[x] | |
| if !ok { | |
| return fmt.Errorf("plural: invalid plural form %q", selector) | |
| } | |
| case Form: | |
| form = x | |
| default: | |
| return fmt.Errorf("plural: selector of type %T; want string or Form", selector) | |
| } | |
| ok := false | |
| for _, f := range valid { | |
| if f == form { | |
| ok = true | |
| break | |
| } | |
| } | |
| if !ok { | |
| return fmt.Errorf("plural: form %q not supported for language %q", selector, e.Language()) | |
| } | |
| e.EncodeUint(uint64(form)) | |
| return nil | |
| } | |
| func execute(d *catmsg.Decoder) bool { | |
| lang := d.Language() | |
| argN := int(d.DecodeUint()) | |
| kind := int(d.DecodeUint()) | |
| scale := -1 // default | |
| if kind > kindDefault { | |
| scale = int(d.DecodeUint()) | |
| } | |
| form := Other | |
| n := -1 | |
| if arg := d.Arg(argN); arg == nil { | |
| // Default to Other. | |
| } else if x, ok := arg.(number.VisibleDigits); ok { | |
| d := x.Digits(nil, lang, scale) | |
| form, n = cardinal.matchDisplayDigits(lang, &d) | |
| } else if x, ok := arg.(Interface); ok { | |
| // This covers lists and formatters from the number package. | |
| form, n = x.PluralForm(lang, scale) | |
| } else { | |
| var f number.Formatter | |
| switch kind { | |
| case kindScale: | |
| f.InitDecimal(lang) | |
| f.SetScale(scale) | |
| case kindScientific: | |
| f.InitScientific(lang) | |
| f.SetScale(scale) | |
| case kindPrecision: | |
| f.InitDecimal(lang) | |
| f.SetPrecision(scale) | |
| case kindDefault: | |
| // sensible default | |
| f.InitDecimal(lang) | |
| if k := reflect.TypeOf(arg).Kind(); reflect.Int <= k && k <= reflect.Uintptr { | |
| f.SetScale(0) | |
| } else { | |
| f.SetScale(2) | |
| } | |
| } | |
| var dec number.Decimal // TODO: buffer in Printer | |
| dec.Convert(f.RoundingContext, arg) | |
| v := number.FormatDigits(&dec, f.RoundingContext) | |
| if !v.NaN && !v.Inf { | |
| form, n = cardinal.matchDisplayDigits(d.Language(), &v) | |
| } | |
| } | |
| for !d.Done() { | |
| f := d.DecodeUint() | |
| if (f == '=' && n == int(d.DecodeUint())) || | |
| (f == '<' && 0 <= n && n < int(d.DecodeUint())) || | |
| form == Form(f) || | |
| Other == Form(f) { | |
| return d.ExecuteMessage() | |
| } | |
| d.SkipMessage() | |
| } | |
| return false | |
| } |