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.
11 "golang.org/x/text/language"
14 // A Renderer renders a Message.
15 type Renderer interface {
16 // Render renders the given string. The given string may be interpreted as a
17 // format string, such as the one used by the fmt package or a template.
20 // Arg returns the i-th argument passed to format a message. This method
21 // should return nil if there is no such argument. Messages need access to
22 // arguments to allow selecting a message based on linguistic features of
24 Arg(i int) interface{}
27 // A Dictionary specifies a source of messages, including variables or macros.
28 type Dictionary interface {
29 // Lookup returns the message for the given key. It returns false for ok if
30 // such a message could not be found.
31 Lookup(key string) (data string, ok bool)
33 // TODO: consider returning an interface, instead of a string. This will
34 // allow implementations to do their own message type decoding.
37 // An Encoder serializes a Message to a string.
39 // The root encoder is used for storing encoded variables.
41 // The parent encoder provides the surrounding scopes for resolving variable
47 // buf holds the encoded message so far. After a message completes encoding,
48 // the contents of buf, prefixed by the encoded length, are flushed to the
52 // vars is the lookup table of variables in the current scope.
56 inBody bool // if false next call must be EncodeMessageType
64 // Language reports the language for which the encoded message will be stored
66 func (e *Encoder) Language() language.Tag { return e.tag }
68 func (e *Encoder) setError(err error) {
69 if e.root.err == nil {
74 // EncodeUint encodes x.
75 func (e *Encoder) EncodeUint(x uint64) {
77 var buf [maxVarintBytes]byte
78 n := encodeUint(buf[:], x)
79 e.buf = append(e.buf, buf[:n]...)
82 // EncodeString encodes s.
83 func (e *Encoder) EncodeString(s string) {
85 e.EncodeUint(uint64(len(s)))
86 e.buf = append(e.buf, s...)
89 // EncodeMessageType marks the current message to be of type h.
91 // It must be the first call of a Message's Compile method.
92 func (e *Encoder) EncodeMessageType(h Handle) {
94 panic("catmsg: EncodeMessageType not the first method called")
97 e.EncodeUint(uint64(h))
100 // EncodeMessage serializes the given message inline at the current position.
101 func (e *Encoder) EncodeMessage(m Message) error {
102 e = &Encoder{root: e.root, parent: e, tag: e.tag}
104 if _, ok := m.(*Var); !ok {
110 func (e *Encoder) checkInBody() {
112 panic("catmsg: expected prior call to EncodeMessageType")
116 // stripPrefix indicates the number of prefix bytes that must be stripped to
117 // turn a single-element sequence into a message that is just this single member
118 // without its size prefix. If the message can be stripped, b[1:n] contains the
120 func stripPrefix(b []byte) (n int) {
121 if len(b) > 0 && Handle(b[0]) == msgFirst {
122 x, n, _ := decodeUint(b[1:])
123 if 1+n+int(x) == len(b) {
130 func (e *Encoder) flushTo(dst *Encoder) {
132 p := stripPrefix(data)
137 dst.EncodeUint(uint64(len(data)))
139 dst.buf = append(dst.buf, data...)
142 func (e *Encoder) addVar(key string, m Message) error {
143 for _, v := range e.parent.vars {
145 err := fmt.Errorf("catmsg: duplicate variable %q", key)
151 // If a variable message is Incomplete, and does not evaluate to a message
152 // during execution, we fall back to the variable name. We encode this by
153 // appending the variable name if the message reports it's incomplete.
156 if err != ErrIncomplete {
160 case len(e.buf) == 1 && Handle(e.buf[0]) == msgFirst: // empty sequence
164 case len(e.buf) == 0:
166 if err := String(key).Compile(e); err != nil {
169 case err == ErrIncomplete:
170 if Handle(e.buf[0]) != msgFirst {
171 seq := &Encoder{root: e.root, parent: e}
172 seq.EncodeMessageType(First)
176 // e contains a sequence; append the fallback string.
177 e.EncodeMessage(String(key))
180 // Flush result to variable heap.
181 offset := len(e.root.buf)
185 // Record variable offset in current scope.
186 scope.vars = append(scope.vars, keyVal{key: key, offset: offset})
196 // EncodeSubstitution inserts a resolved reference to a variable or macro.
198 // This call must be matched with a call to ExecuteSubstitution at decoding
200 func (e *Encoder) EncodeSubstitution(name string, arguments ...int) {
201 if arity := len(arguments); arity > 0 {
202 // TODO: also resolve macros.
203 e.EncodeUint(substituteMacro)
205 for _, a := range arguments {
206 e.EncodeUint(uint64(a))
210 for scope := e; scope != nil; scope = scope.parent {
211 for _, v := range scope.vars {
215 e.EncodeUint(substituteVar) // TODO: support arity > 0
216 e.EncodeUint(uint64(v.offset))
220 // TODO: refer to dictionary-wide scoped variables.
221 e.EncodeUint(substituteError)
223 e.setError(fmt.Errorf("catmsg: unknown var %q", name))
226 // A Decoder deserializes and evaluates messages that are encoded by an encoder.
227 type Decoder struct {
236 macroArg int // TODO: allow more than one argument
239 // NewDecoder returns a new Decoder.
241 // Decoders are designed to be reused for multiple invocations of Execute.
242 // Only one goroutine may call Execute concurrently.
243 func NewDecoder(tag language.Tag, r Renderer, macros Dictionary) *Decoder {
251 func (d *Decoder) setError(err error) {
257 // Language returns the language in which the message is being rendered.
259 // The destination language may be a child language of the language used for
260 // encoding. For instance, a decoding language of "pt-PT"" is consistent with an
261 // encoding language of "pt".
262 func (d *Decoder) Language() language.Tag { return d.tag }
264 // Done reports whether there are more bytes to process in this message.
265 func (d *Decoder) Done() bool { return len(d.data) == 0 }
267 // Render implements Renderer.
268 func (d *Decoder) Render(s string) { d.dst.Render(s) }
270 // Arg implements Renderer.
272 // During evaluation of macros, the argument positions may be mapped to
273 // arguments that differ from the original call.
274 func (d *Decoder) Arg(i int) interface{} {
277 panic("catmsg: only macros with single argument supported")
284 // DecodeUint decodes a number that was encoded with EncodeUint and advances the
286 func (d *Decoder) DecodeUint() uint64 {
287 x, n, err := decodeUintString(d.data)
295 // DecodeString decodes a string that was encoded with EncodeString and advances
297 func (d *Decoder) DecodeString() string {
298 size := d.DecodeUint()
300 d.data = d.data[size:]
304 // SkipMessage skips the message at the current location and advances the
306 func (d *Decoder) SkipMessage() {
307 n := int(d.DecodeUint())
311 // Execute decodes and evaluates msg.
313 // Only one goroutine may call execute.
314 func (d *Decoder) Execute(msg string) error {
322 func (d *Decoder) execute(msg string) bool {
325 ok := d.executeMessage()
330 // executeMessageFromData is like execute, but also decodes a leading message
331 // size and clips the given string accordingly.
333 // It reports the number of bytes consumed and whether a message was selected.
334 func (d *Decoder) executeMessageFromData(s string) (n int, ok bool) {
337 size := int(d.DecodeUint())
338 n = len(s) - len(d.data)
339 // Sanitize the setting. This allows skipping a size argument for
340 // RawString and method Done.
341 d.data = d.data[:size]
342 ok = d.executeMessage()
343 n += size - len(d.data)
348 var errUnknownHandler = errors.New("catmsg: string contains unsupported handler")
350 // executeMessage reads the handle id, initializes the decoder and executes the
351 // message. It is assumed that all of d.data[d.p:] is the single message.
352 func (d *Decoder) executeMessage() bool {
354 // We interpret no data as a valid empty message.
357 handle := d.DecodeUint()
361 if int(handle) < len(handlers) {
362 fn = handlers[handle]
366 d.setError(errUnknownHandler)
367 d.execute(fmt.Sprintf("\x02$!(UNKNOWNMSGHANDLER=%#x)", handle))
373 // ExecuteMessage decodes and executes the message at the current position.
374 func (d *Decoder) ExecuteMessage() bool {
375 n, ok := d.executeMessageFromData(d.data)
380 // ExecuteSubstitution executes the message corresponding to the substitution
381 // as encoded by EncodeSubstitution.
382 func (d *Decoder) ExecuteSubstitution() {
383 switch x := d.DecodeUint(); x {
385 offset := d.DecodeUint()
386 d.executeMessageFromData(d.vars[offset:])
387 case substituteMacro:
388 name := d.DecodeString()
389 data, ok := d.macros.Lookup(name)
391 // TODO: support macros of arity other than 1.
392 d.macroArg = int(d.DecodeUint())
395 // TODO: detect this at creation time.
396 d.setError(fmt.Errorf("catmsg: undefined macro %q", name))
398 case !d.execute(data):
399 d.dst.Render(name) // fall back to macro name.
402 case substituteError:
403 d.dst.Render(d.DecodeString())
405 panic("catmsg: unreachable")