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 templateimport ("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 intMsg 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 crashpanic(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 descriptorsconst (tokAlternates = iotatokCommenttokEndtokLiteraltokOrtokRepeatedtokSectiontokTexttokVariable)// 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-righttype literalElement struct {text []byte}// A variable invocation to be evaluatedtype variableElement struct {linenum intargs []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 nametype fieldName string// A .section block, possibly with a .ortype sectionElement struct {linenum int // of .section itselffield string // cursor field for this blockstart int // first elementor int // first element of .or blockend int // one beyond last element}// A .repeated block, possibly with a .or and a .alternatestype repeatedElement struct {sectionElement // It has the same structure...altstart int // ... except for alternatesaltend 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 processp int // position in buflinenum 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 = fmapt.ldelim = lbracet.rdelim = rbracet.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 -1case 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.pvar i intnewline := func() {t.linenum++i++}// Leading space up to but not including newlinefor 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 textswitch {case i == len(t.buf):// EOF; nothing to docase 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 = ibreak}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 + 1return 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 = leftreturn t.buf[start:left]}// Return the word, leave the trailing space.start = leftbreakdefault: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 = ireturn 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 loopfor i < len(buf) && isSpace(buf[i]) {i++}if i == len(buf) {break}// Got a wordstart := iif 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-emptyif !equal(item, 0, t.ldelim) { // doesn't start with left delimitertok = tokTextreturn}if !equal(item, len(item)-len(t.rdelim), t.rdelim) { // doesn't end with right delimitert.parseError("internal error: unmatched opening delimiter") // lexing should prevent thisreturn}if len(item) <= len(t.ldelim)+len(t.rdelim) { // no contentst.parseError("empty directive")return}// Commentif item[len(t.ldelim)] == '#' {tok = tokCommentreturn}// Split into wordsw = words(item[len(t.ldelim) : len(item)-len(t.rdelim)]) // drop final delimiterif len(w) == 0 {t.parseError("empty directive")return}first := w[0]if first[0] != '.' {tok = tokVariablereturn}if len(first) > 1 && first[1] >= '0' && first[1] <= '9' {// Must be a float.tok = tokVariablereturn}switch first {case ".meta-left", ".meta-right", ".space", ".tab":tok = tokLiteralreturncase ".or":tok = tokOrreturncase ".end":tok = tokEndreturncase ".section":if len(w) != 2 {t.parseError("incorrect fields for .section: %s", item)return}tok = tokSectionreturncase ".repeated":if len(w) != 3 || w[1] != "section" {t.parseError("incorrect fields for .repeated: %s", item)return}tok = tokRepeatedreturncase ".alternates":if len(w) != 2 || w[1] != "with" {t.parseError("incorrect fields for .alternates: %s", item)return}tok = tokAlternatesreturn}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 literalsfor i, word := range words {var lerr errorswitch 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 intlastWord := 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 simplicityswitch tok {case tokComment:returncase tokText:t.elems = append(t.elems, &textElement{item})returncase 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])}returncase tokVariable:t.elems = append(t.elems, t.newVariable(w))return}return false, tok, w}// parseRepeated and parseSection are mutually recursivefunc (t *Template) parseRepeated(words []string) *repeatedElement {r := new(repeatedElement)t.elems = append(t.elems, r)r.linenum = t.linenumr.field = words[2]// Scan section, collecting true and false (.or) blocks.r.start = len(t.elems)r.or = -1r.altstart = -1r.altend = -1Loop: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 Loopcase 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.linenums.field = words[1]// Scan section, collecting true and false (.or) blocks.s.start = len(t.elems)s.or = -1Loop: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 Loopcase 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 = 0t.linenum = 1t.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 = 0t.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)}
