platform_build_blueprint/parser/parser.go
Colin Cross 2108971145 Add convenience methods to Comment objects
Add String() to print out the Comment, Text() to print out the
Comment while stripping /*, //, and */, and EndLine() to return
the line number of the last line of the comment.

Change-Id: I076bc0990143acfc03c42a8cc569894fced8ee24
2015-06-30 14:33:38 -07:00

752 lines
15 KiB
Go

// Copyright 2014 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
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)
}
type File struct {
Defs []Definition
Comments []Comment
}
func parse(p *parser) (file *File, errs []error) {
defer func() {
if r := recover(); r != nil {
if r == errTooManyErrors {
errs = p.errors
return
}
panic(r)
}
}()
defs := p.parseDefinitions()
p.accept(scanner.EOF)
errs = p.errors
comments := p.comments
return &File{
Defs: defs,
Comments: comments,
}, errs
}
func ParseAndEval(filename string, r io.Reader, scope *Scope) (file *File, errs []error) {
p := newParser(r, scope)
p.eval = true
p.scanner.Filename = filename
return parse(p)
}
func Parse(filename string, r io.Reader, scope *Scope) (file *File, errs []error) {
p := newParser(r, scope)
p.scanner.Filename = filename
return parse(p)
}
type parser struct {
scanner scanner.Scanner
tok rune
errors []error
scope *Scope
comments []Comment
eval bool
}
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
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
}
p.next()
}
return true
}
func (p *parser) next() {
if p.tok != scanner.EOF {
p.tok = p.scanner.Scan()
for p.tok == scanner.Comment {
lines := strings.Split(p.scanner.TokenText(), "\n")
p.comments = append(p.comments, Comment{lines, p.scanner.Position})
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 '+':
p.accept('+')
defs = append(defs, p.parseAssignment(ident, pos, "+="))
case '=':
defs = append(defs, p.parseAssignment(ident, pos, "="))
case '{', '(':
defs = append(defs, p.parseModule(ident, pos))
default:
p.errorf("expected \"=\" or \"+=\" or \"{\" 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,
namePos scanner.Position, assigner string) (assignment *Assignment) {
assignment = new(Assignment)
pos := p.scanner.Position
if !p.accept('=') {
return
}
value := p.parseExpression()
assignment.Name = Ident{name, namePos}
assignment.Value = value
assignment.OrigValue = value
assignment.Pos = pos
assignment.Assigner = assigner
if p.scope != nil {
if assigner == "+=" {
if old, err := p.scope.Get(assignment.Name.Name); err == nil {
if old.Referenced {
p.errorf("modified variable with += after referencing")
}
old.Value, err = p.evaluateOperator(old.Value, assignment.Value, '+', assignment.Pos)
return
}
}
err := p.scope.Add(assignment)
if err != nil {
p.errorf("%s", err.Error())
}
}
return
}
func (p *parser) parseModule(typ string,
typPos scanner.Position) (module *Module) {
module = new(Module)
compat := false
lbracePos := p.scanner.Position
if p.tok == '{' {
compat = true
}
if !p.accept(p.tok) {
return
}
properties := p.parsePropertyList(true, compat)
rbracePos := p.scanner.Position
if !compat {
p.accept(')')
} else {
p.accept('}')
}
module.Type = Ident{typ, typPos}
module.Properties = properties
module.LbracePos = lbracePos
module.RbracePos = rbracePos
return
}
func (p *parser) parsePropertyList(isModule, compat bool) (properties []*Property) {
for p.tok == scanner.Ident {
property := p.parseProperty(isModule, compat)
properties = append(properties, property)
if p.tok != ',' {
// There was no comma, so the list is done.
break
}
p.accept(',')
}
return
}
func (p *parser) parseProperty(isModule, compat bool) (property *Property) {
property = new(Property)
name := p.scanner.TokenText()
namePos := p.scanner.Position
p.accept(scanner.Ident)
pos := p.scanner.Position
if isModule {
if compat && p.tok == ':' {
p.accept(':')
} else {
if !p.accept('=') {
return
}
}
} else {
if !p.accept(':') {
return
}
}
value := p.parseExpression()
property.Name = Ident{name, namePos}
property.Value = value
property.Pos = pos
return
}
func (p *parser) parseExpression() (value Value) {
value = p.parseValue()
switch p.tok {
case '+':
return p.parseOperator(value)
default:
return value
}
}
func (p *parser) evaluateOperator(value1, value2 Value, operator rune,
pos scanner.Position) (Value, error) {
value := Value{}
if p.eval {
if value1.Type != value2.Type {
return Value{}, fmt.Errorf("mismatched type in operator %c: %s != %s", operator,
value1.Type, value2.Type)
}
value = value1
value.Variable = ""
switch operator {
case '+':
switch value1.Type {
case String:
value.StringValue = value1.StringValue + value2.StringValue
case List:
value.ListValue = append([]Value{}, value1.ListValue...)
value.ListValue = append(value.ListValue, value2.ListValue...)
case Map:
var err error
value.MapValue, err = p.addMaps(value.MapValue, value2.MapValue, pos)
if err != nil {
return Value{}, err
}
default:
return Value{}, fmt.Errorf("operator %c not supported on type %s", operator,
value1.Type)
}
default:
panic("unknown operator " + string(operator))
}
}
value.Expression = &Expression{
Args: [2]Value{value1, value2},
Operator: operator,
Pos: pos,
}
return value, nil
}
func (p *parser) addMaps(map1, map2 []*Property, pos scanner.Position) ([]*Property, error) {
ret := make([]*Property, 0, len(map1))
inMap1 := make(map[string]*Property)
inMap2 := make(map[string]*Property)
inBoth := make(map[string]*Property)
for _, prop1 := range map1 {
inMap1[prop1.Name.Name] = prop1
}
for _, prop2 := range map2 {
inMap2[prop2.Name.Name] = prop2
if _, ok := inMap1[prop2.Name.Name]; ok {
inBoth[prop2.Name.Name] = prop2
}
}
for _, prop1 := range map1 {
if prop2, ok := inBoth[prop1.Name.Name]; ok {
var err error
newProp := *prop1
newProp.Value, err = p.evaluateOperator(prop1.Value, prop2.Value, '+', pos)
if err != nil {
return nil, err
}
ret = append(ret, &newProp)
} else {
ret = append(ret, prop1)
}
}
for _, prop2 := range map2 {
if _, ok := inBoth[prop2.Name.Name]; !ok {
ret = append(ret, prop2)
}
}
return ret, nil
}
func (p *parser) parseOperator(value1 Value) Value {
operator := p.tok
pos := p.scanner.Position
p.accept(operator)
value2 := p.parseExpression()
value, err := p.evaluateOperator(value1, value2, operator, pos)
if err != nil {
p.errorf(err.Error())
return Value{}
}
return value
}
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) {
switch text := p.scanner.TokenText(); text {
case "true":
value.Type = Bool
value.BoolValue = true
case "false":
value.Type = Bool
value.BoolValue = false
default:
variable := p.scanner.TokenText()
if p.eval {
assignment, err := p.scope.Get(variable)
if err != nil {
p.errorf(err.Error())
}
assignment.Referenced = true
value = assignment.Value
}
value.Variable = variable
}
value.Pos = p.scanner.Position
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.parseExpression()
if p.eval && 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
value.EndPos = p.scanner.Position
p.accept(']')
return
}
func (p *parser) parseMapValue() (value Value) {
value.Type = Map
value.Pos = p.scanner.Position
if !p.accept('{') {
return
}
properties := p.parsePropertyList(false, false)
value.MapValue = properties
value.EndPos = p.scanner.Position
p.accept('}')
return
}
type Expression struct {
Args [2]Value
Operator rune
Pos scanner.Position
}
func (e *Expression) String() string {
return fmt.Sprintf("(%s %c %s)@%d:%s", e.Args[0].String(), e.Operator, e.Args[1].String(),
e.Pos.Offset, e.Pos)
}
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 Ident
Value Value
OrigValue Value
Pos scanner.Position
Assigner string
Referenced bool
}
func (a *Assignment) String() string {
return fmt.Sprintf("%s@%d:%s %s %s", a.Name, a.Pos.Offset, a.Pos, a.Assigner, a.Value)
}
func (a *Assignment) definitionTag() {}
type Module struct {
Type Ident
Properties []*Property
LbracePos scanner.Position
RbracePos 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-%d:%s{%s}", m.Type,
m.LbracePos.Offset, m.LbracePos,
m.RbracePos.Offset, m.RbracePos,
strings.Join(propertyStrings, ", "))
}
func (m *Module) definitionTag() {}
type Property struct {
Name Ident
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 Ident struct {
Name string
Pos scanner.Position
}
func (i Ident) String() string {
return fmt.Sprintf("%s@%d:%s", i.Name, i.Pos.Offset, i.Pos)
}
type Value struct {
Type ValueType
BoolValue bool
StringValue string
ListValue []Value
MapValue []*Property
Expression *Expression
Variable string
Pos scanner.Position
EndPos scanner.Position
}
func (p Value) String() string {
var s string
if p.Variable != "" {
s += p.Variable + " = "
}
if p.Expression != nil {
s += p.Expression.String()
}
switch p.Type {
case Bool:
s += fmt.Sprintf("%t@%d:%s", p.BoolValue, p.Pos.Offset, p.Pos)
case String:
s += 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()
}
s += fmt.Sprintf("@%d:%s-%d:%s[%s]", p.Pos.Offset, p.Pos, p.EndPos.Offset, p.EndPos,
strings.Join(valueStrings, ", "))
case Map:
propertyStrings := make([]string, len(p.MapValue))
for i, property := range p.MapValue {
propertyStrings[i] = property.String()
}
s += fmt.Sprintf("@%d:%s-%d:%s{%s}", p.Pos.Offset, p.Pos, p.EndPos.Offset, p.EndPos,
strings.Join(propertyStrings, ", "))
default:
panic(fmt.Errorf("bad property type: %d", p.Type))
}
return s
}
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.Name]; ok {
return fmt.Errorf("variable already set, previous assignment: %s", old)
}
s.vars[assignment.Name.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")
}
type Comment struct {
Comment []string
Pos scanner.Position
}
func (c Comment) String() string {
l := 0
for _, comment := range c.Comment {
l += len(comment) + 1
}
buf := make([]byte, 0, l)
for _, comment := range c.Comment {
buf = append(buf, comment...)
buf = append(buf, '\n')
}
return string(buf)
}
// Return the text of the comment with // or /* and */ stripped
func (c Comment) Text() string {
l := 0
for _, comment := range c.Comment {
l += len(comment) + 1
}
buf := make([]byte, 0, l)
blockComment := false
if strings.HasPrefix(c.Comment[0], "/*") {
blockComment = true
}
for i, comment := range c.Comment {
if blockComment {
if i == 0 {
comment = strings.TrimPrefix(comment, "/*")
}
if i == len(c.Comment)-1 {
comment = strings.TrimSuffix(comment, "*/")
}
} else {
comment = strings.TrimPrefix(comment, "//")
}
buf = append(buf, comment...)
buf = append(buf, '\n')
}
return string(buf)
}
// Return the line number that the comment ends on
func (c Comment) EndLine() int {
return c.Pos.Line + len(c.Comment) - 1
}