| // 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. |
| |
| package datafmt |
| |
| import ( |
| "container/vector" |
| "go/scanner" |
| "go/token" |
| "os" |
| "strconv" |
| "strings" |
| ) |
| |
| // ---------------------------------------------------------------------------- |
| // Parsing |
| |
| type parser struct { |
| scanner.ErrorVector |
| scanner scanner.Scanner |
| file *token.File |
| pos token.Pos // token position |
| tok token.Token // one token look-ahead |
| lit string // token literal |
| |
| packs map[string]string // PackageName -> ImportPath |
| rules map[string]expr // RuleName -> Expression |
| } |
| |
| func (p *parser) next() { |
| p.pos, p.tok, p.lit = p.scanner.Scan() |
| switch p.tok { |
| case token.CHAN, token.FUNC, token.INTERFACE, token.MAP, token.STRUCT: |
| // Go keywords for composite types are type names |
| // returned by reflect. Accept them as identifiers. |
| p.tok = token.IDENT // p.lit is already set correctly |
| } |
| } |
| |
| func (p *parser) init(fset *token.FileSet, filename string, src []byte) { |
| p.ErrorVector.Reset() |
| p.file = fset.AddFile(filename, fset.Base(), len(src)) |
| p.scanner.Init(p.file, src, p, scanner.AllowIllegalChars) // return '@' as token.ILLEGAL w/o error message |
| p.next() // initializes pos, tok, lit |
| p.packs = make(map[string]string) |
| p.rules = make(map[string]expr) |
| } |
| |
| func (p *parser) error(pos token.Pos, msg string) { |
| p.Error(p.file.Position(pos), msg) |
| } |
| |
| func (p *parser) errorExpected(pos token.Pos, msg string) { |
| msg = "expected " + msg |
| if pos == p.pos { |
| // the error happened at the current position; |
| // make the error message more specific |
| msg += ", found '" + p.tok.String() + "'" |
| if p.tok.IsLiteral() { |
| msg += " " + p.lit |
| } |
| } |
| p.error(pos, msg) |
| } |
| |
| func (p *parser) expect(tok token.Token) token.Pos { |
| pos := p.pos |
| if p.tok != tok { |
| p.errorExpected(pos, "'"+tok.String()+"'") |
| } |
| p.next() // make progress in any case |
| return pos |
| } |
| |
| func (p *parser) parseIdentifier() string { |
| name := p.lit |
| p.expect(token.IDENT) |
| return name |
| } |
| |
| func (p *parser) parseTypeName() (string, bool) { |
| pos := p.pos |
| name, isIdent := p.parseIdentifier(), true |
| if p.tok == token.PERIOD { |
| // got a package name, lookup package |
| if importPath, found := p.packs[name]; found { |
| name = importPath |
| } else { |
| p.error(pos, "package not declared: "+name) |
| } |
| p.next() |
| name, isIdent = name+"."+p.parseIdentifier(), false |
| } |
| return name, isIdent |
| } |
| |
| // Parses a rule name and returns it. If the rule name is |
| // a package-qualified type name, the package name is resolved. |
| // The 2nd result value is true iff the rule name consists of a |
| // single identifier only (and thus could be a package name). |
| // |
| func (p *parser) parseRuleName() (string, bool) { |
| name, isIdent := "", false |
| switch p.tok { |
| case token.IDENT: |
| name, isIdent = p.parseTypeName() |
| case token.DEFAULT: |
| name = "default" |
| p.next() |
| case token.QUO: |
| name = "/" |
| p.next() |
| default: |
| p.errorExpected(p.pos, "rule name") |
| p.next() // make progress in any case |
| } |
| return name, isIdent |
| } |
| |
| func (p *parser) parseString() string { |
| s := "" |
| if p.tok == token.STRING { |
| s, _ = strconv.Unquote(p.lit) |
| // Unquote may fail with an error, but only if the scanner found |
| // an illegal string in the first place. In this case the error |
| // has already been reported. |
| p.next() |
| return s |
| } else { |
| p.expect(token.STRING) |
| } |
| return s |
| } |
| |
| func (p *parser) parseLiteral() literal { |
| s := []byte(p.parseString()) |
| |
| // A string literal may contain %-format specifiers. To simplify |
| // and speed up printing of the literal, split it into segments |
| // that start with "%" possibly followed by a last segment that |
| // starts with some other character. |
| var list vector.Vector |
| i0 := 0 |
| for i := 0; i < len(s); i++ { |
| if s[i] == '%' && i+1 < len(s) { |
| // the next segment starts with a % format |
| if i0 < i { |
| // the current segment is not empty, split it off |
| list.Push(s[i0:i]) |
| i0 = i |
| } |
| i++ // skip %; let loop skip over char after % |
| } |
| } |
| // the final segment may start with any character |
| // (it is empty iff the string is empty) |
| list.Push(s[i0:]) |
| |
| // convert list into a literal |
| lit := make(literal, list.Len()) |
| for i := 0; i < list.Len(); i++ { |
| lit[i] = list.At(i).([]byte) |
| } |
| |
| return lit |
| } |
| |
| func (p *parser) parseField() expr { |
| var fname string |
| switch p.tok { |
| case token.ILLEGAL: |
| if p.lit != "@" { |
| return nil |
| } |
| fname = "@" |
| p.next() |
| case token.MUL: |
| fname = "*" |
| p.next() |
| case token.IDENT: |
| fname = p.parseIdentifier() |
| default: |
| return nil |
| } |
| |
| var ruleName string |
| if p.tok == token.COLON { |
| p.next() |
| ruleName, _ = p.parseRuleName() |
| } |
| |
| return &field{fname, ruleName} |
| } |
| |
| func (p *parser) parseOperand() (x expr) { |
| switch p.tok { |
| case token.STRING: |
| x = p.parseLiteral() |
| |
| case token.LPAREN: |
| p.next() |
| x = p.parseExpression() |
| if p.tok == token.SHR { |
| p.next() |
| x = &group{x, p.parseExpression()} |
| } |
| p.expect(token.RPAREN) |
| |
| case token.LBRACK: |
| p.next() |
| x = &option{p.parseExpression()} |
| p.expect(token.RBRACK) |
| |
| case token.LBRACE: |
| p.next() |
| x = p.parseExpression() |
| var div expr |
| if p.tok == token.QUO { |
| p.next() |
| div = p.parseExpression() |
| } |
| x = &repetition{x, div} |
| p.expect(token.RBRACE) |
| |
| default: |
| x = p.parseField() // may be nil |
| } |
| |
| return x |
| } |
| |
| func (p *parser) parseSequence() expr { |
| var list vector.Vector |
| |
| for x := p.parseOperand(); x != nil; x = p.parseOperand() { |
| list.Push(x) |
| } |
| |
| // no need for a sequence if list.Len() < 2 |
| switch list.Len() { |
| case 0: |
| return nil |
| case 1: |
| return list.At(0).(expr) |
| } |
| |
| // convert list into a sequence |
| seq := make(sequence, list.Len()) |
| for i := 0; i < list.Len(); i++ { |
| seq[i] = list.At(i).(expr) |
| } |
| return seq |
| } |
| |
| func (p *parser) parseExpression() expr { |
| var list vector.Vector |
| |
| for { |
| x := p.parseSequence() |
| if x != nil { |
| list.Push(x) |
| } |
| if p.tok != token.OR { |
| break |
| } |
| p.next() |
| } |
| |
| // no need for an alternatives if list.Len() < 2 |
| switch list.Len() { |
| case 0: |
| return nil |
| case 1: |
| return list.At(0).(expr) |
| } |
| |
| // convert list into a alternatives |
| alt := make(alternatives, list.Len()) |
| for i := 0; i < list.Len(); i++ { |
| alt[i] = list.At(i).(expr) |
| } |
| return alt |
| } |
| |
| func (p *parser) parseFormat() { |
| for p.tok != token.EOF { |
| pos := p.pos |
| |
| name, isIdent := p.parseRuleName() |
| switch p.tok { |
| case token.STRING: |
| // package declaration |
| importPath := p.parseString() |
| |
| // add package declaration |
| if !isIdent { |
| p.error(pos, "illegal package name: "+name) |
| } else if _, found := p.packs[name]; !found { |
| p.packs[name] = importPath |
| } else { |
| p.error(pos, "package already declared: "+name) |
| } |
| |
| case token.ASSIGN: |
| // format rule |
| p.next() |
| x := p.parseExpression() |
| |
| // add rule |
| if _, found := p.rules[name]; !found { |
| p.rules[name] = x |
| } else { |
| p.error(pos, "format rule already declared: "+name) |
| } |
| |
| default: |
| p.errorExpected(p.pos, "package declaration or format rule") |
| p.next() // make progress in any case |
| } |
| |
| if p.tok == token.SEMICOLON { |
| p.next() |
| } else { |
| break |
| } |
| } |
| p.expect(token.EOF) |
| } |
| |
| func remap(p *parser, name string) string { |
| i := strings.Index(name, ".") |
| if i >= 0 { |
| packageName, suffix := name[0:i], name[i:] |
| // lookup package |
| if importPath, found := p.packs[packageName]; found { |
| name = importPath + suffix |
| } else { |
| var invalidPos token.Position |
| p.Error(invalidPos, "package not declared: "+packageName) |
| } |
| } |
| return name |
| } |
| |
| // Parse parses a set of format productions from source src. Custom |
| // formatters may be provided via a map of formatter functions. If |
| // there are no errors, the result is a Format and the error is nil. |
| // Otherwise the format is nil and a non-empty ErrorList is returned. |
| // |
| func Parse(fset *token.FileSet, filename string, src []byte, fmap FormatterMap) (Format, os.Error) { |
| // parse source |
| var p parser |
| p.init(fset, filename, src) |
| p.parseFormat() |
| |
| // add custom formatters, if any |
| for name, form := range fmap { |
| name = remap(&p, name) |
| if _, found := p.rules[name]; !found { |
| p.rules[name] = &custom{name, form} |
| } else { |
| var invalidPos token.Position |
| p.Error(invalidPos, "formatter already declared: "+name) |
| } |
| } |
| |
| return p.rules, p.GetError(scanner.NoMultiples) |
| } |