2015-01-23 23:15:10 +01:00
|
|
|
// 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.
|
|
|
|
|
2014-05-28 01:34:41 +02:00
|
|
|
package parser
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
2015-01-03 00:19:28 +01:00
|
|
|
"sort"
|
2014-05-28 01:34:41 +02:00
|
|
|
"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)
|
|
|
|
}
|
|
|
|
|
2015-01-08 23:56:03 +01:00
|
|
|
type File struct {
|
2015-07-01 01:05:22 +02:00
|
|
|
Name string
|
2015-01-08 23:56:03 +01:00
|
|
|
Defs []Definition
|
2016-06-11 02:27:12 +02:00
|
|
|
Comments []*CommentGroup
|
2016-06-10 02:40:13 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func (f *File) Pos() scanner.Position {
|
|
|
|
return scanner.Position{
|
|
|
|
Filename: f.Name,
|
|
|
|
Line: 1,
|
|
|
|
Column: 1,
|
|
|
|
Offset: 0,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *File) End() scanner.Position {
|
|
|
|
if len(f.Defs) > 0 {
|
|
|
|
return f.Defs[len(f.Defs)-1].End()
|
|
|
|
}
|
|
|
|
return noPos
|
2015-01-08 23:56:03 +01:00
|
|
|
}
|
|
|
|
|
2015-03-20 01:28:06 +01:00
|
|
|
func parse(p *parser) (file *File, errs []error) {
|
2014-05-28 01:34:41 +02:00
|
|
|
defer func() {
|
|
|
|
if r := recover(); r != nil {
|
|
|
|
if r == errTooManyErrors {
|
|
|
|
errs = p.errors
|
|
|
|
return
|
|
|
|
}
|
|
|
|
panic(r)
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
2015-01-08 23:56:03 +01:00
|
|
|
defs := p.parseDefinitions()
|
2014-05-28 01:34:41 +02:00
|
|
|
p.accept(scanner.EOF)
|
|
|
|
errs = p.errors
|
2015-01-08 23:56:03 +01:00
|
|
|
comments := p.comments
|
2014-05-28 01:34:41 +02:00
|
|
|
|
2015-01-08 23:56:03 +01:00
|
|
|
return &File{
|
2015-07-01 01:05:22 +02:00
|
|
|
Name: p.scanner.Filename,
|
2015-01-08 23:56:03 +01:00
|
|
|
Defs: defs,
|
|
|
|
Comments: comments,
|
|
|
|
}, errs
|
2015-03-20 01:28:06 +01:00
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
2014-05-28 01:34:41 +02:00
|
|
|
}
|
|
|
|
|
2022-02-09 03:16:02 +01:00
|
|
|
func ParseExpression(r io.Reader) (value Expression, errs []error) {
|
|
|
|
p := newParser(r, NewScope(nil))
|
|
|
|
value = p.parseExpression()
|
|
|
|
p.accept(scanner.EOF)
|
|
|
|
errs = p.errors
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2014-05-28 01:34:41 +02:00
|
|
|
type parser struct {
|
2015-03-20 01:28:06 +01:00
|
|
|
scanner scanner.Scanner
|
|
|
|
tok rune
|
|
|
|
errors []error
|
|
|
|
scope *Scope
|
2016-06-11 02:27:12 +02:00
|
|
|
comments []*CommentGroup
|
2015-03-20 01:28:06 +01:00
|
|
|
eval bool
|
2014-05-28 01:34:41 +02:00
|
|
|
}
|
|
|
|
|
2015-01-03 00:19:28 +01:00
|
|
|
func newParser(r io.Reader, scope *Scope) *parser {
|
2014-05-28 01:34:41 +02:00
|
|
|
p := &parser{}
|
2015-01-03 00:19:28 +01:00
|
|
|
p.scope = scope
|
2014-05-28 01:34:41 +02:00
|
|
|
p.scanner.Init(r)
|
|
|
|
p.scanner.Error = func(sc *scanner.Scanner, msg string) {
|
|
|
|
p.errorf(msg)
|
|
|
|
}
|
Support parsing int64 in Blueprint file.
Support int64 number instead of int to be more fixed to bit size so
that the underlying arch won't affect overflow cases. Besides,
refection: func (v Value) Int() int64 always cast to int64 no matter the
input is int, int16, int32. Currently we always treat "-" as negative
sign to bind to next value, and "+" as plus operator to add operands
together.
So we allow:
a = 5 + -4 + 5 or a = -4 + 5
But we don't allow:
a = +5 + 4 + -4 since we don't treat "+" as a positive sign, otherwise,
a = 5 + +5 would exist which looks pretty weird. In the future, we may
want fully support number calculator logic eg, "+"/"-" can be
positive/negative sign or operator, and "(" and ")" will be considered
to group expressions with a higher precedence.
int & uint properties within struct keeps unchanged, which is only
allowed when tagged with 'blueprint:mutated'. We only allow *int64
property instead of int64 property within struct since it does't make
sense to do prepending or appending to int64.
Change-Id: I565e046dbd268af3538aee148cd7300037e56523
2017-11-01 22:03:28 +01:00
|
|
|
p.scanner.Mode = scanner.ScanIdents | scanner.ScanInts | scanner.ScanStrings |
|
2015-01-08 23:56:03 +01:00
|
|
|
scanner.ScanRawStrings | scanner.ScanComments
|
2014-05-28 01:34:41 +02:00
|
|
|
p.next()
|
|
|
|
return p
|
|
|
|
}
|
|
|
|
|
2015-07-11 02:51:55 +02:00
|
|
|
func (p *parser) error(err error) {
|
2014-05-28 01:34:41 +02:00
|
|
|
pos := p.scanner.Position
|
|
|
|
if !pos.IsValid() {
|
|
|
|
pos = p.scanner.Pos()
|
|
|
|
}
|
2015-07-11 02:51:55 +02:00
|
|
|
err = &ParseError{
|
|
|
|
Err: err,
|
2014-05-28 01:34:41 +02:00
|
|
|
Pos: pos,
|
|
|
|
}
|
|
|
|
p.errors = append(p.errors, err)
|
|
|
|
if len(p.errors) >= maxErrors {
|
|
|
|
panic(errTooManyErrors)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-07-11 02:51:55 +02:00
|
|
|
func (p *parser) errorf(format string, args ...interface{}) {
|
|
|
|
p.error(fmt.Errorf(format, args...))
|
|
|
|
}
|
|
|
|
|
2014-05-28 01:34:41 +02:00
|
|
|
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
|
|
|
|
}
|
2015-01-08 23:56:03 +01:00
|
|
|
p.next()
|
2014-05-28 01:34:41 +02:00
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *parser) next() {
|
|
|
|
if p.tok != scanner.EOF {
|
|
|
|
p.tok = p.scanner.Scan()
|
2016-06-11 02:27:12 +02:00
|
|
|
if p.tok == scanner.Comment {
|
|
|
|
var comments []*Comment
|
|
|
|
for p.tok == scanner.Comment {
|
|
|
|
lines := strings.Split(p.scanner.TokenText(), "\n")
|
|
|
|
if len(comments) > 0 && p.scanner.Position.Line > comments[len(comments)-1].End().Line+1 {
|
|
|
|
p.comments = append(p.comments, &CommentGroup{Comments: comments})
|
|
|
|
comments = nil
|
|
|
|
}
|
|
|
|
comments = append(comments, &Comment{lines, p.scanner.Position})
|
|
|
|
p.tok = p.scanner.Scan()
|
|
|
|
}
|
|
|
|
p.comments = append(p.comments, &CommentGroup{Comments: comments})
|
2015-01-08 23:56:03 +01:00
|
|
|
}
|
2014-05-28 01:34:41 +02:00
|
|
|
}
|
|
|
|
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 {
|
2015-02-04 19:50:22 +01:00
|
|
|
case '+':
|
|
|
|
p.accept('+')
|
|
|
|
defs = append(defs, p.parseAssignment(ident, pos, "+="))
|
2014-05-28 01:34:41 +02:00
|
|
|
case '=':
|
2015-02-04 19:50:22 +01:00
|
|
|
defs = append(defs, p.parseAssignment(ident, pos, "="))
|
2015-01-15 02:04:13 +01:00
|
|
|
case '{', '(':
|
2014-05-28 01:34:41 +02:00
|
|
|
defs = append(defs, p.parseModule(ident, pos))
|
|
|
|
default:
|
2015-02-04 19:50:22 +01:00
|
|
|
p.errorf("expected \"=\" or \"+=\" or \"{\" or \"(\", found %s",
|
2014-05-28 01:34:41 +02:00
|
|
|
scanner.TokenString(p.tok))
|
|
|
|
}
|
|
|
|
case scanner.EOF:
|
|
|
|
return
|
|
|
|
default:
|
|
|
|
p.errorf("expected assignment or module definition, found %s",
|
|
|
|
scanner.TokenString(p.tok))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-06-07 21:28:16 +02:00
|
|
|
func (p *parser) parseAssignment(name string, namePos scanner.Position,
|
|
|
|
assigner string) (assignment *Assignment) {
|
2014-05-28 01:34:41 +02:00
|
|
|
|
|
|
|
assignment = new(Assignment)
|
|
|
|
|
2015-01-08 23:56:03 +01:00
|
|
|
pos := p.scanner.Position
|
2014-05-28 01:34:41 +02:00
|
|
|
if !p.accept('=') {
|
|
|
|
return
|
|
|
|
}
|
2015-01-03 00:47:54 +01:00
|
|
|
value := p.parseExpression()
|
2014-05-28 01:34:41 +02:00
|
|
|
|
2016-06-10 00:52:30 +02:00
|
|
|
assignment.Name = name
|
|
|
|
assignment.NamePos = namePos
|
2014-05-28 01:34:41 +02:00
|
|
|
assignment.Value = value
|
2015-02-04 19:50:22 +01:00
|
|
|
assignment.OrigValue = value
|
2016-06-10 02:03:57 +02:00
|
|
|
assignment.EqualsPos = pos
|
2015-02-04 19:50:22 +01:00
|
|
|
assignment.Assigner = assigner
|
2014-05-28 01:34:41 +02:00
|
|
|
|
2015-01-03 00:19:28 +01:00
|
|
|
if p.scope != nil {
|
2015-02-04 19:50:22 +01:00
|
|
|
if assigner == "+=" {
|
2016-06-10 00:52:30 +02:00
|
|
|
if old, local := p.scope.Get(assignment.Name); old == nil {
|
|
|
|
p.errorf("modified non-existent variable %q with +=", assignment.Name)
|
2015-07-11 02:51:55 +02:00
|
|
|
} else if !local {
|
2016-06-10 00:52:30 +02:00
|
|
|
p.errorf("modified non-local variable %q with +=", assignment.Name)
|
2015-07-11 02:51:55 +02:00
|
|
|
} else if old.Referenced {
|
2016-06-10 00:52:30 +02:00
|
|
|
p.errorf("modified variable %q with += after referencing", assignment.Name)
|
2015-07-11 02:51:55 +02:00
|
|
|
} else {
|
2016-06-10 02:03:57 +02:00
|
|
|
val, err := p.evaluateOperator(old.Value, assignment.Value, '+', assignment.EqualsPos)
|
2015-07-11 02:51:55 +02:00
|
|
|
if err != nil {
|
|
|
|
p.error(err)
|
|
|
|
} else {
|
|
|
|
old.Value = val
|
2015-03-20 01:28:06 +01:00
|
|
|
}
|
2015-02-04 19:50:22 +01:00
|
|
|
}
|
2015-07-11 02:51:55 +02:00
|
|
|
} else {
|
|
|
|
err := p.scope.Add(assignment)
|
|
|
|
if err != nil {
|
|
|
|
p.error(err)
|
|
|
|
}
|
2015-03-20 01:28:06 +01:00
|
|
|
}
|
2015-01-03 00:19:28 +01:00
|
|
|
}
|
|
|
|
|
2014-05-28 01:34:41 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2016-06-07 21:28:16 +02:00
|
|
|
func (p *parser) parseModule(typ string, typPos scanner.Position) *Module {
|
2014-05-28 01:34:41 +02:00
|
|
|
|
2015-01-15 02:04:13 +01:00
|
|
|
compat := false
|
2015-01-08 23:56:03 +01:00
|
|
|
lbracePos := p.scanner.Position
|
2015-01-15 02:04:13 +01:00
|
|
|
if p.tok == '{' {
|
|
|
|
compat = true
|
|
|
|
}
|
|
|
|
|
|
|
|
if !p.accept(p.tok) {
|
2016-06-07 21:28:16 +02:00
|
|
|
return nil
|
2014-05-28 01:34:41 +02:00
|
|
|
}
|
2015-01-15 02:04:13 +01:00
|
|
|
properties := p.parsePropertyList(true, compat)
|
2015-01-08 23:56:03 +01:00
|
|
|
rbracePos := p.scanner.Position
|
2015-01-15 02:04:13 +01:00
|
|
|
if !compat {
|
|
|
|
p.accept(')')
|
|
|
|
} else {
|
|
|
|
p.accept('}')
|
|
|
|
}
|
2014-05-28 01:34:41 +02:00
|
|
|
|
2016-06-07 21:28:16 +02:00
|
|
|
return &Module{
|
2016-06-10 00:52:30 +02:00
|
|
|
Type: typ,
|
|
|
|
TypePos: typPos,
|
2016-06-07 21:28:16 +02:00
|
|
|
Map: Map{
|
|
|
|
Properties: properties,
|
|
|
|
LBracePos: lbracePos,
|
|
|
|
RBracePos: rbracePos,
|
|
|
|
},
|
|
|
|
}
|
2014-05-28 01:34:41 +02:00
|
|
|
}
|
|
|
|
|
2015-01-15 02:04:13 +01:00
|
|
|
func (p *parser) parsePropertyList(isModule, compat bool) (properties []*Property) {
|
2014-05-28 01:34:41 +02:00
|
|
|
for p.tok == scanner.Ident {
|
2015-01-15 02:04:13 +01:00
|
|
|
property := p.parseProperty(isModule, compat)
|
2014-05-28 01:34:41 +02:00
|
|
|
properties = append(properties, property)
|
|
|
|
|
|
|
|
if p.tok != ',' {
|
|
|
|
// There was no comma, so the list is done.
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
p.accept(',')
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-05-21 23:56:53 +02:00
|
|
|
func (p *parser) parseMapItemList() []*MapItem {
|
|
|
|
var items []*MapItem
|
|
|
|
// this is a map, not a struct, we only know we're at the end if we hit a '}'
|
|
|
|
for p.tok != '}' {
|
|
|
|
items = append(items, p.parseMapItem())
|
|
|
|
|
|
|
|
if p.tok != ',' {
|
|
|
|
// There was no comma, so the list is done.
|
|
|
|
break
|
|
|
|
}
|
|
|
|
p.accept(',')
|
|
|
|
}
|
|
|
|
return items
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *parser) parseMapItem() *MapItem {
|
|
|
|
keyExpression := p.parseExpression()
|
|
|
|
if keyExpression.Type() != StringType {
|
|
|
|
p.errorf("only strings are supported as map keys: %s (%s)", keyExpression.Type(), keyExpression.String())
|
|
|
|
}
|
|
|
|
key := keyExpression.(*String)
|
|
|
|
p.accept(':')
|
|
|
|
pos := p.scanner.Position
|
|
|
|
value := p.parseExpression()
|
|
|
|
return &MapItem{
|
|
|
|
ColonPos: pos,
|
|
|
|
Key: key,
|
|
|
|
Value: value,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-01-15 02:04:13 +01:00
|
|
|
func (p *parser) parseProperty(isModule, compat bool) (property *Property) {
|
2014-05-28 01:34:41 +02:00
|
|
|
property = new(Property)
|
|
|
|
|
|
|
|
name := p.scanner.TokenText()
|
2015-01-08 23:56:03 +01:00
|
|
|
namePos := p.scanner.Position
|
|
|
|
p.accept(scanner.Ident)
|
2014-05-28 01:34:41 +02:00
|
|
|
pos := p.scanner.Position
|
2015-01-15 02:04:13 +01:00
|
|
|
|
|
|
|
if isModule {
|
2018-06-25 05:52:48 +02:00
|
|
|
if compat {
|
|
|
|
if !p.accept(':') {
|
|
|
|
return
|
|
|
|
}
|
2015-01-15 02:04:13 +01:00
|
|
|
} else {
|
|
|
|
if !p.accept('=') {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if !p.accept(':') {
|
|
|
|
return
|
|
|
|
}
|
2014-05-28 01:34:41 +02:00
|
|
|
}
|
2015-01-08 23:56:03 +01:00
|
|
|
|
2015-01-03 00:47:54 +01:00
|
|
|
value := p.parseExpression()
|
2014-05-28 01:34:41 +02:00
|
|
|
|
2016-06-10 00:52:30 +02:00
|
|
|
property.Name = name
|
|
|
|
property.NamePos = namePos
|
2014-05-28 01:34:41 +02:00
|
|
|
property.Value = value
|
2016-06-10 02:03:57 +02:00
|
|
|
property.ColonPos = pos
|
2014-05-28 01:34:41 +02:00
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2016-06-07 21:28:16 +02:00
|
|
|
func (p *parser) parseExpression() (value Expression) {
|
2015-01-03 00:47:54 +01:00
|
|
|
value = p.parseValue()
|
|
|
|
switch p.tok {
|
|
|
|
case '+':
|
|
|
|
return p.parseOperator(value)
|
Support parsing int64 in Blueprint file.
Support int64 number instead of int to be more fixed to bit size so
that the underlying arch won't affect overflow cases. Besides,
refection: func (v Value) Int() int64 always cast to int64 no matter the
input is int, int16, int32. Currently we always treat "-" as negative
sign to bind to next value, and "+" as plus operator to add operands
together.
So we allow:
a = 5 + -4 + 5 or a = -4 + 5
But we don't allow:
a = +5 + 4 + -4 since we don't treat "+" as a positive sign, otherwise,
a = 5 + +5 would exist which looks pretty weird. In the future, we may
want fully support number calculator logic eg, "+"/"-" can be
positive/negative sign or operator, and "(" and ")" will be considered
to group expressions with a higher precedence.
int & uint properties within struct keeps unchanged, which is only
allowed when tagged with 'blueprint:mutated'. We only allow *int64
property instead of int64 property within struct since it does't make
sense to do prepending or appending to int64.
Change-Id: I565e046dbd268af3538aee148cd7300037e56523
2017-11-01 22:03:28 +01:00
|
|
|
case '-':
|
|
|
|
p.errorf("subtraction not supported: %s", p.scanner.String())
|
|
|
|
return value
|
2015-01-03 00:47:54 +01:00
|
|
|
default:
|
|
|
|
return value
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-06-07 21:28:16 +02:00
|
|
|
func (p *parser) evaluateOperator(value1, value2 Expression, operator rune,
|
|
|
|
pos scanner.Position) (*Operator, error) {
|
2015-01-03 00:47:54 +01:00
|
|
|
|
2016-06-07 21:28:16 +02:00
|
|
|
value := value1
|
2015-01-08 23:56:03 +01:00
|
|
|
|
2015-03-20 01:28:06 +01:00
|
|
|
if p.eval {
|
2016-06-07 21:28:16 +02:00
|
|
|
e1 := value1.Eval()
|
|
|
|
e2 := value2.Eval()
|
|
|
|
if e1.Type() != e2.Type() {
|
|
|
|
return nil, fmt.Errorf("mismatched type in operator %c: %s != %s", operator,
|
|
|
|
e1.Type(), e2.Type())
|
2015-03-20 01:28:06 +01:00
|
|
|
}
|
|
|
|
|
2016-06-07 21:28:16 +02:00
|
|
|
value = e1.Copy()
|
2015-03-20 01:28:06 +01:00
|
|
|
|
|
|
|
switch operator {
|
|
|
|
case '+':
|
2016-06-07 21:28:16 +02:00
|
|
|
switch v := value.(type) {
|
|
|
|
case *String:
|
|
|
|
v.Value += e2.(*String).Value
|
Support parsing int64 in Blueprint file.
Support int64 number instead of int to be more fixed to bit size so
that the underlying arch won't affect overflow cases. Besides,
refection: func (v Value) Int() int64 always cast to int64 no matter the
input is int, int16, int32. Currently we always treat "-" as negative
sign to bind to next value, and "+" as plus operator to add operands
together.
So we allow:
a = 5 + -4 + 5 or a = -4 + 5
But we don't allow:
a = +5 + 4 + -4 since we don't treat "+" as a positive sign, otherwise,
a = 5 + +5 would exist which looks pretty weird. In the future, we may
want fully support number calculator logic eg, "+"/"-" can be
positive/negative sign or operator, and "(" and ")" will be considered
to group expressions with a higher precedence.
int & uint properties within struct keeps unchanged, which is only
allowed when tagged with 'blueprint:mutated'. We only allow *int64
property instead of int64 property within struct since it does't make
sense to do prepending or appending to int64.
Change-Id: I565e046dbd268af3538aee148cd7300037e56523
2017-11-01 22:03:28 +01:00
|
|
|
case *Int64:
|
|
|
|
v.Value += e2.(*Int64).Value
|
2018-03-22 01:00:39 +01:00
|
|
|
v.Token = ""
|
2016-06-07 21:28:16 +02:00
|
|
|
case *List:
|
|
|
|
v.Values = append(v.Values, e2.(*List).Values...)
|
|
|
|
case *Map:
|
2015-03-20 01:28:06 +01:00
|
|
|
var err error
|
2016-06-07 21:28:16 +02:00
|
|
|
v.Properties, err = p.addMaps(v.Properties, e2.(*Map).Properties, pos)
|
2015-03-20 01:28:06 +01:00
|
|
|
if err != nil {
|
2016-06-07 21:28:16 +02:00
|
|
|
return nil, err
|
2015-03-20 01:28:06 +01:00
|
|
|
}
|
|
|
|
default:
|
2016-06-07 21:28:16 +02:00
|
|
|
return nil, fmt.Errorf("operator %c not supported on type %s", operator, v.Type())
|
2015-02-12 19:12:10 +01:00
|
|
|
}
|
2015-01-03 00:47:54 +01:00
|
|
|
default:
|
2015-03-20 01:28:06 +01:00
|
|
|
panic("unknown operator " + string(operator))
|
2015-01-03 00:47:54 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-06-07 21:28:16 +02:00
|
|
|
return &Operator{
|
|
|
|
Args: [2]Expression{value1, value2},
|
|
|
|
Operator: operator,
|
|
|
|
OperatorPos: pos,
|
|
|
|
Value: value,
|
|
|
|
}, nil
|
2015-02-04 19:50:22 +01:00
|
|
|
}
|
|
|
|
|
2015-03-20 01:28:06 +01:00
|
|
|
func (p *parser) addMaps(map1, map2 []*Property, pos scanner.Position) ([]*Property, error) {
|
2015-02-12 19:12:10 +01:00
|
|
|
ret := make([]*Property, 0, len(map1))
|
2015-03-10 22:37:27 +01:00
|
|
|
|
2015-02-12 19:12:10 +01:00
|
|
|
inMap1 := make(map[string]*Property)
|
|
|
|
inMap2 := make(map[string]*Property)
|
|
|
|
inBoth := make(map[string]*Property)
|
2015-03-10 22:37:27 +01:00
|
|
|
|
2015-02-12 19:12:10 +01:00
|
|
|
for _, prop1 := range map1 {
|
2016-06-10 00:52:30 +02:00
|
|
|
inMap1[prop1.Name] = prop1
|
2015-02-12 19:12:10 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
for _, prop2 := range map2 {
|
2016-06-10 00:52:30 +02:00
|
|
|
inMap2[prop2.Name] = prop2
|
|
|
|
if _, ok := inMap1[prop2.Name]; ok {
|
|
|
|
inBoth[prop2.Name] = prop2
|
2015-02-12 19:12:10 +01:00
|
|
|
}
|
|
|
|
}
|
2015-03-10 22:37:27 +01:00
|
|
|
|
2015-02-12 19:12:10 +01:00
|
|
|
for _, prop1 := range map1 {
|
2016-06-10 00:52:30 +02:00
|
|
|
if prop2, ok := inBoth[prop1.Name]; ok {
|
2015-02-12 19:12:10 +01:00
|
|
|
var err error
|
|
|
|
newProp := *prop1
|
2015-03-20 01:28:06 +01:00
|
|
|
newProp.Value, err = p.evaluateOperator(prop1.Value, prop2.Value, '+', pos)
|
2015-02-12 19:12:10 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
ret = append(ret, &newProp)
|
|
|
|
} else {
|
|
|
|
ret = append(ret, prop1)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, prop2 := range map2 {
|
2016-06-10 00:52:30 +02:00
|
|
|
if _, ok := inBoth[prop2.Name]; !ok {
|
2015-02-12 19:12:10 +01:00
|
|
|
ret = append(ret, prop2)
|
|
|
|
}
|
|
|
|
}
|
2015-03-10 22:37:27 +01:00
|
|
|
|
2015-02-12 19:12:10 +01:00
|
|
|
return ret, nil
|
|
|
|
}
|
|
|
|
|
2016-06-07 21:28:16 +02:00
|
|
|
func (p *parser) parseOperator(value1 Expression) *Operator {
|
2015-02-04 19:50:22 +01:00
|
|
|
operator := p.tok
|
|
|
|
pos := p.scanner.Position
|
|
|
|
p.accept(operator)
|
|
|
|
|
|
|
|
value2 := p.parseExpression()
|
|
|
|
|
2015-03-20 01:28:06 +01:00
|
|
|
value, err := p.evaluateOperator(value1, value2, operator, pos)
|
2015-02-04 19:50:22 +01:00
|
|
|
if err != nil {
|
2015-07-11 02:51:55 +02:00
|
|
|
p.error(err)
|
2016-06-07 21:28:16 +02:00
|
|
|
return nil
|
2015-02-04 19:50:22 +01:00
|
|
|
}
|
|
|
|
|
2015-01-08 23:56:03 +01:00
|
|
|
return value
|
2016-06-07 21:28:16 +02:00
|
|
|
|
2015-01-03 00:47:54 +01:00
|
|
|
}
|
|
|
|
|
2016-06-07 21:28:16 +02:00
|
|
|
func (p *parser) parseValue() (value Expression) {
|
2014-05-28 01:34:41 +02:00
|
|
|
switch p.tok {
|
|
|
|
case scanner.Ident:
|
2015-01-03 00:19:28 +01:00
|
|
|
return p.parseVariable()
|
Support parsing int64 in Blueprint file.
Support int64 number instead of int to be more fixed to bit size so
that the underlying arch won't affect overflow cases. Besides,
refection: func (v Value) Int() int64 always cast to int64 no matter the
input is int, int16, int32. Currently we always treat "-" as negative
sign to bind to next value, and "+" as plus operator to add operands
together.
So we allow:
a = 5 + -4 + 5 or a = -4 + 5
But we don't allow:
a = +5 + 4 + -4 since we don't treat "+" as a positive sign, otherwise,
a = 5 + +5 would exist which looks pretty weird. In the future, we may
want fully support number calculator logic eg, "+"/"-" can be
positive/negative sign or operator, and "(" and ")" will be considered
to group expressions with a higher precedence.
int & uint properties within struct keeps unchanged, which is only
allowed when tagged with 'blueprint:mutated'. We only allow *int64
property instead of int64 property within struct since it does't make
sense to do prepending or appending to int64.
Change-Id: I565e046dbd268af3538aee148cd7300037e56523
2017-11-01 22:03:28 +01:00
|
|
|
case '-', scanner.Int: // Integer might have '-' sign ahead ('+' is only treated as operator now)
|
|
|
|
return p.parseIntValue()
|
2022-01-19 00:35:08 +01:00
|
|
|
case scanner.String, scanner.RawString:
|
2014-05-28 01:34:41 +02:00
|
|
|
return p.parseStringValue()
|
|
|
|
case '[':
|
|
|
|
return p.parseListValue()
|
2014-07-26 02:01:20 +02:00
|
|
|
case '{':
|
|
|
|
return p.parseMapValue()
|
2014-05-28 01:34:41 +02:00
|
|
|
default:
|
|
|
|
p.errorf("expected bool, list, or string value; found %s",
|
|
|
|
scanner.TokenString(p.tok))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-06-07 21:28:16 +02:00
|
|
|
func (p *parser) parseVariable() Expression {
|
|
|
|
var value Expression
|
|
|
|
|
2014-05-28 01:34:41 +02:00
|
|
|
switch text := p.scanner.TokenText(); text {
|
2016-06-07 21:28:16 +02:00
|
|
|
case "true", "false":
|
|
|
|
value = &Bool{
|
|
|
|
LiteralPos: p.scanner.Position,
|
|
|
|
Value: text == "true",
|
2018-03-22 01:00:39 +01:00
|
|
|
Token: text,
|
2016-06-07 21:28:16 +02:00
|
|
|
}
|
2014-05-28 01:34:41 +02:00
|
|
|
default:
|
2015-03-20 01:28:06 +01:00
|
|
|
if p.eval {
|
2016-06-07 21:28:16 +02:00
|
|
|
if assignment, local := p.scope.Get(text); assignment == nil {
|
|
|
|
p.errorf("variable %q is not set", text)
|
2015-07-11 02:51:55 +02:00
|
|
|
} else {
|
|
|
|
if local {
|
|
|
|
assignment.Referenced = true
|
|
|
|
}
|
|
|
|
value = assignment.Value
|
2015-03-20 01:28:06 +01:00
|
|
|
}
|
2020-01-21 22:31:06 +01:00
|
|
|
} else {
|
|
|
|
value = &NotEvaluated{}
|
2015-01-03 00:19:28 +01:00
|
|
|
}
|
2016-06-07 21:28:16 +02:00
|
|
|
value = &Variable{
|
|
|
|
Name: text,
|
|
|
|
NamePos: p.scanner.Position,
|
|
|
|
Value: value,
|
|
|
|
}
|
2014-05-28 01:34:41 +02:00
|
|
|
}
|
2015-01-03 00:19:28 +01:00
|
|
|
|
2014-05-28 01:34:41 +02:00
|
|
|
p.accept(scanner.Ident)
|
2016-06-07 21:28:16 +02:00
|
|
|
return value
|
2014-05-28 01:34:41 +02:00
|
|
|
}
|
|
|
|
|
2016-06-07 21:28:16 +02:00
|
|
|
func (p *parser) parseStringValue() *String {
|
2014-05-28 01:34:41 +02:00
|
|
|
str, err := strconv.Unquote(p.scanner.TokenText())
|
|
|
|
if err != nil {
|
|
|
|
p.errorf("couldn't parse string: %s", err)
|
2016-06-07 21:28:16 +02:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
value := &String{
|
|
|
|
LiteralPos: p.scanner.Position,
|
|
|
|
Value: str,
|
2014-05-28 01:34:41 +02:00
|
|
|
}
|
2022-01-19 00:35:08 +01:00
|
|
|
p.accept(p.tok)
|
2016-06-07 21:28:16 +02:00
|
|
|
return value
|
2014-05-28 01:34:41 +02:00
|
|
|
}
|
|
|
|
|
Support parsing int64 in Blueprint file.
Support int64 number instead of int to be more fixed to bit size so
that the underlying arch won't affect overflow cases. Besides,
refection: func (v Value) Int() int64 always cast to int64 no matter the
input is int, int16, int32. Currently we always treat "-" as negative
sign to bind to next value, and "+" as plus operator to add operands
together.
So we allow:
a = 5 + -4 + 5 or a = -4 + 5
But we don't allow:
a = +5 + 4 + -4 since we don't treat "+" as a positive sign, otherwise,
a = 5 + +5 would exist which looks pretty weird. In the future, we may
want fully support number calculator logic eg, "+"/"-" can be
positive/negative sign or operator, and "(" and ")" will be considered
to group expressions with a higher precedence.
int & uint properties within struct keeps unchanged, which is only
allowed when tagged with 'blueprint:mutated'. We only allow *int64
property instead of int64 property within struct since it does't make
sense to do prepending or appending to int64.
Change-Id: I565e046dbd268af3538aee148cd7300037e56523
2017-11-01 22:03:28 +01:00
|
|
|
func (p *parser) parseIntValue() *Int64 {
|
|
|
|
var str string
|
|
|
|
literalPos := p.scanner.Position
|
|
|
|
if p.tok == '-' {
|
|
|
|
str += string(p.tok)
|
|
|
|
p.accept(p.tok)
|
|
|
|
if p.tok != scanner.Int {
|
|
|
|
p.errorf("expected int; found %s", scanner.TokenString(p.tok))
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
str += p.scanner.TokenText()
|
|
|
|
i, err := strconv.ParseInt(str, 10, 64)
|
|
|
|
if err != nil {
|
|
|
|
p.errorf("couldn't parse int: %s", err)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
value := &Int64{
|
|
|
|
LiteralPos: literalPos,
|
|
|
|
Value: i,
|
2018-03-22 01:00:39 +01:00
|
|
|
Token: str,
|
Support parsing int64 in Blueprint file.
Support int64 number instead of int to be more fixed to bit size so
that the underlying arch won't affect overflow cases. Besides,
refection: func (v Value) Int() int64 always cast to int64 no matter the
input is int, int16, int32. Currently we always treat "-" as negative
sign to bind to next value, and "+" as plus operator to add operands
together.
So we allow:
a = 5 + -4 + 5 or a = -4 + 5
But we don't allow:
a = +5 + 4 + -4 since we don't treat "+" as a positive sign, otherwise,
a = 5 + +5 would exist which looks pretty weird. In the future, we may
want fully support number calculator logic eg, "+"/"-" can be
positive/negative sign or operator, and "(" and ")" will be considered
to group expressions with a higher precedence.
int & uint properties within struct keeps unchanged, which is only
allowed when tagged with 'blueprint:mutated'. We only allow *int64
property instead of int64 property within struct since it does't make
sense to do prepending or appending to int64.
Change-Id: I565e046dbd268af3538aee148cd7300037e56523
2017-11-01 22:03:28 +01:00
|
|
|
}
|
|
|
|
p.accept(scanner.Int)
|
|
|
|
return value
|
|
|
|
}
|
|
|
|
|
2016-06-07 21:28:16 +02:00
|
|
|
func (p *parser) parseListValue() *List {
|
|
|
|
lBracePos := p.scanner.Position
|
2014-05-28 01:34:41 +02:00
|
|
|
if !p.accept('[') {
|
2016-06-07 21:28:16 +02:00
|
|
|
return nil
|
2014-05-28 01:34:41 +02:00
|
|
|
}
|
|
|
|
|
2016-06-07 21:28:16 +02:00
|
|
|
var elements []Expression
|
2015-01-03 00:19:28 +01:00
|
|
|
for p.tok != ']' {
|
2015-01-03 00:47:54 +01:00
|
|
|
element := p.parseExpression()
|
2015-01-03 00:19:28 +01:00
|
|
|
elements = append(elements, element)
|
2014-05-28 01:34:41 +02:00
|
|
|
|
|
|
|
if p.tok != ',' {
|
|
|
|
// There was no comma, so the list is done.
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
p.accept(',')
|
|
|
|
}
|
|
|
|
|
2016-06-07 21:28:16 +02:00
|
|
|
rBracePos := p.scanner.Position
|
2014-05-28 01:34:41 +02:00
|
|
|
p.accept(']')
|
2016-06-07 21:28:16 +02:00
|
|
|
|
|
|
|
return &List{
|
|
|
|
LBracePos: lBracePos,
|
|
|
|
RBracePos: rBracePos,
|
|
|
|
Values: elements,
|
|
|
|
}
|
2014-05-28 01:34:41 +02:00
|
|
|
}
|
|
|
|
|
2016-06-07 21:28:16 +02:00
|
|
|
func (p *parser) parseMapValue() *Map {
|
|
|
|
lBracePos := p.scanner.Position
|
2014-07-26 02:01:20 +02:00
|
|
|
if !p.accept('{') {
|
2016-06-07 21:28:16 +02:00
|
|
|
return nil
|
2014-07-26 02:01:20 +02:00
|
|
|
}
|
|
|
|
|
2021-05-21 23:56:53 +02:00
|
|
|
var properties []*Property
|
|
|
|
var mapItems []*MapItem
|
|
|
|
// if the next item is an identifier, this is a property
|
|
|
|
if p.tok == scanner.Ident {
|
|
|
|
properties = p.parsePropertyList(false, false)
|
|
|
|
} else {
|
|
|
|
// otherwise, we assume that this is a map
|
|
|
|
mapItems = p.parseMapItemList()
|
|
|
|
}
|
2014-07-26 02:01:20 +02:00
|
|
|
|
2016-06-07 21:28:16 +02:00
|
|
|
rBracePos := p.scanner.Position
|
2014-07-26 02:01:20 +02:00
|
|
|
p.accept('}')
|
2015-07-06 21:29:57 +02:00
|
|
|
|
2016-06-07 21:28:16 +02:00
|
|
|
return &Map{
|
|
|
|
LBracePos: lBracePos,
|
|
|
|
RBracePos: rBracePos,
|
|
|
|
Properties: properties,
|
2021-05-21 23:56:53 +02:00
|
|
|
MapItems: mapItems,
|
2014-05-28 01:34:41 +02:00
|
|
|
}
|
|
|
|
}
|
2015-01-03 00:19:28 +01:00
|
|
|
|
|
|
|
type Scope struct {
|
2015-07-11 02:51:55 +02:00
|
|
|
vars map[string]*Assignment
|
|
|
|
inheritedVars map[string]*Assignment
|
2015-01-03 00:19:28 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func NewScope(s *Scope) *Scope {
|
|
|
|
newScope := &Scope{
|
2015-07-11 02:51:55 +02:00
|
|
|
vars: make(map[string]*Assignment),
|
|
|
|
inheritedVars: make(map[string]*Assignment),
|
2015-01-03 00:19:28 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if s != nil {
|
|
|
|
for k, v := range s.vars {
|
2015-07-11 02:51:55 +02:00
|
|
|
newScope.inheritedVars[k] = v
|
|
|
|
}
|
|
|
|
for k, v := range s.inheritedVars {
|
|
|
|
newScope.inheritedVars[k] = v
|
2015-01-03 00:19:28 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return newScope
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Scope) Add(assignment *Assignment) error {
|
2016-06-10 00:52:30 +02:00
|
|
|
if old, ok := s.vars[assignment.Name]; ok {
|
2015-01-03 00:19:28 +01:00
|
|
|
return fmt.Errorf("variable already set, previous assignment: %s", old)
|
|
|
|
}
|
|
|
|
|
2016-06-10 00:52:30 +02:00
|
|
|
if old, ok := s.inheritedVars[assignment.Name]; ok {
|
2015-07-11 02:51:55 +02:00
|
|
|
return fmt.Errorf("variable already set in inherited scope, previous assignment: %s", old)
|
|
|
|
}
|
|
|
|
|
2016-06-10 00:52:30 +02:00
|
|
|
s.vars[assignment.Name] = assignment
|
2015-01-03 00:19:28 +01:00
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Scope) Remove(name string) {
|
|
|
|
delete(s.vars, name)
|
2015-07-11 02:51:55 +02:00
|
|
|
delete(s.inheritedVars, name)
|
2015-01-03 00:19:28 +01:00
|
|
|
}
|
|
|
|
|
2015-07-11 02:51:55 +02:00
|
|
|
func (s *Scope) Get(name string) (*Assignment, bool) {
|
2015-01-03 00:19:28 +01:00
|
|
|
if a, ok := s.vars[name]; ok {
|
2015-07-11 02:51:55 +02:00
|
|
|
return a, true
|
2015-01-03 00:19:28 +01:00
|
|
|
}
|
|
|
|
|
2015-07-11 02:51:55 +02:00
|
|
|
if a, ok := s.inheritedVars[name]; ok {
|
|
|
|
return a, false
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil, false
|
2015-01-03 00:19:28 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Scope) String() string {
|
|
|
|
vars := []string{}
|
|
|
|
|
|
|
|
for k := range s.vars {
|
|
|
|
vars = append(vars, k)
|
|
|
|
}
|
2015-07-11 02:51:55 +02:00
|
|
|
for k := range s.inheritedVars {
|
|
|
|
vars = append(vars, k)
|
|
|
|
}
|
2015-01-03 00:19:28 +01:00
|
|
|
|
|
|
|
sort.Strings(vars)
|
|
|
|
|
|
|
|
ret := []string{}
|
|
|
|
for _, v := range vars {
|
2015-07-11 02:51:55 +02:00
|
|
|
if assignment, ok := s.vars[v]; ok {
|
|
|
|
ret = append(ret, assignment.String())
|
|
|
|
} else {
|
|
|
|
ret = append(ret, s.inheritedVars[v].String())
|
|
|
|
}
|
2015-01-03 00:19:28 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return strings.Join(ret, "\n")
|
|
|
|
}
|