blob: 07b26e8a8f39f7a8e04820ff02804e8858fe39f4 [file] [log] [blame]
package parser
import (
"errors"
"fmt"
"io"
"sort"
"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, scope *Scope) (defs []Definition, errs []error) {
p := newParser(r, scope)
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
scope *Scope
}
func newParser(r io.Reader, scope *Scope) *parser {
p := &parser{}
p.scope = scope
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
if p.scope != nil {
p.scope.Add(assignment)
}
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.parseVariable()
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) parseVariable() (value Value) {
value.Pos = p.scanner.Position
switch text := p.scanner.TokenText(); text {
case "true":
value.Type = Bool
value.BoolValue = true
case "false":
value.Type = Bool
value.BoolValue = false
default:
assignment, err := p.scope.Get(p.scanner.TokenText())
if err != nil {
p.errorf(err.Error())
}
value = assignment.Value
}
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 != ']' {
element := p.parseValue()
if element.Type != String {
p.errorf("Expected string in list, found %s", element.String())
return
}
elements = append(elements, element)
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))
}
}
type Scope struct {
vars map[string]*Assignment
}
func NewScope(s *Scope) *Scope {
newScope := &Scope{
vars: make(map[string]*Assignment),
}
if s != nil {
for k, v := range s.vars {
newScope.vars[k] = v
}
}
return newScope
}
func (s *Scope) Add(assignment *Assignment) error {
if old, ok := s.vars[assignment.Name]; ok {
return fmt.Errorf("variable already set, previous assignment: %s", old)
}
s.vars[assignment.Name] = assignment
return nil
}
func (s *Scope) Remove(name string) {
delete(s.vars, name)
}
func (s *Scope) Get(name string) (*Assignment, error) {
if a, ok := s.vars[name]; ok {
return a, nil
}
return nil, fmt.Errorf("variable %s not set", name)
}
func (s *Scope) String() string {
vars := []string{}
for k := range s.vars {
vars = append(vars, k)
}
sort.Strings(vars)
ret := []string{}
for _, v := range vars {
ret = append(ret, s.vars[v].String())
}
return strings.Join(ret, "\n")
}