// Copyright 2016 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 (
"fmt"
"strings"
"text/scanner"
)
type Node interface {
// Pos returns the position of the first token in the Node
Pos() scanner.Position
// End returns the position of the character after the last token in the Node
End() scanner.Position
}
// Definition is an Assignment or a Module at the top level of a Blueprints file
type Definition interface {
Node
String() string
definitionTag()
}
// An Assignment is a variable assignment at the top level of a Blueprints file, scoped to the
// file and and subdirs.
type Assignment struct {
Name string
NamePos scanner.Position
Value Expression
OrigValue Expression
EqualsPos scanner.Position
Assigner string
Referenced bool
}
func (a *Assignment) String() string {
return fmt.Sprintf("%s@%s %s %s (%s) %t", a.Name, a.EqualsPos, a.Assigner, a.Value, a.OrigValue, a.Referenced)
}
func (a *Assignment) Pos() scanner.Position { return a.NamePos }
func (a *Assignment) End() scanner.Position { return a.Value.End() }
func (a *Assignment) definitionTag() {}
// A Module is a module definition at the top level of a Blueprints file
type Module struct {
Type string
TypePos scanner.Position
Map
}
func (m *Module) Copy() *Module {
ret := *m
ret.Properties = make([]*Property, len(m.Properties))
for i := range m.Properties {
ret.Properties[i] = m.Properties[i].Copy()
}
return &ret
}
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@%s-%s{%s}", m.Type,
m.LBracePos, m.RBracePos,
strings.Join(propertyStrings, ", "))
}
func (m *Module) definitionTag() {}
func (m *Module) Pos() scanner.Position { return m.TypePos }
func (m *Module) End() scanner.Position { return m.Map.End() }
// A Property is a name: value pair within a Map, which may be a top level Module.
type Property struct {
Name string
NamePos scanner.Position
ColonPos scanner.Position
Value Expression
}
func (p *Property) Copy() *Property {
ret := *p
ret.Value = p.Value.Copy()
return &ret
}
func (p *Property) String() string {
return fmt.Sprintf("%s@%s: %s", p.Name, p.ColonPos, p.Value)
}
func (p *Property) Pos() scanner.Position { return p.NamePos }
func (p *Property) End() scanner.Position { return p.Value.End() }
// An Expression is a Value in a Property or Assignment. It can be a literal (String or Bool), a
// Map, a List, an Operator that combines two expressions of the same type, or a Variable that
// references and Assignment.
type Expression interface {
Node
// Copy returns a copy of the Expression that will not affect the original if mutated
Copy() Expression
String() string
// Type returns the underlying Type enum of the Expression if it were to be evalutated
Type() Type
// Eval returns an expression that is fully evaluated to a simple type (List, Map, String, or
// Bool). It will return the same object for every call to Eval().
Eval() Expression
}
// ExpressionsAreSame tells whether the two values are the same Expression.
// This includes the symbolic representation of each Expression but not their positions in the original source tree.
// This does not apply any simplification to the expressions before comparing them
// (for example, "!!a" wouldn't be deemed equal to "a")
func ExpressionsAreSame(a Expression, b Expression) (equal bool, err error) {
return hackyExpressionsAreSame(a, b)
}
// TODO(jeffrygaston) once positions are removed from Expression stucts,
// remove this function and have callers use reflect.DeepEqual(a, b)
func hackyExpressionsAreSame(a Expression, b Expression) (equal bool, err error) {
if a.Type() != b.Type() {
return false, nil
}
left, err := hackyFingerprint(a)
if err != nil {
return false, nil
}
right, err := hackyFingerprint(b)
if err != nil {
return false, nil
}
areEqual := string(left) == string(right)
return areEqual, nil
}
func hackyFingerprint(expression Expression) (fingerprint []byte, err error) {
assignment := &Assignment{"a", noPos, expression, expression, noPos, "=", false}
module := &File{}
module.Defs = append(module.Defs, assignment)
p := newPrinter(module)
return p.Print()
}
type Type int
const (
BoolType Type = iota + 1
StringType
Int64Type
ListType
MapType
)
func (t Type) String() string {
switch t {
case BoolType:
return "bool"
case StringType:
return "string"
case Int64Type:
return "int64"
case ListType:
return "list"
case MapType:
return "map"
default:
panic(fmt.Errorf("Unknown type %d", t))
}
}
type Operator struct {
Args [2]Expression
Operator rune
OperatorPos scanner.Position
Value Expression
}
func (x *Operator) Copy() Expression {
ret := *x
ret.Args[0] = x.Args[0].Copy()
ret.Args[1] = x.Args[1].Copy()
return &ret
}
func (x *Operator) Eval() Expression {
return x.Value.Eval()
}
func (x *Operator) Type() Type {
return x.Args[0].Type()
}
func (x *Operator) Pos() scanner.Position { return x.Args[0].Pos() }
func (x *Operator) End() scanner.Position { return x.Args[1].End() }
func (x *Operator) String() string {
return fmt.Sprintf("(%s %c %s = %s)@%s", x.Args[0].String(), x.Operator, x.Args[1].String(),
x.Value, x.OperatorPos)
}
type Variable struct {
Name string
NamePos scanner.Position
Value Expression
}
func (x *Variable) Pos() scanner.Position { return x.NamePos }
func (x *Variable) End() scanner.Position { return endPos(x.NamePos, len(x.Name)) }
func (x *Variable) Copy() Expression {
ret := *x
return &ret
}
func (x *Variable) Eval() Expression {
return x.Value.Eval()
}
func (x *Variable) String() string {
return x.Name + " = " + x.Value.String()
}
func (x *Variable) Type() Type { return x.Value.Type() }
type Map struct {
LBracePos scanner.Position
RBracePos scanner.Position
Properties []*Property
}
func (x *Map) Pos() scanner.Position { return x.LBracePos }
func (x *Map) End() scanner.Position { return endPos(x.RBracePos, 1) }
func (x *Map) Copy() Expression {
ret := *x
ret.Properties = make([]*Property, len(x.Properties))
for i := range x.Properties {
ret.Properties[i] = x.Properties[i].Copy()
}
return &ret
}
func (x *Map) Eval() Expression {
return x
}
func (x *Map) String() string {
propertyStrings := make([]string, len(x.Properties))
for i, property := range x.Properties {
propertyStrings[i] = property.String()
}
return fmt.Sprintf("@%s-%s{%s}", x.LBracePos, x.RBracePos,
strings.Join(propertyStrings, ", "))
}
func (x *Map) Type() Type { return MapType }
// GetProperty looks for a property with the given name.
// It resembles the bracket operator of a built-in Golang map.
func (x *Map) GetProperty(name string) (Property *Property, found bool) {
prop, found, _ := x.getPropertyImpl(name)
return prop, found // we don't currently expose the index to callers
}
func (x *Map) getPropertyImpl(name string) (Property *Property, found bool, index int) {
for i, prop := range x.Properties {
if prop.Name == name {
return prop, true, i
}
}
return nil, false, -1
}
// GetProperty removes the property with the given name, if it exists.
func (x *Map) RemoveProperty(propertyName string) (removed bool) {
_, found, index := x.getPropertyImpl(propertyName)
if found {
x.Properties = append(x.Properties[:index], x.Properties[index+1:]...)
}
return found
}
type List struct {
LBracePos scanner.Position
RBracePos scanner.Position
Values []Expression
}
func (x *List) Pos() scanner.Position { return x.LBracePos }
func (x *List) End() scanner.Position { return endPos(x.RBracePos, 1) }
func (x *List) Copy() Expression {
ret := *x
ret.Values = make([]Expression, len(x.Values))
for i := range ret.Values {
ret.Values[i] = x.Values[i].Copy()
}
return &ret
}
func (x *List) Eval() Expression {
return x
}
func (x *List) String() string {
valueStrings := make([]string, len(x.Values))
for i, value := range x.Values {
valueStrings[i] = value.String()
}
return fmt.Sprintf("@%s-%s[%s]", x.LBracePos, x.RBracePos,
strings.Join(valueStrings, ", "))
}
func (x *List) Type() Type { return ListType }
type String struct {
LiteralPos scanner.Position
Value string
}
func (x *String) Pos() scanner.Position { return x.LiteralPos }
func (x *String) End() scanner.Position { return endPos(x.LiteralPos, len(x.Value)+2) }
func (x *String) Copy() Expression {
ret := *x
return &ret
}
func (x *String) Eval() Expression {
return x
}
func (x *String) String() string {
return fmt.Sprintf("%q@%s", x.Value, x.LiteralPos)
}
func (x *String) Type() Type {
return StringType
}
type Int64 struct {
LiteralPos scanner.Position
Value int64
Token string
}
func (x *Int64) Pos() scanner.Position { return x.LiteralPos }
func (x *Int64) End() scanner.Position { return endPos(x.LiteralPos, len(x.Token)) }
func (x *Int64) Copy() Expression {
ret := *x
return &ret
}
func (x *Int64) Eval() Expression {
return x
}
func (x *Int64) String() string {
return fmt.Sprintf("%q@%s", x.Value, x.LiteralPos)
}
func (x *Int64) Type() Type {
return Int64Type
}
type Bool struct {
LiteralPos scanner.Position
Value bool
Token string
}
func (x *Bool) Pos() scanner.Position { return x.LiteralPos }
func (x *Bool) End() scanner.Position { return endPos(x.LiteralPos, len(x.Token)) }
func (x *Bool) Copy() Expression {
ret := *x
return &ret
}
func (x *Bool) Eval() Expression {
return x
}
func (x *Bool) String() string {
return fmt.Sprintf("%t@%s", x.Value, x.LiteralPos)
}
func (x *Bool) Type() Type {
return BoolType
}
type CommentGroup struct {
Comments []*Comment
}
func (x *CommentGroup) Pos() scanner.Position { return x.Comments[0].Pos() }
func (x *CommentGroup) End() scanner.Position { return x.Comments[len(x.Comments)-1].End() }
type Comment struct {
Comment []string
Slash scanner.Position
}
func (c Comment) Pos() scanner.Position {
return c.Slash
}
func (c Comment) End() scanner.Position {
pos := c.Slash
for _, comment := range c.Comment {
pos.Offset += len(comment) + 1
pos.Column = len(comment) + 1
}
pos.Line += len(c.Comment) - 1
return pos
}
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) + "@" + c.Slash.String()
}
// 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)
}
func endPos(pos scanner.Position, n int) scanner.Position {
pos.Offset += n
pos.Column += n
return pos
}