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