OpenCores
URL https://opencores.org/ocsvn/openrisc/openrisc/trunk

Subversion Repositories openrisc

[/] [openrisc/] [trunk/] [gnu-dev/] [or1k-gcc/] [libgo/] [go/] [old/] [template/] [parse.go] - Rev 747

Compare with Previous | Blame | View Log

// Copyright 2009 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.

// Code to parse a template.

package template

import (
        "fmt"
        "io"
        "io/ioutil"
        "reflect"
        "strconv"
        "strings"
        "unicode"
        "unicode/utf8"
)

// Errors returned during parsing and execution.  Users may extract the information and reformat
// if they desire.
type Error struct {
        Line int
        Msg  string
}

func (e *Error) Error() string { return fmt.Sprintf("line %d: %s", e.Line, e.Msg) }

// checkError is a deferred function to turn a panic with type *Error into a plain error return.
// Other panics are unexpected and so are re-enabled.
func checkError(error *error) {
        if v := recover(); v != nil {
                if e, ok := v.(*Error); ok {
                        *error = e
                } else {
                        // runtime errors should crash
                        panic(v)
                }
        }
}

// Most of the literals are aces.
var lbrace = []byte{'{'}
var rbrace = []byte{'}'}
var space = []byte{' '}
var tab = []byte{'\t'}

// The various types of "tokens", which are plain text or (usually) brace-delimited descriptors
const (
        tokAlternates = iota
        tokComment
        tokEnd
        tokLiteral
        tokOr
        tokRepeated
        tokSection
        tokText
        tokVariable
)

// FormatterMap is the type describing the mapping from formatter
// names to the functions that implement them.
type FormatterMap map[string]func(io.Writer, string, ...interface{})

// Built-in formatters.
var builtins = FormatterMap{
        "html": HTMLFormatter,
        "str":  StringFormatter,
        "":     StringFormatter,
}

// The parsed state of a template is a vector of xxxElement structs.
// Sections have line numbers so errors can be reported better during execution.

// Plain text.
type textElement struct {
        text []byte
}

// A literal such as .meta-left or .meta-right
type literalElement struct {
        text []byte
}

// A variable invocation to be evaluated
type variableElement struct {
        linenum int
        args    []interface{} // The fields and literals in the invocation.
        fmts    []string      // Names of formatters to apply. len(fmts) > 0
}

// A variableElement arg to be evaluated as a field name
type fieldName string

// A .section block, possibly with a .or
type sectionElement struct {
        linenum int    // of .section itself
        field   string // cursor field for this block
        start   int    // first element
        or      int    // first element of .or block
        end     int    // one beyond last element
}

// A .repeated block, possibly with a .or and a .alternates
type repeatedElement struct {
        sectionElement     // It has the same structure...
        altstart       int // ... except for alternates
        altend         int
}

// Template is the type that represents a template definition.
// It is unchanged after parsing.
type Template struct {
        fmap FormatterMap // formatters for variables
        // Used during parsing:
        ldelim, rdelim []byte // delimiters; default {}
        buf            []byte // input text to process
        p              int    // position in buf
        linenum        int    // position in input
        // Parsed results:
        elems []interface{}
}

// New creates a new template with the specified formatter map (which
// may be nil) to define auxiliary functions for formatting variables.
func New(fmap FormatterMap) *Template {
        t := new(Template)
        t.fmap = fmap
        t.ldelim = lbrace
        t.rdelim = rbrace
        t.elems = make([]interface{}, 0, 16)
        return t
}

// Report error and stop executing.  The line number must be provided explicitly.
func (t *Template) execError(st *state, line int, err string, args ...interface{}) {
        panic(&Error{line, fmt.Sprintf(err, args...)})
}

// Report error, panic to terminate parsing.
// The line number comes from the template state.
func (t *Template) parseError(err string, args ...interface{}) {
        panic(&Error{t.linenum, fmt.Sprintf(err, args...)})
}

// Is this an exported - upper case - name?
func isExported(name string) bool {
        r, _ := utf8.DecodeRuneInString(name)
        return unicode.IsUpper(r)
}

// -- Lexical analysis

// Is c a space character?
func isSpace(c uint8) bool { return c == ' ' || c == '\t' || c == '\r' || c == '\n' }

// Safely, does s[n:n+len(t)] == t?
func equal(s []byte, n int, t []byte) bool {
        b := s[n:]
        if len(t) > len(b) { // not enough space left for a match.
                return false
        }
        for i, c := range t {
                if c != b[i] {
                        return false
                }
        }
        return true
}

// isQuote returns true if c is a string- or character-delimiting quote character.
func isQuote(c byte) bool {
        return c == '"' || c == '`' || c == '\''
}

// endQuote returns the end quote index for the quoted string that
// starts at n, or -1 if no matching end quote is found before the end
// of the line.
func endQuote(s []byte, n int) int {
        quote := s[n]
        for n++; n < len(s); n++ {
                switch s[n] {
                case '\\':
                        if quote == '"' || quote == '\'' {
                                n++
                        }
                case '\n':
                        return -1
                case quote:
                        return n
                }
        }
        return -1
}

// nextItem returns the next item from the input buffer.  If the returned
// item is empty, we are at EOF.  The item will be either a
// delimited string or a non-empty string between delimited
// strings. Tokens stop at (but include, if plain text) a newline.
// Action tokens on a line by themselves drop any space on
// either side, up to and including the newline.
func (t *Template) nextItem() []byte {
        startOfLine := t.p == 0 || t.buf[t.p-1] == '\n'
        start := t.p
        var i int
        newline := func() {
                t.linenum++
                i++
        }
        // Leading space up to but not including newline
        for i = start; i < len(t.buf); i++ {
                if t.buf[i] == '\n' || !isSpace(t.buf[i]) {
                        break
                }
        }
        leadingSpace := i > start
        // What's left is nothing, newline, delimited string, or plain text
        switch {
        case i == len(t.buf):
                // EOF; nothing to do
        case t.buf[i] == '\n':
                newline()
        case equal(t.buf, i, t.ldelim):
                left := i         // Start of left delimiter.
                right := -1       // Will be (immediately after) right delimiter.
                haveText := false // Delimiters contain text.
                i += len(t.ldelim)
                // Find the end of the action.
                for ; i < len(t.buf); i++ {
                        if t.buf[i] == '\n' {
                                break
                        }
                        if isQuote(t.buf[i]) {
                                i = endQuote(t.buf, i)
                                if i == -1 {
                                        t.parseError("unmatched quote")
                                        return nil
                                }
                                continue
                        }
                        if equal(t.buf, i, t.rdelim) {
                                i += len(t.rdelim)
                                right = i
                                break
                        }
                        haveText = true
                }
                if right < 0 {
                        t.parseError("unmatched opening delimiter")
                        return nil
                }
                // Is this a special action (starts with '.' or '#') and the only thing on the line?
                if startOfLine && haveText {
                        firstChar := t.buf[left+len(t.ldelim)]
                        if firstChar == '.' || firstChar == '#' {
                                // It's special and the first thing on the line. Is it the last?
                                for j := right; j < len(t.buf) && isSpace(t.buf[j]); j++ {
                                        if t.buf[j] == '\n' {
                                                // Yes it is. Drop the surrounding space and return the {.foo}
                                                t.linenum++
                                                t.p = j + 1
                                                return t.buf[left:right]
                                        }
                                }
                        }
                }
                // No it's not. If there's leading space, return that.
                if leadingSpace {
                        // not trimming space: return leading space if there is some.
                        t.p = left
                        return t.buf[start:left]
                }
                // Return the word, leave the trailing space.
                start = left
                break
        default:
                for ; i < len(t.buf); i++ {
                        if t.buf[i] == '\n' {
                                newline()
                                break
                        }
                        if equal(t.buf, i, t.ldelim) {
                                break
                        }
                }
        }
        item := t.buf[start:i]
        t.p = i
        return item
}

// Turn a byte array into a space-split array of strings,
// taking into account quoted strings.
func words(buf []byte) []string {
        s := make([]string, 0, 5)
        for i := 0; i < len(buf); {
                // One word per loop
                for i < len(buf) && isSpace(buf[i]) {
                        i++
                }
                if i == len(buf) {
                        break
                }
                // Got a word
                start := i
                if isQuote(buf[i]) {
                        i = endQuote(buf, i)
                        if i < 0 {
                                i = len(buf)
                        } else {
                                i++
                        }
                }
                // Even with quotes, break on space only.  This handles input
                // such as {""|} and catches quoting mistakes.
                for i < len(buf) && !isSpace(buf[i]) {
                        i++
                }
                s = append(s, string(buf[start:i]))
        }
        return s
}

// Analyze an item and return its token type and, if it's an action item, an array of
// its constituent words.
func (t *Template) analyze(item []byte) (tok int, w []string) {
        // item is known to be non-empty
        if !equal(item, 0, t.ldelim) { // doesn't start with left delimiter
                tok = tokText
                return
        }
        if !equal(item, len(item)-len(t.rdelim), t.rdelim) { // doesn't end with right delimiter
                t.parseError("internal error: unmatched opening delimiter") // lexing should prevent this
                return
        }
        if len(item) <= len(t.ldelim)+len(t.rdelim) { // no contents
                t.parseError("empty directive")
                return
        }
        // Comment
        if item[len(t.ldelim)] == '#' {
                tok = tokComment
                return
        }
        // Split into words
        w = words(item[len(t.ldelim) : len(item)-len(t.rdelim)]) // drop final delimiter
        if len(w) == 0 {
                t.parseError("empty directive")
                return
        }
        first := w[0]
        if first[0] != '.' {
                tok = tokVariable
                return
        }
        if len(first) > 1 && first[1] >= '0' && first[1] <= '9' {
                // Must be a float.
                tok = tokVariable
                return
        }
        switch first {
        case ".meta-left", ".meta-right", ".space", ".tab":
                tok = tokLiteral
                return
        case ".or":
                tok = tokOr
                return
        case ".end":
                tok = tokEnd
                return
        case ".section":
                if len(w) != 2 {
                        t.parseError("incorrect fields for .section: %s", item)
                        return
                }
                tok = tokSection
                return
        case ".repeated":
                if len(w) != 3 || w[1] != "section" {
                        t.parseError("incorrect fields for .repeated: %s", item)
                        return
                }
                tok = tokRepeated
                return
        case ".alternates":
                if len(w) != 2 || w[1] != "with" {
                        t.parseError("incorrect fields for .alternates: %s", item)
                        return
                }
                tok = tokAlternates
                return
        }
        t.parseError("bad directive: %s", item)
        return
}

// formatter returns the Formatter with the given name in the Template, or nil if none exists.
func (t *Template) formatter(name string) func(io.Writer, string, ...interface{}) {
        if t.fmap != nil {
                if fn := t.fmap[name]; fn != nil {
                        return fn
                }
        }
        return builtins[name]
}

// -- Parsing

// newVariable allocates a new variable-evaluation element.
func (t *Template) newVariable(words []string) *variableElement {
        formatters := extractFormatters(words)
        args := make([]interface{}, len(words))

        // Build argument list, processing any literals
        for i, word := range words {
                var lerr error
                switch word[0] {
                case '"', '`', '\'':
                        v, err := strconv.Unquote(word)
                        if err == nil && word[0] == '\'' {
                                args[i], _ = utf8.DecodeRuneInString(v)
                        } else {
                                args[i], lerr = v, err
                        }

                case '.', '+', '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
                        v, err := strconv.ParseInt(word, 0, 64)
                        if err == nil {
                                args[i] = v
                        } else {
                                v, err := strconv.ParseFloat(word, 64)
                                args[i], lerr = v, err
                        }

                default:
                        args[i] = fieldName(word)
                }
                if lerr != nil {
                        t.parseError("invalid literal: %q: %s", word, lerr)
                }
        }

        // We could remember the function address here and avoid the lookup later,
        // but it's more dynamic to let the user change the map contents underfoot.
        // We do require the name to be present, though.

        // Is it in user-supplied map?
        for _, f := range formatters {
                if t.formatter(f) == nil {
                        t.parseError("unknown formatter: %q", f)
                }
        }

        return &variableElement{t.linenum, args, formatters}
}

// extractFormatters extracts a list of formatters from words.
// After the final space-separated argument in a variable, formatters may be
// specified separated by pipe symbols. For example: {a b c|d|e}
// The words parameter still has the formatters joined by '|' in the last word.
// extractFormatters splits formatters, replaces the last word with the content
// found before the first '|' within it, and returns the formatters obtained.
// If no formatters are found in words, the default formatter is returned.
func extractFormatters(words []string) (formatters []string) {
        // "" is the default formatter.
        formatters = []string{""}
        if len(words) == 0 {
                return
        }
        var bar int
        lastWord := words[len(words)-1]
        if isQuote(lastWord[0]) {
                end := endQuote([]byte(lastWord), 0)
                if end < 0 || end+1 == len(lastWord) || lastWord[end+1] != '|' {
                        return
                }
                bar = end + 1
        } else {
                bar = strings.IndexRune(lastWord, '|')
                if bar < 0 {
                        return
                }
        }
        words[len(words)-1] = lastWord[0:bar]
        formatters = strings.Split(lastWord[bar+1:], "|")
        return
}

// Grab the next item.  If it's simple, just append it to the template.
// Otherwise return its details.
func (t *Template) parseSimple(item []byte) (done bool, tok int, w []string) {
        tok, w = t.analyze(item)
        done = true // assume for simplicity
        switch tok {
        case tokComment:
                return
        case tokText:
                t.elems = append(t.elems, &textElement{item})
                return
        case tokLiteral:
                switch w[0] {
                case ".meta-left":
                        t.elems = append(t.elems, &literalElement{t.ldelim})
                case ".meta-right":
                        t.elems = append(t.elems, &literalElement{t.rdelim})
                case ".space":
                        t.elems = append(t.elems, &literalElement{space})
                case ".tab":
                        t.elems = append(t.elems, &literalElement{tab})
                default:
                        t.parseError("internal error: unknown literal: %s", w[0])
                }
                return
        case tokVariable:
                t.elems = append(t.elems, t.newVariable(w))
                return
        }
        return false, tok, w
}

// parseRepeated and parseSection are mutually recursive

func (t *Template) parseRepeated(words []string) *repeatedElement {
        r := new(repeatedElement)
        t.elems = append(t.elems, r)
        r.linenum = t.linenum
        r.field = words[2]
        // Scan section, collecting true and false (.or) blocks.
        r.start = len(t.elems)
        r.or = -1
        r.altstart = -1
        r.altend = -1
Loop:
        for {
                item := t.nextItem()
                if len(item) == 0 {
                        t.parseError("missing .end for .repeated section")
                        break
                }
                done, tok, w := t.parseSimple(item)
                if done {
                        continue
                }
                switch tok {
                case tokEnd:
                        break Loop
                case tokOr:
                        if r.or >= 0 {
                                t.parseError("extra .or in .repeated section")
                                break Loop
                        }
                        r.altend = len(t.elems)
                        r.or = len(t.elems)
                case tokSection:
                        t.parseSection(w)
                case tokRepeated:
                        t.parseRepeated(w)
                case tokAlternates:
                        if r.altstart >= 0 {
                                t.parseError("extra .alternates in .repeated section")
                                break Loop
                        }
                        if r.or >= 0 {
                                t.parseError(".alternates inside .or block in .repeated section")
                                break Loop
                        }
                        r.altstart = len(t.elems)
                default:
                        t.parseError("internal error: unknown repeated section item: %s", item)
                        break Loop
                }
        }
        if r.altend < 0 {
                r.altend = len(t.elems)
        }
        r.end = len(t.elems)
        return r
}

func (t *Template) parseSection(words []string) *sectionElement {
        s := new(sectionElement)
        t.elems = append(t.elems, s)
        s.linenum = t.linenum
        s.field = words[1]
        // Scan section, collecting true and false (.or) blocks.
        s.start = len(t.elems)
        s.or = -1
Loop:
        for {
                item := t.nextItem()
                if len(item) == 0 {
                        t.parseError("missing .end for .section")
                        break
                }
                done, tok, w := t.parseSimple(item)
                if done {
                        continue
                }
                switch tok {
                case tokEnd:
                        break Loop
                case tokOr:
                        if s.or >= 0 {
                                t.parseError("extra .or in .section")
                                break Loop
                        }
                        s.or = len(t.elems)
                case tokSection:
                        t.parseSection(w)
                case tokRepeated:
                        t.parseRepeated(w)
                case tokAlternates:
                        t.parseError(".alternates not in .repeated")
                default:
                        t.parseError("internal error: unknown section item: %s", item)
                }
        }
        s.end = len(t.elems)
        return s
}

func (t *Template) parse() {
        for {
                item := t.nextItem()
                if len(item) == 0 {
                        break
                }
                done, tok, w := t.parseSimple(item)
                if done {
                        continue
                }
                switch tok {
                case tokOr, tokEnd, tokAlternates:
                        t.parseError("unexpected %s", w[0])
                case tokSection:
                        t.parseSection(w)
                case tokRepeated:
                        t.parseRepeated(w)
                default:
                        t.parseError("internal error: bad directive in parse: %s", item)
                }
        }
}

// -- Execution

// -- Public interface

// Parse initializes a Template by parsing its definition.  The string
// s contains the template text.  If any errors occur, Parse returns
// the error.
func (t *Template) Parse(s string) (err error) {
        if t.elems == nil {
                return &Error{1, "template not allocated with New"}
        }
        if !validDelim(t.ldelim) || !validDelim(t.rdelim) {
                return &Error{1, fmt.Sprintf("bad delimiter strings %q %q", t.ldelim, t.rdelim)}
        }
        defer checkError(&err)
        t.buf = []byte(s)
        t.p = 0
        t.linenum = 1
        t.parse()
        return nil
}

// ParseFile is like Parse but reads the template definition from the
// named file.
func (t *Template) ParseFile(filename string) (err error) {
        b, err := ioutil.ReadFile(filename)
        if err != nil {
                return err
        }
        return t.Parse(string(b))
}

// Execute applies a parsed template to the specified data object,
// generating output to wr.
func (t *Template) Execute(wr io.Writer, data interface{}) (err error) {
        // Extract the driver data.
        val := reflect.ValueOf(data)
        defer checkError(&err)
        t.p = 0
        t.execute(0, len(t.elems), &state{parent: nil, data: val, wr: wr})
        return nil
}

// SetDelims sets the left and right delimiters for operations in the
// template.  They are validated during parsing.  They could be
// validated here but it's better to keep the routine simple.  The
// delimiters are very rarely invalid and Parse has the necessary
// error-handling interface already.
func (t *Template) SetDelims(left, right string) {
        t.ldelim = []byte(left)
        t.rdelim = []byte(right)
}

// Parse creates a Template with default parameters (such as {} for
// metacharacters).  The string s contains the template text while
// the formatter map fmap, which may be nil, defines auxiliary functions
// for formatting variables.  The template is returned. If any errors
// occur, err will be non-nil.
func Parse(s string, fmap FormatterMap) (t *Template, err error) {
        t = New(fmap)
        err = t.Parse(s)
        if err != nil {
                t = nil
        }
        return
}

// ParseFile is a wrapper function that creates a Template with default
// parameters (such as {} for metacharacters).  The filename identifies
// a file containing the template text, while the formatter map fmap, which
// may be nil, defines auxiliary functions for formatting variables.
// The template is returned. If any errors occur, err will be non-nil.
func ParseFile(filename string, fmap FormatterMap) (t *Template, err error) {
        b, err := ioutil.ReadFile(filename)
        if err != nil {
                return nil, err
        }
        return Parse(string(b), fmap)
}

// MustParse is like Parse but panics if the template cannot be parsed.
func MustParse(s string, fmap FormatterMap) *Template {
        t, err := Parse(s, fmap)
        if err != nil {
                panic("template.MustParse error: " + err.Error())
        }
        return t
}

// MustParseFile is like ParseFile but panics if the file cannot be read
// or the template cannot be parsed.
func MustParseFile(filename string, fmap FormatterMap) *Template {
        b, err := ioutil.ReadFile(filename)
        if err != nil {
                panic("template.MustParseFile error: " + err.Error())
        }
        return MustParse(string(b), fmap)
}

Compare with Previous | Blame | View Log

powered by: WebSVN 2.1.0

© copyright 1999-2024 OpenCores.org, equivalent to Oliscience, all rights reserved. OpenCores®, registered trademark.