123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407 |
- // 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")
- }
- }
|