2476f81d84
Example: foo { // string property name: "hello", // map property translations: { ge: "guten tag", fr: "bonjour", }, } Change-Id: Ib9e6570cb6f355677b4ba0235e8fb4186e40f5f8
381 lines
7 KiB
Go
381 lines
7 KiB
Go
package parser
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"strconv"
|
|
"strings"
|
|
"text/scanner"
|
|
)
|
|
|
|
var errTooManyErrors = errors.New("too many errors")
|
|
|
|
const maxErrors = 1
|
|
|
|
type ParseError struct {
|
|
Err error
|
|
Pos scanner.Position
|
|
}
|
|
|
|
func (e *ParseError) Error() string {
|
|
return fmt.Sprintf("%s: %s", e.Pos, e.Err)
|
|
}
|
|
|
|
func Parse(filename string, r io.Reader) (defs []Definition, errs []error) {
|
|
p := newParser(r)
|
|
p.scanner.Filename = filename
|
|
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
if r == errTooManyErrors {
|
|
defs = nil
|
|
errs = p.errors
|
|
return
|
|
}
|
|
panic(r)
|
|
}
|
|
}()
|
|
|
|
defs = p.parseDefinitions()
|
|
p.accept(scanner.EOF)
|
|
errs = p.errors
|
|
|
|
return
|
|
}
|
|
|
|
type parser struct {
|
|
scanner scanner.Scanner
|
|
tok rune
|
|
errors []error
|
|
}
|
|
|
|
func newParser(r io.Reader) *parser {
|
|
p := &parser{}
|
|
p.scanner.Init(r)
|
|
p.scanner.Error = func(sc *scanner.Scanner, msg string) {
|
|
p.errorf(msg)
|
|
}
|
|
p.scanner.Mode = scanner.ScanIdents | scanner.ScanStrings |
|
|
scanner.ScanRawStrings | scanner.ScanComments | scanner.SkipComments
|
|
p.next()
|
|
return p
|
|
}
|
|
|
|
func (p *parser) errorf(format string, args ...interface{}) {
|
|
pos := p.scanner.Position
|
|
if !pos.IsValid() {
|
|
pos = p.scanner.Pos()
|
|
}
|
|
err := &ParseError{
|
|
Err: fmt.Errorf(format, args...),
|
|
Pos: pos,
|
|
}
|
|
p.errors = append(p.errors, err)
|
|
if len(p.errors) >= maxErrors {
|
|
panic(errTooManyErrors)
|
|
}
|
|
}
|
|
|
|
func (p *parser) accept(toks ...rune) bool {
|
|
for _, tok := range toks {
|
|
if p.tok != tok {
|
|
p.errorf("expected %s, found %s", scanner.TokenString(tok),
|
|
scanner.TokenString(p.tok))
|
|
return false
|
|
}
|
|
if p.tok != scanner.EOF {
|
|
p.tok = p.scanner.Scan()
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (p *parser) next() {
|
|
if p.tok != scanner.EOF {
|
|
p.tok = p.scanner.Scan()
|
|
}
|
|
return
|
|
}
|
|
|
|
func (p *parser) parseDefinitions() (defs []Definition) {
|
|
for {
|
|
switch p.tok {
|
|
case scanner.Ident:
|
|
ident := p.scanner.TokenText()
|
|
pos := p.scanner.Position
|
|
|
|
p.accept(scanner.Ident)
|
|
|
|
switch p.tok {
|
|
case '=':
|
|
defs = append(defs, p.parseAssignment(ident, pos))
|
|
case '{':
|
|
defs = append(defs, p.parseModule(ident, pos))
|
|
default:
|
|
p.errorf("expected \"=\" or \"{\", found %s",
|
|
scanner.TokenString(p.tok))
|
|
}
|
|
case scanner.EOF:
|
|
return
|
|
default:
|
|
p.errorf("expected assignment or module definition, found %s",
|
|
scanner.TokenString(p.tok))
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
func (p *parser) parseAssignment(name string,
|
|
pos scanner.Position) (assignment *Assignment) {
|
|
|
|
assignment = new(Assignment)
|
|
|
|
if !p.accept('=') {
|
|
return
|
|
}
|
|
value := p.parseValue()
|
|
|
|
assignment.Name = name
|
|
assignment.Value = value
|
|
assignment.Pos = pos
|
|
|
|
return
|
|
}
|
|
|
|
func (p *parser) parseModule(typ string,
|
|
pos scanner.Position) (module *Module) {
|
|
|
|
module = new(Module)
|
|
|
|
if !p.accept('{') {
|
|
return
|
|
}
|
|
properties := p.parsePropertyList()
|
|
p.accept('}')
|
|
|
|
module.Type = typ
|
|
module.Properties = properties
|
|
module.Pos = pos
|
|
return
|
|
}
|
|
|
|
func (p *parser) parsePropertyList() (properties []*Property) {
|
|
for p.tok == scanner.Ident {
|
|
property := p.parseProperty()
|
|
properties = append(properties, property)
|
|
|
|
if p.tok != ',' {
|
|
// There was no comma, so the list is done.
|
|
break
|
|
}
|
|
|
|
p.accept(',')
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func (p *parser) parseProperty() (property *Property) {
|
|
property = new(Property)
|
|
|
|
name := p.scanner.TokenText()
|
|
pos := p.scanner.Position
|
|
if !p.accept(scanner.Ident, ':') {
|
|
return
|
|
}
|
|
value := p.parseValue()
|
|
|
|
property.Name = name
|
|
property.Value = value
|
|
property.Pos = pos
|
|
|
|
return
|
|
}
|
|
|
|
func (p *parser) parseValue() (value Value) {
|
|
switch p.tok {
|
|
case scanner.Ident:
|
|
return p.parseBoolValue()
|
|
case scanner.String:
|
|
return p.parseStringValue()
|
|
case '[':
|
|
return p.parseListValue()
|
|
case '{':
|
|
return p.parseMapValue()
|
|
default:
|
|
p.errorf("expected bool, list, or string value; found %s",
|
|
scanner.TokenString(p.tok))
|
|
return
|
|
}
|
|
}
|
|
|
|
func (p *parser) parseBoolValue() (value Value) {
|
|
value.Type = Bool
|
|
value.Pos = p.scanner.Position
|
|
switch text := p.scanner.TokenText(); text {
|
|
case "true":
|
|
value.BoolValue = true
|
|
case "false":
|
|
value.BoolValue = false
|
|
default:
|
|
p.errorf("expected true or false; found %q", text)
|
|
return
|
|
}
|
|
p.accept(scanner.Ident)
|
|
return
|
|
}
|
|
|
|
func (p *parser) parseStringValue() (value Value) {
|
|
value.Type = String
|
|
value.Pos = p.scanner.Position
|
|
str, err := strconv.Unquote(p.scanner.TokenText())
|
|
if err != nil {
|
|
p.errorf("couldn't parse string: %s", err)
|
|
return
|
|
}
|
|
value.StringValue = str
|
|
p.accept(scanner.String)
|
|
return
|
|
}
|
|
|
|
func (p *parser) parseListValue() (value Value) {
|
|
value.Type = List
|
|
value.Pos = p.scanner.Position
|
|
if !p.accept('[') {
|
|
return
|
|
}
|
|
|
|
var elements []Value
|
|
for p.tok == scanner.String {
|
|
elements = append(elements, p.parseStringValue())
|
|
|
|
if p.tok != ',' {
|
|
// There was no comma, so the list is done.
|
|
break
|
|
}
|
|
|
|
p.accept(',')
|
|
}
|
|
|
|
value.ListValue = elements
|
|
|
|
p.accept(']')
|
|
return
|
|
}
|
|
|
|
func (p *parser) parseMapValue() (value Value) {
|
|
value.Type = Map
|
|
value.Pos = p.scanner.Position
|
|
if !p.accept('{') {
|
|
return
|
|
}
|
|
|
|
properties := p.parsePropertyList()
|
|
value.MapValue = properties
|
|
|
|
p.accept('}')
|
|
return
|
|
}
|
|
|
|
type ValueType int
|
|
|
|
const (
|
|
Bool ValueType = iota
|
|
String
|
|
List
|
|
Map
|
|
)
|
|
|
|
func (p ValueType) String() string {
|
|
switch p {
|
|
case Bool:
|
|
return "bool"
|
|
case String:
|
|
return "string"
|
|
case List:
|
|
return "list"
|
|
case Map:
|
|
return "map"
|
|
default:
|
|
panic(fmt.Errorf("unknown value type: %d", p))
|
|
}
|
|
}
|
|
|
|
type Definition interface {
|
|
String() string
|
|
definitionTag()
|
|
}
|
|
|
|
type Assignment struct {
|
|
Name string
|
|
Value Value
|
|
Pos scanner.Position
|
|
}
|
|
|
|
func (a *Assignment) String() string {
|
|
return fmt.Sprintf("%s@%d:%s: %s", a.Name, a.Pos.Offset, a.Pos, a.Value)
|
|
}
|
|
|
|
func (a *Assignment) definitionTag() {}
|
|
|
|
type Module struct {
|
|
Type string
|
|
Properties []*Property
|
|
Pos scanner.Position
|
|
}
|
|
|
|
func (m *Module) String() string {
|
|
propertyStrings := make([]string, len(m.Properties))
|
|
for i, property := range m.Properties {
|
|
propertyStrings[i] = property.String()
|
|
}
|
|
return fmt.Sprintf("%s@%d:%s{%s}", m.Type, m.Pos.Offset, m.Pos,
|
|
strings.Join(propertyStrings, ", "))
|
|
}
|
|
|
|
func (m *Module) definitionTag() {}
|
|
|
|
type Property struct {
|
|
Name string
|
|
Value Value
|
|
Pos scanner.Position
|
|
}
|
|
|
|
func (p *Property) String() string {
|
|
return fmt.Sprintf("%s@%d:%s: %s", p.Name, p.Pos.Offset, p.Pos, p.Value)
|
|
}
|
|
|
|
type Value struct {
|
|
Type ValueType
|
|
BoolValue bool
|
|
StringValue string
|
|
ListValue []Value
|
|
MapValue []*Property
|
|
Pos scanner.Position
|
|
}
|
|
|
|
func (p Value) String() string {
|
|
switch p.Type {
|
|
case Bool:
|
|
return fmt.Sprintf("%t@%d:%s", p.BoolValue, p.Pos.Offset, p.Pos)
|
|
case String:
|
|
return fmt.Sprintf("%q@%d:%s", p.StringValue, p.Pos.Offset, p.Pos)
|
|
case List:
|
|
valueStrings := make([]string, len(p.ListValue))
|
|
for i, value := range p.ListValue {
|
|
valueStrings[i] = value.String()
|
|
}
|
|
return fmt.Sprintf("@%d:%s[%s]", p.Pos.Offset, p.Pos,
|
|
strings.Join(valueStrings, ", "))
|
|
case Map:
|
|
propertyStrings := make([]string, len(p.MapValue))
|
|
for i, property := range p.MapValue {
|
|
propertyStrings[i] = property.String()
|
|
}
|
|
return fmt.Sprintf("@%d:%s{%s}", p.Pos.Offset, p.Pos,
|
|
strings.Join(propertyStrings, ", "))
|
|
default:
|
|
panic(fmt.Errorf("bad property type: %d", p.Type))
|
|
}
|
|
}
|