// 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 catmsg import ( "errors" "fmt" "golang.org/x/text/language" ) // A Renderer renders a Message. type Renderer interface { // Render renders the given string. The given string may be interpreted as a // format string, such as the one used by the fmt package or a template. Render(s string) // Arg returns the i-th argument passed to format a message. This method // should return nil if there is no such argument. Messages need access to // arguments to allow selecting a message based on linguistic features of // those arguments. Arg(i int) interface{} } // A Dictionary specifies a source of messages, including variables or macros. type Dictionary interface { // Lookup returns the message for the given key. It returns false for ok if // such a message could not be found. Lookup(key string) (data string, ok bool) // TODO: consider returning an interface, instead of a string. This will // allow implementations to do their own message type decoding. } // An Encoder serializes a Message to a string. type Encoder struct { // The root encoder is used for storing encoded variables. root *Encoder // The parent encoder provides the surrounding scopes for resolving variable // names. parent *Encoder tag language.Tag // buf holds the encoded message so far. After a message completes encoding, // the contents of buf, prefixed by the encoded length, are flushed to the // parent buffer. buf []byte // vars is the lookup table of variables in the current scope. vars []keyVal err error inBody bool // if false next call must be EncodeMessageType } type keyVal struct { key string offset int } // Language reports the language for which the encoded message will be stored // in the Catalog. func (e *Encoder) Language() language.Tag { return e.tag } func (e *Encoder) setError(err error) { if e.root.err == nil { e.root.err = err } } // EncodeUint encodes x. func (e *Encoder) EncodeUint(x uint64) { e.checkInBody() var buf [maxVarintBytes]byte n := encodeUint(buf[:], x) e.buf = append(e.buf, buf[:n]...) } // EncodeString encodes s. func (e *Encoder) EncodeString(s string) { e.checkInBody() e.EncodeUint(uint64(len(s))) e.buf = append(e.buf, s...) } // EncodeMessageType marks the current message to be of type h. // // It must be the first call of a Message's Compile method. func (e *Encoder) EncodeMessageType(h Handle) { if e.inBody { panic("catmsg: EncodeMessageType not the first method called") } e.inBody = true e.EncodeUint(uint64(h)) } // EncodeMessage serializes the given message inline at the current position. func (e *Encoder) EncodeMessage(m Message) error { e = &Encoder{root: e.root, parent: e, tag: e.tag} err := m.Compile(e) if _, ok := m.(*Var); !ok { e.flushTo(e.parent) } return err } func (e *Encoder) checkInBody() { if !e.inBody { panic("catmsg: expected prior call to EncodeMessageType") } } // stripPrefix indicates the number of prefix bytes that must be stripped to // turn a single-element sequence into a message that is just this single member // without its size prefix. If the message can be stripped, b[1:n] contains the // size prefix. func stripPrefix(b []byte) (n int) { if len(b) > 0 && Handle(b[0]) == msgFirst { x, n, _ := decodeUint(b[1:]) if 1+n+int(x) == len(b) { return 1 + n } } return 0 } func (e *Encoder) flushTo(dst *Encoder) { data := e.buf p := stripPrefix(data) if p > 0 { data = data[1:] } else { // Prefix the size. dst.EncodeUint(uint64(len(data))) } dst.buf = append(dst.buf, data...) } func (e *Encoder) addVar(key string, m Message) error { for _, v := range e.parent.vars { if v.key == key { err := fmt.Errorf("catmsg: duplicate variable %q", key) e.setError(err) return err } } scope := e.parent // If a variable message is Incomplete, and does not evaluate to a message // during execution, we fall back to the variable name. We encode this by // appending the variable name if the message reports it's incomplete. err := m.Compile(e) if err != ErrIncomplete { e.setError(err) } switch { case len(e.buf) == 1 && Handle(e.buf[0]) == msgFirst: // empty sequence e.buf = e.buf[:0] e.inBody = false fallthrough case len(e.buf) == 0: // Empty message. if err := String(key).Compile(e); err != nil { e.setError(err) } case err == ErrIncomplete: if Handle(e.buf[0]) != msgFirst { seq := &Encoder{root: e.root, parent: e} seq.EncodeMessageType(msgFirst) e.flushTo(seq) e = seq } // e contains a sequence; append the fallback string. e.EncodeMessage(String(key)) } // Flush result to variable heap. offset := len(e.root.buf) e.flushTo(e.root) e.buf = e.buf[:0] // Record variable offset in current scope. scope.vars = append(scope.vars, keyVal{key: key, offset: offset}) return err } const ( substituteVar = iota substituteMacro substituteError ) // EncodeSubstitution inserts a resolved reference to a variable or macro. // // This call must be matched with a call to ExecuteSubstitution at decoding // time. func (e *Encoder) EncodeSubstitution(name string, arguments ...int) { if arity := len(arguments); arity > 0 { // TODO: also resolve macros. e.EncodeUint(substituteMacro) e.EncodeString(name) for _, a := range arguments { e.EncodeUint(uint64(a)) } return } for scope := e; scope != nil; scope = scope.parent { for _, v := range scope.vars { if v.key != name { continue } e.EncodeUint(substituteVar) // TODO: support arity > 0 e.EncodeUint(uint64(v.offset)) return } } // TODO: refer to dictionary-wide scoped variables. e.EncodeUint(substituteError) e.EncodeString(name) e.setError(fmt.Errorf("catmsg: unknown var %q", name)) } // A Decoder deserializes and evaluates messages that are encoded by an encoder. type Decoder struct { tag language.Tag dst Renderer macros Dictionary err error vars string data string macroArg int // TODO: allow more than one argument } // NewDecoder returns a new Decoder. // // Decoders are designed to be reused for multiple invocations of Execute. // Only one goroutine may call Execute concurrently. func NewDecoder(tag language.Tag, r Renderer, macros Dictionary) *Decoder { return &Decoder{ tag: tag, dst: r, macros: macros, } } func (d *Decoder) setError(err error) { if d.err == nil { d.err = err } } // Language returns the language in which the message is being rendered. // // The destination language may be a child language of the language used for // encoding. For instance, a decoding language of "pt-PT"" is consistent with an // encoding language of "pt". func (d *Decoder) Language() language.Tag { return d.tag } // Done reports whether there are more bytes to process in this message. func (d *Decoder) Done() bool { return len(d.data) == 0 } // Render implements Renderer. func (d *Decoder) Render(s string) { d.dst.Render(s) } // Arg implements Renderer. // // During evaluation of macros, the argument positions may be mapped to // arguments that differ from the original call. func (d *Decoder) Arg(i int) interface{} { if d.macroArg != 0 { if i != 1 { panic("catmsg: only macros with single argument supported") } i = d.macroArg } return d.dst.Arg(i) } // DecodeUint decodes a number that was encoded with EncodeUint and advances the // position. func (d *Decoder) DecodeUint() uint64 { x, n, err := decodeUintString(d.data) d.data = d.data[n:] if err != nil { d.setError(err) } return x } // DecodeString decodes a string that was encoded with EncodeString and advances // the position. func (d *Decoder) DecodeString() string { size := d.DecodeUint() s := d.data[:size] d.data = d.data[size:] return s } // SkipMessage skips the message at the current location and advances the // position. func (d *Decoder) SkipMessage() { n := int(d.DecodeUint()) d.data = d.data[n:] } // Execute decodes and evaluates msg. // // Only one goroutine may call execute. func (d *Decoder) Execute(msg string) error { d.err = nil if !d.execute(msg) { return ErrNoMatch } return d.err } func (d *Decoder) execute(msg string) bool { saved := d.data d.data = msg ok := d.executeMessage() d.data = saved return ok } // executeMessageFromData is like execute, but also decodes a leading message // size and clips the given string accordingly. // // It reports the number of bytes consumed and whether a message was selected. func (d *Decoder) executeMessageFromData(s string) (n int, ok bool) { saved := d.data d.data = s size := int(d.DecodeUint()) n = len(s) - len(d.data) // Sanitize the setting. This allows skipping a size argument for // RawString and method Done. d.data = d.data[:size] ok = d.executeMessage() n += size - len(d.data) d.data = saved return n, ok } var errUnknownHandler = errors.New("catmsg: string contains unsupported handler") // executeMessage reads the handle id, initializes the decoder and executes the // message. It is assumed that all of d.data[d.p:] is the single message. func (d *Decoder) executeMessage() bool { if d.Done() { // We interpret no data as a valid empty message. return true } handle := d.DecodeUint() var fn Handler mutex.Lock() if int(handle) < len(handlers) { fn = handlers[handle] } mutex.Unlock() if fn == nil { d.setError(errUnknownHandler) d.execute(fmt.Sprintf("\x02$!(UNKNOWNMSGHANDLER=%#x)", handle)) return true } return fn(d) } // ExecuteMessage decodes and executes the message at the current position. func (d *Decoder) ExecuteMessage() bool { n, ok := d.executeMessageFromData(d.data) d.data = d.data[n:] return ok } // ExecuteSubstitution executes the message corresponding to the substitution // as encoded by EncodeSubstitution. func (d *Decoder) ExecuteSubstitution() { switch x := d.DecodeUint(); x { case substituteVar: offset := d.DecodeUint() d.executeMessageFromData(d.vars[offset:]) case substituteMacro: name := d.DecodeString() data, ok := d.macros.Lookup(name) old := d.macroArg // TODO: support macros of arity other than 1. d.macroArg = int(d.DecodeUint()) switch { case !ok: // TODO: detect this at creation time. d.setError(fmt.Errorf("catmsg: undefined macro %q", name)) fallthrough case !d.execute(data): d.dst.Render(name) // fall back to macro name. } d.macroArg = old case substituteError: d.dst.Render(d.DecodeString()) default: panic("catmsg: unreachable") } }