OSDN Git Service

new repo
[bytom/vapor.git] / vendor / golang.org / x / text / internal / catmsg / catmsg.go
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
5 // Package catmsg contains support types for package x/text/message/catalog.
6 //
7 // This package contains the low-level implementations of Message used by the
8 // catalog package and provides primitives for other packages to implement their
9 // own. For instance, the plural package provides functionality for selecting
10 // translation strings based on the plural category of substitution arguments.
11 //
12 //
13 // Encoding and Decoding
14 //
15 // Catalogs store Messages encoded as a single string. Compiling a message into
16 // a string both results in compacter representation and speeds up evaluation.
17 //
18 // A Message must implement a Compile method to convert its arbitrary
19 // representation to a string. The Compile method takes an Encoder which
20 // facilitates serializing the message. Encoders also provide more context of
21 // the messages's creation (such as for which language the message is intended),
22 // which may not be known at the time of the creation of the message.
23 //
24 // Each message type must also have an accompanying decoder registered to decode
25 // the message. This decoder takes a Decoder argument which provides the
26 // counterparts for the decoding.
27 //
28 //
29 // Renderers
30 //
31 // A Decoder must be initialized with a Renderer implementation. These
32 // implementations must be provided by packages that use Catalogs, typically
33 // formatting packages such as x/text/message. A typical user will not need to
34 // worry about this type; it is only relevant to packages that do string
35 // formatting and want to use the catalog package to handle localized strings.
36 //
37 // A package that uses catalogs for selecting strings receives selection results
38 // as sequence of substrings passed to the Renderer. The following snippet shows
39 // how to express the above example using the message package.
40 //
41 //   message.Set(language.English, "You are %d minute(s) late.",
42 //       catalog.Var("minutes", plural.Select(1, "one", "minute")),
43 //       catalog.String("You are %[1]d ${minutes} late."))
44 //
45 //   p := message.NewPrinter(language.English)
46 //   p.Printf("You are %d minute(s) late.", 5) // always 5 minutes late.
47 //
48 // To evaluate the Printf, package message wraps the arguments in a Renderer
49 // that is passed to the catalog for message decoding. The call sequence that
50 // results from evaluating the above message, assuming the person is rather
51 // tardy, is:
52 //
53 //   Render("You are %[1]d ")
54 //   Arg(1)
55 //   Render("minutes")
56 //   Render(" late.")
57 //
58 // The calls to Arg is caused by the plural.Select execution, which evaluates
59 // the argument to determine whether the singular or plural message form should
60 // be selected. The calls to Render reports the partial results to the message
61 // package for further evaluation.
62 package catmsg
63
64 import (
65         "errors"
66         "fmt"
67         "strconv"
68         "strings"
69         "sync"
70
71         "golang.org/x/text/language"
72 )
73
74 // A Handle refers to a registered message type.
75 type Handle int
76
77 // First is used as a Handle to EncodeMessageType, followed by a series of calls
78 // to EncodeMessage, to implement selecting the first matching Message.
79 //
80 // TODO: this can be removed once we either can use type aliases or if the
81 // internals of this package are merged with the catalog package.
82 var First Handle = msgFirst
83
84 // A Handler decodes and evaluates data compiled by a Message and sends the
85 // result to the Decoder. The output may depend on the value of the substitution
86 // arguments, accessible by the Decoder's Arg method. The Handler returns false
87 // if there is no translation for the given substitution arguments.
88 type Handler func(d *Decoder) bool
89
90 // Register records the existence of a message type and returns a Handle that
91 // can be used in the Encoder's EncodeMessageType method to create such
92 // messages. The prefix of the name should be the package path followed by
93 // an optional disambiguating string.
94 // Register will panic if a handle for the same name was already registered.
95 func Register(name string, handler Handler) Handle {
96         mutex.Lock()
97         defer mutex.Unlock()
98
99         if _, ok := names[name]; ok {
100                 panic(fmt.Errorf("catmsg: handler for %q already exists", name))
101         }
102         h := Handle(len(handlers))
103         names[name] = h
104         handlers = append(handlers, handler)
105         return h
106 }
107
108 // These handlers require fixed positions in the handlers slice.
109 const (
110         msgVars Handle = iota
111         msgFirst
112         msgRaw
113         msgString
114         numFixed
115 )
116
117 const prefix = "golang.org/x/text/internal/catmsg."
118
119 var (
120         mutex sync.Mutex
121         names = map[string]Handle{
122                 prefix + "Vars":   msgVars,
123                 prefix + "First":  msgFirst,
124                 prefix + "Raw":    msgRaw,
125                 prefix + "String": msgString,
126         }
127         handlers = make([]Handler, numFixed)
128 )
129
130 func init() {
131         // This handler is a message type wrapper that initializes a decoder
132         // with a variable block. This message type, if present, is always at the
133         // start of an encoded message.
134         handlers[msgVars] = func(d *Decoder) bool {
135                 blockSize := int(d.DecodeUint())
136                 d.vars = d.data[:blockSize]
137                 d.data = d.data[blockSize:]
138                 return d.executeMessage()
139         }
140
141         // First takes the first message in a sequence that results in a match for
142         // the given substitution arguments.
143         handlers[msgFirst] = func(d *Decoder) bool {
144                 for !d.Done() {
145                         if d.ExecuteMessage() {
146                                 return true
147                         }
148                 }
149                 return false
150         }
151
152         handlers[msgRaw] = func(d *Decoder) bool {
153                 d.Render(d.data)
154                 return true
155         }
156
157         // A String message alternates between a string constant and a variable
158         // substitution.
159         handlers[msgString] = func(d *Decoder) bool {
160                 for !d.Done() {
161                         if str := d.DecodeString(); str != "" {
162                                 d.Render(str)
163                         }
164                         if d.Done() {
165                                 break
166                         }
167                         d.ExecuteSubstitution()
168                 }
169                 return true
170         }
171 }
172
173 var (
174         // ErrIncomplete indicates a compiled message does not define translations
175         // for all possible argument values. If this message is returned, evaluating
176         // a message may result in the ErrNoMatch error.
177         ErrIncomplete = errors.New("catmsg: incomplete message; may not give result for all inputs")
178
179         // ErrNoMatch indicates no translation message matched the given input
180         // parameters when evaluating a message.
181         ErrNoMatch = errors.New("catmsg: no translation for inputs")
182 )
183
184 // A Message holds a collection of translations for the same phrase that may
185 // vary based on the values of substitution arguments.
186 type Message interface {
187         // Compile encodes the format string(s) of the message as a string for later
188         // evaluation.
189         //
190         // The first call Compile makes on the encoder must be EncodeMessageType.
191         // The handle passed to this call may either be a handle returned by
192         // Register to encode a single custom message, or HandleFirst followed by
193         // a sequence of calls to EncodeMessage.
194         //
195         // Compile must return ErrIncomplete if it is possible for evaluation to
196         // not match any translation for a given set of formatting parameters.
197         // For example, selecting a translation based on plural form may not yield
198         // a match if the form "Other" is not one of the selectors.
199         //
200         // Compile may return any other application-specific error. For backwards
201         // compatibility with package like fmt, which often do not do sanity
202         // checking of format strings ahead of time, Compile should still make an
203         // effort to have some sensible fallback in case of an error.
204         Compile(e *Encoder) error
205 }
206
207 // Compile converts a Message to a data string that can be stored in a Catalog.
208 // The resulting string can subsequently be decoded by passing to the Execute
209 // method of a Decoder.
210 func Compile(tag language.Tag, macros Dictionary, m Message) (data string, err error) {
211         // TODO: pass macros so they can be used for validation.
212         v := &Encoder{inBody: true} // encoder for variables
213         v.root = v
214         e := &Encoder{root: v, parent: v, tag: tag} // encoder for messages
215         err = m.Compile(e)
216         // This package serves te message package, which in turn is meant to be a
217         // drop-in replacement for fmt.  With the fmt package, format strings are
218         // evaluated lazily and errors are handled by substituting strings in the
219         // result, rather then returning an error. Dealing with multiple languages
220         // makes it more important to check errors ahead of time. We chose to be
221         // consistent and compatible and allow graceful degradation in case of
222         // errors.
223         buf := e.buf[stripPrefix(e.buf):]
224         if len(v.buf) > 0 {
225                 // Prepend variable block.
226                 b := make([]byte, 1+maxVarintBytes+len(v.buf)+len(buf))
227                 b[0] = byte(msgVars)
228                 b = b[:1+encodeUint(b[1:], uint64(len(v.buf)))]
229                 b = append(b, v.buf...)
230                 b = append(b, buf...)
231                 buf = b
232         }
233         if err == nil {
234                 err = v.err
235         }
236         return string(buf), err
237 }
238
239 // Var defines a message that can be substituted for a placeholder of the same
240 // name. If an expression does not result in a string after evaluation, Name is
241 // used as the substitution. For example:
242 //    Var{
243 //      Name:    "minutes",
244 //      Message: plural.Select(1, "one", "minute"),
245 //    }
246 // will resolve to minute for singular and minutes for plural forms.
247 type Var struct {
248         Name    string
249         Message Message
250 }
251
252 var errIsVar = errors.New("catmsg: variable used as message")
253
254 // Compile implements Message.
255 //
256 // Note that this method merely registers a variable; it does not create an
257 // encoded message.
258 func (v *Var) Compile(e *Encoder) error {
259         if err := e.addVar(v.Name, v.Message); err != nil {
260                 return err
261         }
262         // Using a Var by itself is an error. If it is in a sequence followed by
263         // other messages referring to it, this error will be ignored.
264         return errIsVar
265 }
266
267 // Raw is a message consisting of a single format string that is passed as is
268 // to the Renderer.
269 //
270 // Note that a Renderer may still do its own variable substitution.
271 type Raw string
272
273 // Compile implements Message.
274 func (r Raw) Compile(e *Encoder) (err error) {
275         e.EncodeMessageType(msgRaw)
276         // Special case: raw strings don't have a size encoding and so don't use
277         // EncodeString.
278         e.buf = append(e.buf, r...)
279         return nil
280 }
281
282 // String is a message consisting of a single format string which contains
283 // placeholders that may be substituted with variables.
284 //
285 // Variable substitutions are marked with placeholders and a variable name of
286 // the form ${name}. Any other substitutions such as Go templates or
287 // printf-style substitutions are left to be done by the Renderer.
288 //
289 // When evaluation a string interpolation, a Renderer will receive separate
290 // calls for each placeholder and interstitial string. For example, for the
291 // message: "%[1]v ${invites} %[2]v to ${their} party." The sequence of calls
292 // is:
293 //   d.Render("%[1]v ")
294 //   d.Arg(1)
295 //   d.Render(resultOfInvites)
296 //   d.Render(" %[2]v to ")
297 //   d.Arg(2)
298 //   d.Render(resultOfTheir)
299 //   d.Render(" party.")
300 // where the messages for "invites" and "their" both use a plural.Select
301 // referring to the first argument.
302 //
303 // Strings may also invoke macros. Macros are essentially variables that can be
304 // reused. Macros may, for instance, be used to make selections between
305 // different conjugations of a verb. See the catalog package description for an
306 // overview of macros.
307 type String string
308
309 // Compile implements Message. It parses the placeholder formats and returns
310 // any error.
311 func (s String) Compile(e *Encoder) (err error) {
312         msg := string(s)
313         const subStart = "${"
314         hasHeader := false
315         p := 0
316         b := []byte{}
317         for {
318                 i := strings.Index(msg[p:], subStart)
319                 if i == -1 {
320                         break
321                 }
322                 b = append(b, msg[p:p+i]...)
323                 p += i + len(subStart)
324                 if i = strings.IndexByte(msg[p:], '}'); i == -1 {
325                         b = append(b, "$!(MISSINGBRACE)"...)
326                         err = fmt.Errorf("catmsg: missing '}'")
327                         p = len(msg)
328                         break
329                 }
330                 name := strings.TrimSpace(msg[p : p+i])
331                 if q := strings.IndexByte(name, '('); q == -1 {
332                         if !hasHeader {
333                                 hasHeader = true
334                                 e.EncodeMessageType(msgString)
335                         }
336                         e.EncodeString(string(b))
337                         e.EncodeSubstitution(name)
338                         b = b[:0]
339                 } else if j := strings.IndexByte(name[q:], ')'); j == -1 {
340                         // TODO: what should the error be?
341                         b = append(b, "$!(MISSINGPAREN)"...)
342                         err = fmt.Errorf("catmsg: missing ')'")
343                 } else if x, sErr := strconv.ParseUint(strings.TrimSpace(name[q+1:q+j]), 10, 32); sErr != nil {
344                         // TODO: handle more than one argument
345                         b = append(b, "$!(BADNUM)"...)
346                         err = fmt.Errorf("catmsg: invalid number %q", strings.TrimSpace(name[q+1:q+j]))
347                 } else {
348                         if !hasHeader {
349                                 hasHeader = true
350                                 e.EncodeMessageType(msgString)
351                         }
352                         e.EncodeString(string(b))
353                         e.EncodeSubstitution(name[:q], int(x))
354                         b = b[:0]
355                 }
356                 p += i + 1
357         }
358         b = append(b, msg[p:]...)
359         if !hasHeader {
360                 // Simplify string to a raw string.
361                 Raw(string(b)).Compile(e)
362         } else if len(b) > 0 {
363                 e.EncodeString(string(b))
364         }
365         return err
366 }