// 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 catmsgimport ()// A Renderer renders a Message.typeRendererinterface {// 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.typeDictionaryinterface {// 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.typeEncoderstruct {// 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 ( *Encoder) () language.Tag { return .tag }func ( *Encoder) ( error) {if .root.err == nil { .root.err = }}// EncodeUint encodes x.func ( *Encoder) ( uint64) { .checkInBody()var [maxVarintBytes]byte := encodeUint([:], ) .buf = append(.buf, [:]...)}// EncodeString encodes s.func ( *Encoder) ( string) { .checkInBody() .EncodeUint(uint64(len())) .buf = append(.buf, ...)}// EncodeMessageType marks the current message to be of type h.//// It must be the first call of a Message's Compile method.func ( *Encoder) ( Handle) {if .inBody {panic("catmsg: EncodeMessageType not the first method called") } .inBody = true .EncodeUint(uint64())}// EncodeMessage serializes the given message inline at the current position.func ( *Encoder) ( Message) error { = &Encoder{root: .root, parent: , tag: .tag} := .Compile()if , := .(*Var); ! { .flushTo(.parent) }return}func ( *Encoder) () {if !.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( []byte) ( int) {iflen() > 0 && Handle([0]) == msgFirst { , , := decodeUint([1:])if1++int() == len() {return1 + } }return0}func ( *Encoder) ( *Encoder) { := .buf := stripPrefix()if > 0 { = [1:] } else {// Prefix the size. .EncodeUint(uint64(len())) } .buf = append(.buf, ...)}func ( *Encoder) ( string, Message) error {for , := range .parent.vars {if .key == { := fmt.Errorf("catmsg: duplicate variable %q", ) .setError()return } } := .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. := .Compile()if != ErrIncomplete { .setError() }switch {caselen(.buf) == 1 && Handle(.buf[0]) == msgFirst: // empty sequence .buf = .buf[:0] .inBody = falsefallthroughcaselen(.buf) == 0:// Empty message.if := String().Compile(); != nil { .setError() }case == ErrIncomplete:ifHandle(.buf[0]) != msgFirst { := &Encoder{root: .root, parent: } .EncodeMessageType(msgFirst) .flushTo() = }// e contains a sequence; append the fallback string. .EncodeMessage(String()) }// Flush result to variable heap. := len(.root.buf) .flushTo(.root) .buf = .buf[:0]// Record variable offset in current scope. .vars = append(.vars, keyVal{key: , offset: })return}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 ( *Encoder) ( string, ...int) {if := len(); > 0 {// TODO: also resolve macros. .EncodeUint(substituteMacro) .EncodeString()for , := range { .EncodeUint(uint64()) }return }for := ; != nil; = .parent {for , := range .vars {if .key != {continue } .EncodeUint(substituteVar) // TODO: support arity > 0 .EncodeUint(uint64(.offset))return } }// TODO: refer to dictionary-wide scoped variables. .EncodeUint(substituteError) .EncodeString() .setError(fmt.Errorf("catmsg: unknown var %q", ))}// A Decoder deserializes and evaluates messages that are encoded by an encoder.typeDecoderstruct { 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 ( language.Tag, Renderer, Dictionary) *Decoder {return &Decoder{tag: ,dst: ,macros: , }}func ( *Decoder) ( error) {if .err == nil { .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 ( *Decoder) () language.Tag { return .tag }// Done reports whether there are more bytes to process in this message.func ( *Decoder) () bool { returnlen(.data) == 0 }// Render implements Renderer.func ( *Decoder) ( string) { .dst.Render() }// Arg implements Renderer.//// During evaluation of macros, the argument positions may be mapped to// arguments that differ from the original call.func ( *Decoder) ( int) interface{} {if .macroArg != 0 {if != 1 {panic("catmsg: only macros with single argument supported") } = .macroArg }return .dst.Arg()}// DecodeUint decodes a number that was encoded with EncodeUint and advances the// position.func ( *Decoder) () uint64 { , , := decodeUintString(.data) .data = .data[:]if != nil { .setError() }return}// DecodeString decodes a string that was encoded with EncodeString and advances// the position.func ( *Decoder) () string { := .DecodeUint() := .data[:] .data = .data[:]return}// SkipMessage skips the message at the current location and advances the// position.func ( *Decoder) () { := int(.DecodeUint()) .data = .data[:]}// Execute decodes and evaluates msg.//// Only one goroutine may call execute.func ( *Decoder) ( string) error { .err = nilif !.execute() {returnErrNoMatch }return .err}func ( *Decoder) ( string) bool { := .data .data = := .executeMessage() .data = return}// 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 ( *Decoder) ( string) ( int, bool) { := .data .data = := int(.DecodeUint()) = len() - len(.data)// Sanitize the setting. This allows skipping a size argument for // RawString and method Done. .data = .data[:] = .executeMessage() += - len(.data) .data = return , }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 ( *Decoder) () bool {if .Done() {// We interpret no data as a valid empty message.returntrue } := .DecodeUint()varHandlermutex.Lock()ifint() < len(handlers) { = handlers[] }mutex.Unlock()if == nil { .setError(errUnknownHandler) .execute(fmt.Sprintf("\x02$!(UNKNOWNMSGHANDLER=%#x)", ))returntrue }return ()}// ExecuteMessage decodes and executes the message at the current position.func ( *Decoder) () bool { , := .executeMessageFromData(.data) .data = .data[:]return}// ExecuteSubstitution executes the message corresponding to the substitution// as encoded by EncodeSubstitution.func ( *Decoder) () {switch := .DecodeUint(); {casesubstituteVar: := .DecodeUint() .executeMessageFromData(.vars[:])casesubstituteMacro: := .DecodeString() , := .macros.Lookup() := .macroArg// TODO: support macros of arity other than 1. .macroArg = int(.DecodeUint())switch {case !:// TODO: detect this at creation time. .setError(fmt.Errorf("catmsg: undefined macro %q", ))fallthroughcase !.execute(): .dst.Render() // fall back to macro name. } .macroArg = casesubstituteError: .dst.Render(.DecodeString())default:panic("catmsg: unreachable") }}
The pages are generated with Goldsv0.8.2. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds.