From 684228e371e7cd71dd6839c733cd00994eb0a318 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 26 Feb 2015 11:32:39 -0800 Subject: [PATCH 1/6] config/lang: add math operations for ints --- config/lang/ast/arithmetic.go | 43 ++++++++++ config/lang/ast/arithmetic_op.go | 12 +++ config/lang/check_types.go | 50 ++++++++++- config/lang/eval.go | 65 +++++++++++++++ config/lang/eval_test.go | 32 +++++++ config/lang/lang.y | 29 ++++++- config/lang/lex.go | 12 +++ config/lang/lex_test.go | 9 ++ config/lang/parse_test.go | 31 +++++++ config/lang/y.go | 93 +++++++++++++-------- config/lang/y.output | 138 ++++++++++++++++++------------- 11 files changed, 421 insertions(+), 93 deletions(-) create mode 100644 config/lang/ast/arithmetic.go create mode 100644 config/lang/ast/arithmetic_op.go diff --git a/config/lang/ast/arithmetic.go b/config/lang/ast/arithmetic.go new file mode 100644 index 000000000..94dc24f89 --- /dev/null +++ b/config/lang/ast/arithmetic.go @@ -0,0 +1,43 @@ +package ast + +import ( + "bytes" + "fmt" +) + +// Arithmetic represents a node where the result is arithmetic of +// two or more operands in the order given. +type Arithmetic struct { + Op ArithmeticOp + Exprs []Node + Posx Pos +} + +func (n *Arithmetic) Accept(v Visitor) Node { + for i, expr := range n.Exprs { + n.Exprs[i] = expr.Accept(v) + } + + return v(n) +} + +func (n *Arithmetic) Pos() Pos { + return n.Posx +} + +func (n *Arithmetic) GoString() string { + return fmt.Sprintf("*%#v", *n) +} + +func (n *Arithmetic) String() string { + var b bytes.Buffer + for _, expr := range n.Exprs { + b.WriteString(fmt.Sprintf("%s", expr)) + } + + return b.String() +} + +func (n *Arithmetic) Type(Scope) (Type, error) { + return TypeInt, nil +} diff --git a/config/lang/ast/arithmetic_op.go b/config/lang/ast/arithmetic_op.go new file mode 100644 index 000000000..920b1d56e --- /dev/null +++ b/config/lang/ast/arithmetic_op.go @@ -0,0 +1,12 @@ +package ast + +// ArithmeticOp is the operation to use for the math. +type ArithmeticOp int + +const ( + ArithmeticOpInvalid ArithmeticOp = 0 + ArithmeticOpAdd ArithmeticOp = iota + ArithmeticOpSub + ArithmeticOpMul + ArithmeticOpDiv +) diff --git a/config/lang/check_types.go b/config/lang/check_types.go index 0165eadfc..c153055de 100644 --- a/config/lang/check_types.go +++ b/config/lang/check_types.go @@ -55,6 +55,9 @@ func (v *TypeCheck) visit(raw ast.Node) ast.Node { var result ast.Node var err error switch n := raw.(type) { + case *ast.Arithmetic: + tc := &typeCheckArithmetic{n} + result, err = tc.TypeCheck(v) case *ast.Call: tc := &typeCheckCall{n} result, err = tc.TypeCheck(v) @@ -70,7 +73,7 @@ func (v *TypeCheck) visit(raw ast.Node) ast.Node { default: tc, ok := raw.(TypeCheckNode) if !ok { - err = fmt.Errorf("unknown node: %#v", raw) + err = fmt.Errorf("unknown node for type check: %#v", raw) break } @@ -86,6 +89,51 @@ func (v *TypeCheck) visit(raw ast.Node) ast.Node { return result } +type typeCheckArithmetic struct { + n *ast.Arithmetic +} + +func (tc *typeCheckArithmetic) TypeCheck(v *TypeCheck) (ast.Node, error) { + // The arguments are on the stack in reverse order, so pop them off. + args := make([]ast.Type, len(tc.n.Exprs)) + for i, _ := range tc.n.Exprs { + args[len(tc.n.Exprs)-1-i] = v.StackPop() + } + + // Determine the resulting type we want + mathType := ast.TypeInt + switch v := args[0]; v { + case ast.TypeInt: + fallthrough + case ast.TypeFloat: + mathType = v + default: + return nil, fmt.Errorf( + "Math operations can only be done with ints and floats, got %s", + v) + } + + // Verify the args + for i, arg := range args { + if arg != mathType { + cn := v.ImplicitConversion(args[i], mathType, tc.n.Exprs[i]) + if cn != nil { + tc.n.Exprs[i] = cn + continue + } + + return nil, fmt.Errorf( + "operand %d should be %s, got %s", + i+1, mathType, arg) + } + } + + // Return type + v.StackPush(mathType) + + return tc.n, nil +} + type typeCheckCall struct { n *ast.Call } diff --git a/config/lang/eval.go b/config/lang/eval.go index cc4d756df..d66563c27 100644 --- a/config/lang/eval.go +++ b/config/lang/eval.go @@ -134,6 +134,8 @@ func (v *evalVisitor) visit(raw ast.Node) ast.Node { // types as well as any other EvalNode implementations. func evalNode(raw ast.Node) (EvalNode, error) { switch n := raw.(type) { + case *ast.Arithmetic: + return &evalArithmetic{n}, nil case *ast.Call: return &evalCall{n}, nil case *ast.Concat: @@ -152,6 +154,69 @@ func evalNode(raw ast.Node) (EvalNode, error) { } } +type evalArithmetic struct{ *ast.Arithmetic } + +func (v *evalArithmetic) Eval( + s ast.Scope, stack *ast.Stack) (interface{}, ast.Type, error) { + // The arguments are on the stack in reverse order, so pop them off. + var resultType ast.Type + exprs := make([]interface{}, len(v.Exprs)) + for i, _ := range v.Exprs { + node := stack.Pop().(*ast.LiteralNode) + exprs[len(v.Exprs)-1-i] = node.Value + resultType = node.Typex + } + + // Do the operation + var result interface{} + var err error + switch resultType { + case ast.TypeInt: + result, err = v.evalInt(v.Op, exprs) + default: + return nil, resultType, fmt.Errorf( + "unknown math operand type: %s", resultType) + } + + return result, resultType, err +} + +func (v *evalArithmetic) evalInt( + op ast.ArithmeticOp, exprs []interface{}) (int, error) { + switch v.Op { + case ast.ArithmeticOpAdd: + result := 0 + for _, expr := range exprs { + result += expr.(int) + } + + return result, nil + case ast.ArithmeticOpSub: + result := exprs[0].(int) + for _, expr := range exprs[1:] { + result -= expr.(int) + } + + return result, nil + case ast.ArithmeticOpMul: + result := exprs[0].(int) + for _, expr := range exprs[1:] { + result *= expr.(int) + } + + return result, nil + case ast.ArithmeticOpDiv: + result := exprs[0].(int) + for _, expr := range exprs[1:] { + result /= expr.(int) + } + + return result, nil + default: + return 0, fmt.Errorf("unknown math operation: %s", v.Op) + } +} + type evalCall struct{ *ast.Call } func (v *evalCall) Eval(s ast.Scope, stack *ast.Stack) (interface{}, ast.Type, error) { diff --git a/config/lang/eval_test.go b/config/lang/eval_test.go index bf66be79f..e0c53db83 100644 --- a/config/lang/eval_test.go +++ b/config/lang/eval_test.go @@ -39,6 +39,38 @@ func TestEval(t *testing.T) { ast.TypeString, }, + { + "foo ${42+1}", + nil, + false, + "foo 43", + ast.TypeString, + }, + + { + "foo ${42-1}", + nil, + false, + "foo 41", + ast.TypeString, + }, + + { + "foo ${42*2}", + nil, + false, + "foo 84", + ast.TypeString, + }, + + { + "foo ${42/2}", + nil, + false, + "foo 21", + ast.TypeString, + }, + { "foo ${rand()}", &ast.BasicScope{ diff --git a/config/lang/lang.y b/config/lang/lang.y index 4ee47fb54..68c7d0b37 100644 --- a/config/lang/lang.y +++ b/config/lang/lang.y @@ -22,9 +22,9 @@ import ( %token PROGRAM_STRING_START PROGRAM_STRING_END %token PAREN_LEFT PAREN_RIGHT COMMA -%token IDENTIFIER INTEGER FLOAT STRING +%token ARITH_OP IDENTIFIER INTEGER FLOAT STRING -%type expr interpolation literal literalModeTop literalModeValue +%type arith expr interpolation literal literalModeTop literalModeValue %type args %% @@ -116,6 +116,10 @@ expr: Posx: $1.Pos, } } +| arith + { + $$ = $1 + } | IDENTIFIER { $$ = &ast.VariableAccess{Name: $1.Value.(string), Posx: $1.Pos} @@ -125,6 +129,27 @@ expr: $$ = &ast.Call{Func: $1.Value.(string), Args: $3, Posx: $1.Pos} } +arith: + INTEGER ARITH_OP INTEGER + { + $$ = &ast.Arithmetic{ + Op: $2.Value.(ast.ArithmeticOp), + Exprs: []ast.Node{ + &ast.LiteralNode{ + Value: $1.Value.(int), + Typex: ast.TypeInt, + Posx: $1.Pos, + }, + &ast.LiteralNode{ + Value: $3.Value.(int), + Typex: ast.TypeInt, + Posx: $3.Pos, + }, + }, + Posx: $1.Pos, + } + } + args: { $$ = nil diff --git a/config/lang/lex.go b/config/lang/lex.go index a1172b420..6b30f2a4c 100644 --- a/config/lang/lex.go +++ b/config/lang/lex.go @@ -168,6 +168,18 @@ func (x *parserLex) lexModeInterpolation(yylval *parserSymType) int { return PAREN_RIGHT case ',': return COMMA + case '+': + yylval.token = &parserToken{Value: ast.ArithmeticOpAdd} + return ARITH_OP + case '-': + yylval.token = &parserToken{Value: ast.ArithmeticOpSub} + return ARITH_OP + case '*': + yylval.token = &parserToken{Value: ast.ArithmeticOpMul} + return ARITH_OP + case '/': + yylval.token = &parserToken{Value: ast.ArithmeticOpDiv} + return ARITH_OP default: x.backup() return x.lexId(yylval) diff --git a/config/lang/lex_test.go b/config/lang/lex_test.go index 8c5207d78..5341e594a 100644 --- a/config/lang/lex_test.go +++ b/config/lang/lex_test.go @@ -63,6 +63,15 @@ func TestLex(t *testing.T) { PROGRAM_BRACKET_RIGHT, lexEOF}, }, + { + "${bar(42+1)}", + []int{PROGRAM_BRACKET_LEFT, + IDENTIFIER, PAREN_LEFT, + INTEGER, ARITH_OP, INTEGER, + PAREN_RIGHT, + PROGRAM_BRACKET_RIGHT, lexEOF}, + }, + { "${bar(3.14159)}", []int{PROGRAM_BRACKET_LEFT, diff --git a/config/lang/parse_test.go b/config/lang/parse_test.go index e2ba2b310..d2671e233 100644 --- a/config/lang/parse_test.go +++ b/config/lang/parse_test.go @@ -152,6 +152,37 @@ func TestParse(t *testing.T) { }, }, + { + "foo ${42+1}", + false, + &ast.Concat{ + Posx: ast.Pos{Column: 1, Line: 1}, + Exprs: []ast.Node{ + &ast.LiteralNode{ + Value: "foo ", + Typex: ast.TypeString, + Posx: ast.Pos{Column: 1, Line: 1}, + }, + &ast.Arithmetic{ + Op: ast.ArithmeticOpAdd, + Exprs: []ast.Node{ + &ast.LiteralNode{ + Value: 42, + Typex: ast.TypeInt, + Posx: ast.Pos{Column: 7, Line: 1}, + }, + &ast.LiteralNode{ + Value: 1, + Typex: ast.TypeInt, + Posx: ast.Pos{Column: 10, Line: 1}, + }, + }, + Posx: ast.Pos{Column: 7, Line: 1}, + }, + }, + }, + }, + { "${foo()}", false, diff --git a/config/lang/y.go b/config/lang/y.go index de1ce7501..bb56ebdc0 100644 --- a/config/lang/y.go +++ b/config/lang/y.go @@ -24,10 +24,11 @@ const PROGRAM_STRING_END = 57349 const PAREN_LEFT = 57350 const PAREN_RIGHT = 57351 const COMMA = 57352 -const IDENTIFIER = 57353 -const INTEGER = 57354 -const FLOAT = 57355 -const STRING = 57356 +const ARITH_OP = 57353 +const IDENTIFIER = 57354 +const INTEGER = 57355 +const FLOAT = 57356 +const STRING = 57357 var parserToknames = []string{ "PROGRAM_BRACKET_LEFT", @@ -37,6 +38,7 @@ var parserToknames = []string{ "PAREN_LEFT", "PAREN_RIGHT", "COMMA", + "ARITH_OP", "IDENTIFIER", "INTEGER", "FLOAT", @@ -48,7 +50,7 @@ const parserEofCode = 1 const parserErrCode = 2 const parserMaxDepth = 200 -//line lang.y:151 +//line lang.y:176 //line yacctab:1 var parserExca = []int{ @@ -57,51 +59,51 @@ var parserExca = []int{ -2, 0, } -const parserNprod = 17 +const parserNprod = 19 const parserPrivate = 57344 var parserTokenNames []string var parserStates []string -const parserLast = 23 +const parserLast = 26 var parserAct = []int{ - 9, 7, 7, 3, 18, 19, 8, 15, 13, 11, - 12, 6, 6, 14, 8, 1, 17, 10, 2, 16, - 20, 4, 5, + 9, 7, 7, 18, 3, 21, 22, 8, 17, 14, + 11, 12, 6, 6, 16, 8, 15, 1, 20, 10, + 2, 19, 4, 23, 5, 13, } var parserPact = []int{ - -2, -1000, -2, -1000, -1000, -1000, -1000, -3, -1000, 8, - -2, -1000, -1000, -1, -1000, -3, -5, -1000, -1000, -3, - -1000, + -2, -1000, -2, -1000, -1000, -1000, -1000, -3, -1000, 11, + -2, 3, -1000, -1000, 0, -1000, -10, -3, -1000, -4, + -1000, -1000, -3, -1000, } var parserPgo = []int{ - 0, 0, 22, 21, 17, 3, 19, 15, + 0, 25, 0, 24, 22, 19, 4, 21, 17, } var parserR1 = []int{ - 0, 7, 7, 4, 4, 5, 5, 2, 1, 1, - 1, 1, 1, 6, 6, 6, 3, + 0, 8, 8, 5, 5, 6, 6, 3, 2, 2, + 2, 2, 2, 2, 1, 7, 7, 7, 4, } var parserR2 = []int{ 0, 0, 1, 1, 2, 1, 1, 3, 1, 1, - 1, 1, 4, 0, 3, 1, 1, + 1, 1, 1, 4, 3, 0, 3, 1, 1, } var parserChk = []int{ - -1000, -7, -4, -5, -3, -2, 14, 4, -5, -1, - -4, 12, 13, 11, 5, 8, -6, -1, 9, 10, - -1, + -1000, -8, -5, -6, -4, -3, 15, 4, -6, -2, + -5, 13, 14, -1, 12, 5, 11, 8, 13, -7, + -2, 9, 10, -2, } var parserDef = []int{ - 1, -2, 2, 3, 5, 6, 16, 0, 4, 0, - 8, 9, 10, 11, 7, 13, 0, 15, 12, 0, - 14, + 1, -2, 2, 3, 5, 6, 18, 0, 4, 0, + 8, 9, 10, 11, 12, 7, 0, 15, 14, 0, + 17, 13, 0, 16, } var parserTok1 = []int{ @@ -110,7 +112,7 @@ var parserTok1 = []int{ var parserTok2 = []int{ 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, - 12, 13, 14, + 12, 13, 14, 15, } var parserTok3 = []int{ 0, @@ -433,30 +435,55 @@ parserdefault: case 11: //line lang.y:120 { - parserVAL.node = &ast.VariableAccess{Name: parserS[parserpt-0].token.Value.(string), Posx: parserS[parserpt-0].token.Pos} + parserVAL.node = parserS[parserpt-0].node } case 12: //line lang.y:124 { - parserVAL.node = &ast.Call{Func: parserS[parserpt-3].token.Value.(string), Args: parserS[parserpt-1].nodeList, Posx: parserS[parserpt-3].token.Pos} + parserVAL.node = &ast.VariableAccess{Name: parserS[parserpt-0].token.Value.(string), Posx: parserS[parserpt-0].token.Pos} } case 13: - //line lang.y:129 + //line lang.y:128 + { + parserVAL.node = &ast.Call{Func: parserS[parserpt-3].token.Value.(string), Args: parserS[parserpt-1].nodeList, Posx: parserS[parserpt-3].token.Pos} + } + case 14: + //line lang.y:134 + { + parserVAL.node = &ast.Arithmetic{ + Op: parserS[parserpt-1].token.Value.(ast.ArithmeticOp), + Exprs: []ast.Node{ + &ast.LiteralNode{ + Value: parserS[parserpt-2].token.Value.(int), + Typex: ast.TypeInt, + Posx: parserS[parserpt-2].token.Pos, + }, + &ast.LiteralNode{ + Value: parserS[parserpt-0].token.Value.(int), + Typex: ast.TypeInt, + Posx: parserS[parserpt-0].token.Pos, + }, + }, + Posx: parserS[parserpt-2].token.Pos, + } + } + case 15: + //line lang.y:154 { parserVAL.nodeList = nil } - case 14: - //line lang.y:133 + case 16: + //line lang.y:158 { parserVAL.nodeList = append(parserS[parserpt-2].nodeList, parserS[parserpt-0].node) } - case 15: - //line lang.y:137 + case 17: + //line lang.y:162 { parserVAL.nodeList = append(parserVAL.nodeList, parserS[parserpt-0].node) } - case 16: - //line lang.y:143 + case 18: + //line lang.y:168 { parserVAL.node = &ast.LiteralNode{ Value: parserS[parserpt-0].token.Value.(string), diff --git a/config/lang/y.output b/config/lang/y.output index aa076e45a..c1f9df4a0 100644 --- a/config/lang/y.output +++ b/config/lang/y.output @@ -51,21 +51,22 @@ state 5 state 6 - literal: STRING. (16) + literal: STRING. (18) - . reduce 16 (src line 141) + . reduce 18 (src line 166) state 7 interpolation: PROGRAM_BRACKET_LEFT.expr PROGRAM_BRACKET_RIGHT PROGRAM_BRACKET_LEFT shift 7 - IDENTIFIER shift 13 + IDENTIFIER shift 14 INTEGER shift 11 FLOAT shift 12 STRING shift 6 . error + arith goto 13 expr goto 9 interpolation goto 5 literal goto 4 @@ -81,7 +82,7 @@ state 8 state 9 interpolation: PROGRAM_BRACKET_LEFT expr.PROGRAM_BRACKET_RIGHT - PROGRAM_BRACKET_RIGHT shift 14 + PROGRAM_BRACKET_RIGHT shift 15 . error @@ -99,7 +100,9 @@ state 10 state 11 expr: INTEGER. (9) + arith: INTEGER.ARITH_OP INTEGER + ARITH_OP shift 16 . reduce 9 (src line 103) @@ -110,89 +113,110 @@ state 12 state 13 - expr: IDENTIFIER. (11) - expr: IDENTIFIER.PAREN_LEFT args PAREN_RIGHT + expr: arith. (11) - PAREN_LEFT shift 15 . reduce 11 (src line 119) state 14 + expr: IDENTIFIER. (12) + expr: IDENTIFIER.PAREN_LEFT args PAREN_RIGHT + + PAREN_LEFT shift 17 + . reduce 12 (src line 123) + + +state 15 interpolation: PROGRAM_BRACKET_LEFT expr PROGRAM_BRACKET_RIGHT. (7) . reduce 7 (src line 92) -state 15 - expr: IDENTIFIER PAREN_LEFT.args PAREN_RIGHT - args: . (13) - - PROGRAM_BRACKET_LEFT shift 7 - IDENTIFIER shift 13 - INTEGER shift 11 - FLOAT shift 12 - STRING shift 6 - . reduce 13 (src line 128) - - expr goto 17 - interpolation goto 5 - literal goto 4 - literalModeTop goto 10 - literalModeValue goto 3 - args goto 16 - state 16 - expr: IDENTIFIER PAREN_LEFT args.PAREN_RIGHT - args: args.COMMA expr + arith: INTEGER ARITH_OP.INTEGER - PAREN_RIGHT shift 18 - COMMA shift 19 + INTEGER shift 18 . error state 17 - args: expr. (15) - - . reduce 15 (src line 136) - - -state 18 - expr: IDENTIFIER PAREN_LEFT args PAREN_RIGHT. (12) - - . reduce 12 (src line 123) - - -state 19 - args: args COMMA.expr + expr: IDENTIFIER PAREN_LEFT.args PAREN_RIGHT + args: . (15) PROGRAM_BRACKET_LEFT shift 7 - IDENTIFIER shift 13 + IDENTIFIER shift 14 INTEGER shift 11 FLOAT shift 12 STRING shift 6 - . error + . reduce 15 (src line 153) + arith goto 13 expr goto 20 interpolation goto 5 literal goto 4 literalModeTop goto 10 literalModeValue goto 3 + args goto 19 -state 20 - args: args COMMA expr. (14) +state 18 + arith: INTEGER ARITH_OP INTEGER. (14) . reduce 14 (src line 132) -14 terminals, 8 nonterminals -17 grammar rules, 21/2000 states +state 19 + expr: IDENTIFIER PAREN_LEFT args.PAREN_RIGHT + args: args.COMMA expr + + PAREN_RIGHT shift 21 + COMMA shift 22 + . error + + +state 20 + args: expr. (17) + + . reduce 17 (src line 161) + + +state 21 + expr: IDENTIFIER PAREN_LEFT args PAREN_RIGHT. (13) + + . reduce 13 (src line 127) + + +state 22 + args: args COMMA.expr + + PROGRAM_BRACKET_LEFT shift 7 + IDENTIFIER shift 14 + INTEGER shift 11 + FLOAT shift 12 + STRING shift 6 + . error + + arith goto 13 + expr goto 23 + interpolation goto 5 + literal goto 4 + literalModeTop goto 10 + literalModeValue goto 3 + +state 23 + args: args COMMA expr. (16) + + . reduce 16 (src line 157) + + +15 terminals, 9 nonterminals +19 grammar rules, 24/2000 states 0 shift/reduce, 0 reduce/reduce conflicts reported -57 working sets used -memory: parser 25/30000 -16 extra closures -25 shift entries, 1 exceptions -12 goto entries -15 entries saved by goto default -Optimizer space used: output 23/30000 -23 table entries, 0 zero -maximum spread: 14, maximum offset: 19 +58 working sets used +memory: parser 28/30000 +19 extra closures +27 shift entries, 1 exceptions +13 goto entries +17 entries saved by goto default +Optimizer space used: output 26/30000 +26 table entries, 0 zero +maximum spread: 15, maximum offset: 22 From 07b78fdf84a6537b63202790871aefadadd50395 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 26 Feb 2015 14:26:14 -0800 Subject: [PATCH 2/6] config/lang: math operations --- config/lang/builtins.go | 96 ++++++++++++++++++++++++++ config/lang/check_types.go | 30 +++++++-- config/lang/eval.go | 73 ++------------------ config/lang/eval_test.go | 24 +++++++ config/lang/lang.y | 31 ++------- config/lang/y.go | 80 +++++++++------------- config/lang/y.output | 133 +++++++++++++++++++------------------ 7 files changed, 259 insertions(+), 208 deletions(-) diff --git a/config/lang/builtins.go b/config/lang/builtins.go index e8513119f..e14aba0d8 100644 --- a/config/lang/builtins.go +++ b/config/lang/builtins.go @@ -15,11 +15,107 @@ func registerBuiltins(scope *ast.BasicScope) *ast.BasicScope { if scope.FuncMap == nil { scope.FuncMap = make(map[string]ast.Function) } + + // Implicit conversions + scope.FuncMap["__builtin_FloatToInt"] = builtinFloatToInt() + scope.FuncMap["__builtin_FloatToString"] = builtinFloatToString() + scope.FuncMap["__builtin_IntToFloat"] = builtinIntToFloat() scope.FuncMap["__builtin_IntToString"] = builtinIntToString() scope.FuncMap["__builtin_StringToInt"] = builtinStringToInt() + + // Math operations + scope.FuncMap["__builtin_IntMath"] = builtinIntMath() + scope.FuncMap["__builtin_FloatMath"] = builtinFloatMath() return scope } +func builtinFloatMath() ast.Function { + return ast.Function{ + ArgTypes: []ast.Type{ast.TypeInt}, + Variadic: true, + VariadicType: ast.TypeFloat, + ReturnType: ast.TypeFloat, + Callback: func(args []interface{}) (interface{}, error) { + op := args[0].(ast.ArithmeticOp) + result := args[1].(float64) + for _, raw := range args[2:] { + arg := raw.(float64) + switch op { + case ast.ArithmeticOpAdd: + result += arg + case ast.ArithmeticOpSub: + result -= arg + case ast.ArithmeticOpMul: + result *= arg + case ast.ArithmeticOpDiv: + result /= arg + } + } + + return result, nil + }, + } +} + +func builtinIntMath() ast.Function { + return ast.Function{ + ArgTypes: []ast.Type{ast.TypeInt}, + Variadic: true, + VariadicType: ast.TypeInt, + ReturnType: ast.TypeInt, + Callback: func(args []interface{}) (interface{}, error) { + op := args[0].(ast.ArithmeticOp) + result := args[1].(int) + for _, raw := range args[2:] { + arg := raw.(int) + switch op { + case ast.ArithmeticOpAdd: + result += arg + case ast.ArithmeticOpSub: + result -= arg + case ast.ArithmeticOpMul: + result *= arg + case ast.ArithmeticOpDiv: + result /= arg + } + } + + return result, nil + }, + } +} + +func builtinFloatToInt() ast.Function { + return ast.Function{ + ArgTypes: []ast.Type{ast.TypeFloat}, + ReturnType: ast.TypeInt, + Callback: func(args []interface{}) (interface{}, error) { + return int(args[0].(float64)), nil + }, + } +} + +func builtinFloatToString() ast.Function { + return ast.Function{ + ArgTypes: []ast.Type{ast.TypeFloat}, + ReturnType: ast.TypeString, + Callback: func(args []interface{}) (interface{}, error) { + return strconv.FormatFloat( + args[0].(float64), 'g', -1, 64), nil + }, + } +} + +func builtinIntToFloat() ast.Function { + return ast.Function{ + ArgTypes: []ast.Type{ast.TypeInt}, + ReturnType: ast.TypeFloat, + Callback: func(args []interface{}) (interface{}, error) { + return float64(args[0].(int)), nil + }, + } +} + func builtinIntToString() ast.Function { return ast.Function{ ArgTypes: []ast.Type{ast.TypeInt}, diff --git a/config/lang/check_types.go b/config/lang/check_types.go index c153055de..3b6eefbf0 100644 --- a/config/lang/check_types.go +++ b/config/lang/check_types.go @@ -95,17 +95,20 @@ type typeCheckArithmetic struct { func (tc *typeCheckArithmetic) TypeCheck(v *TypeCheck) (ast.Node, error) { // The arguments are on the stack in reverse order, so pop them off. - args := make([]ast.Type, len(tc.n.Exprs)) + exprs := make([]ast.Type, len(tc.n.Exprs)) for i, _ := range tc.n.Exprs { - args[len(tc.n.Exprs)-1-i] = v.StackPop() + exprs[len(tc.n.Exprs)-1-i] = v.StackPop() } // Determine the resulting type we want + mathFunc := "__builtin_IntMath" mathType := ast.TypeInt - switch v := args[0]; v { + switch v := exprs[0]; v { case ast.TypeInt: - fallthrough + mathFunc = "__builtin_IntMath" + mathType = v case ast.TypeFloat: + mathFunc = "__builtin_FloatMath" mathType = v default: return nil, fmt.Errorf( @@ -114,9 +117,9 @@ func (tc *typeCheckArithmetic) TypeCheck(v *TypeCheck) (ast.Node, error) { } // Verify the args - for i, arg := range args { + for i, arg := range exprs { if arg != mathType { - cn := v.ImplicitConversion(args[i], mathType, tc.n.Exprs[i]) + cn := v.ImplicitConversion(exprs[i], mathType, tc.n.Exprs[i]) if cn != nil { tc.n.Exprs[i] = cn continue @@ -131,7 +134,20 @@ func (tc *typeCheckArithmetic) TypeCheck(v *TypeCheck) (ast.Node, error) { // Return type v.StackPush(mathType) - return tc.n, nil + // Replace our node with a call to the proper function. This isn't + // type checked but we already verified types. + args := make([]ast.Node, len(tc.n.Exprs)+1) + args[0] = &ast.LiteralNode{ + Value: tc.n.Op, + Typex: ast.TypeInt, + Posx: tc.n.Pos(), + } + copy(args[1:], tc.n.Exprs) + return &ast.Call{ + Func: mathFunc, + Args: args, + Posx: tc.n.Pos(), + }, nil } type typeCheckCall struct { diff --git a/config/lang/eval.go b/config/lang/eval.go index d66563c27..9ec583e41 100644 --- a/config/lang/eval.go +++ b/config/lang/eval.go @@ -32,7 +32,12 @@ func Eval(root ast.Node, config *EvalConfig) (interface{}, ast.Type, error) { } scope := registerBuiltins(config.GlobalScope) implicitMap := map[ast.Type]map[ast.Type]string{ + ast.TypeFloat: { + ast.TypeInt: "__builtin_FloatToInt", + ast.TypeString: "__builtin_FloatToString", + }, ast.TypeInt: { + ast.TypeFloat: "__builtin_IntToFloat", ast.TypeString: "__builtin_IntToString", }, ast.TypeString: { @@ -47,7 +52,8 @@ func Eval(root ast.Node, config *EvalConfig) (interface{}, ast.Type, error) { // Build up the semantic checks for execution checks := make( []SemanticChecker, - len(config.SemanticChecks), len(config.SemanticChecks)+2) + len(config.SemanticChecks), + len(config.SemanticChecks)+2) copy(checks, config.SemanticChecks) checks = append(checks, ic.Visit) checks = append(checks, tv.Visit) @@ -134,8 +140,6 @@ func (v *evalVisitor) visit(raw ast.Node) ast.Node { // types as well as any other EvalNode implementations. func evalNode(raw ast.Node) (EvalNode, error) { switch n := raw.(type) { - case *ast.Arithmetic: - return &evalArithmetic{n}, nil case *ast.Call: return &evalCall{n}, nil case *ast.Concat: @@ -154,69 +158,6 @@ func evalNode(raw ast.Node) (EvalNode, error) { } } -type evalArithmetic struct{ *ast.Arithmetic } - -func (v *evalArithmetic) Eval( - s ast.Scope, stack *ast.Stack) (interface{}, ast.Type, error) { - // The arguments are on the stack in reverse order, so pop them off. - var resultType ast.Type - exprs := make([]interface{}, len(v.Exprs)) - for i, _ := range v.Exprs { - node := stack.Pop().(*ast.LiteralNode) - exprs[len(v.Exprs)-1-i] = node.Value - resultType = node.Typex - } - - // Do the operation - var result interface{} - var err error - switch resultType { - case ast.TypeInt: - result, err = v.evalInt(v.Op, exprs) - default: - return nil, resultType, fmt.Errorf( - "unknown math operand type: %s", resultType) - } - - return result, resultType, err -} - -func (v *evalArithmetic) evalInt( - op ast.ArithmeticOp, exprs []interface{}) (int, error) { - switch v.Op { - case ast.ArithmeticOpAdd: - result := 0 - for _, expr := range exprs { - result += expr.(int) - } - - return result, nil - case ast.ArithmeticOpSub: - result := exprs[0].(int) - for _, expr := range exprs[1:] { - result -= expr.(int) - } - - return result, nil - case ast.ArithmeticOpMul: - result := exprs[0].(int) - for _, expr := range exprs[1:] { - result *= expr.(int) - } - - return result, nil - case ast.ArithmeticOpDiv: - result := exprs[0].(int) - for _, expr := range exprs[1:] { - result /= expr.(int) - } - - return result, nil - default: - return 0, fmt.Errorf("unknown math operation: %s", v.Op) - } -} - type evalCall struct{ *ast.Call } func (v *evalCall) Eval(s ast.Scope, stack *ast.Stack) (interface{}, ast.Type, error) { diff --git a/config/lang/eval_test.go b/config/lang/eval_test.go index e0c53db83..3446be9d2 100644 --- a/config/lang/eval_test.go +++ b/config/lang/eval_test.go @@ -71,6 +71,30 @@ func TestEval(t *testing.T) { ast.TypeString, }, + { + "foo ${42.0+1.0}", + nil, + false, + "foo 43", + ast.TypeString, + }, + + { + "foo ${42.0+1}", + nil, + false, + "foo 43", + ast.TypeString, + }, + + { + "foo ${42+1.0}", + nil, + false, + "foo 43", + ast.TypeString, + }, + { "foo ${rand()}", &ast.BasicScope{ diff --git a/config/lang/lang.y b/config/lang/lang.y index 68c7d0b37..96517a76e 100644 --- a/config/lang/lang.y +++ b/config/lang/lang.y @@ -24,7 +24,7 @@ import ( %token ARITH_OP IDENTIFIER INTEGER FLOAT STRING -%type arith expr interpolation literal literalModeTop literalModeValue +%type expr interpolation literal literalModeTop literalModeValue %type args %% @@ -116,9 +116,13 @@ expr: Posx: $1.Pos, } } -| arith +| expr ARITH_OP expr { - $$ = $1 + $$ = &ast.Arithmetic{ + Op: $2.Value.(ast.ArithmeticOp), + Exprs: []ast.Node{$1, $3}, + Posx: $1.Pos(), + } } | IDENTIFIER { @@ -129,27 +133,6 @@ expr: $$ = &ast.Call{Func: $1.Value.(string), Args: $3, Posx: $1.Pos} } -arith: - INTEGER ARITH_OP INTEGER - { - $$ = &ast.Arithmetic{ - Op: $2.Value.(ast.ArithmeticOp), - Exprs: []ast.Node{ - &ast.LiteralNode{ - Value: $1.Value.(int), - Typex: ast.TypeInt, - Posx: $1.Pos, - }, - &ast.LiteralNode{ - Value: $3.Value.(int), - Typex: ast.TypeInt, - Posx: $3.Pos, - }, - }, - Posx: $1.Pos, - } - } - args: { $$ = nil diff --git a/config/lang/y.go b/config/lang/y.go index bb56ebdc0..f9198d8f7 100644 --- a/config/lang/y.go +++ b/config/lang/y.go @@ -50,7 +50,7 @@ const parserEofCode = 1 const parserErrCode = 2 const parserMaxDepth = 200 -//line lang.y:176 +//line lang.y:159 //line yacctab:1 var parserExca = []int{ @@ -59,7 +59,7 @@ var parserExca = []int{ -2, 0, } -const parserNprod = 19 +const parserNprod = 18 const parserPrivate = 57344 var parserTokenNames []string @@ -69,41 +69,41 @@ const parserLast = 26 var parserAct = []int{ - 9, 7, 7, 18, 3, 21, 22, 8, 17, 14, - 11, 12, 6, 6, 16, 8, 15, 1, 20, 10, - 2, 19, 4, 23, 5, 13, + 9, 7, 7, 15, 3, 20, 21, 8, 14, 13, + 11, 12, 6, 6, 15, 8, 17, 19, 16, 10, + 2, 1, 22, 18, 4, 5, } var parserPact = []int{ - -2, -1000, -2, -1000, -1000, -1000, -1000, -3, -1000, 11, - -2, 3, -1000, -1000, 0, -1000, -10, -3, -1000, -4, - -1000, -1000, -3, -1000, + -2, -1000, -2, -1000, -1000, -1000, -1000, -3, -1000, 3, + -2, -1000, -1000, 10, -1000, -3, -3, -8, -4, -8, + -1000, -3, -8, } var parserPgo = []int{ - 0, 25, 0, 24, 22, 19, 4, 21, 17, + 0, 0, 25, 24, 19, 4, 23, 21, } var parserR1 = []int{ - 0, 8, 8, 5, 5, 6, 6, 3, 2, 2, - 2, 2, 2, 2, 1, 7, 7, 7, 4, + 0, 7, 7, 4, 4, 5, 5, 2, 1, 1, + 1, 1, 1, 1, 6, 6, 6, 3, } var parserR2 = []int{ 0, 0, 1, 1, 2, 1, 1, 3, 1, 1, - 1, 1, 1, 4, 3, 0, 3, 1, 1, + 1, 3, 1, 4, 0, 3, 1, 1, } var parserChk = []int{ - -1000, -8, -5, -6, -4, -3, 15, 4, -6, -2, - -5, 13, 14, -1, 12, 5, 11, 8, 13, -7, - -2, 9, 10, -2, + -1000, -7, -4, -5, -3, -2, 15, 4, -5, -1, + -4, 13, 14, 12, 5, 11, 8, -1, -6, -1, + 9, 10, -1, } var parserDef = []int{ - 1, -2, 2, 3, 5, 6, 18, 0, 4, 0, - 8, 9, 10, 11, 12, 7, 0, 15, 14, 0, - 17, 13, 0, 16, + 1, -2, 2, 3, 5, 6, 17, 0, 4, 0, + 8, 9, 10, 12, 7, 0, 14, 11, 0, 16, + 13, 0, 15, } var parserTok1 = []int{ @@ -435,55 +435,39 @@ parserdefault: case 11: //line lang.y:120 { - parserVAL.node = parserS[parserpt-0].node + parserVAL.node = &ast.Arithmetic{ + Op: parserS[parserpt-1].token.Value.(ast.ArithmeticOp), + Exprs: []ast.Node{parserS[parserpt-2].node, parserS[parserpt-0].node}, + Posx: parserS[parserpt-2].node.Pos(), + } } case 12: - //line lang.y:124 + //line lang.y:128 { parserVAL.node = &ast.VariableAccess{Name: parserS[parserpt-0].token.Value.(string), Posx: parserS[parserpt-0].token.Pos} } case 13: - //line lang.y:128 + //line lang.y:132 { parserVAL.node = &ast.Call{Func: parserS[parserpt-3].token.Value.(string), Args: parserS[parserpt-1].nodeList, Posx: parserS[parserpt-3].token.Pos} } case 14: - //line lang.y:134 - { - parserVAL.node = &ast.Arithmetic{ - Op: parserS[parserpt-1].token.Value.(ast.ArithmeticOp), - Exprs: []ast.Node{ - &ast.LiteralNode{ - Value: parserS[parserpt-2].token.Value.(int), - Typex: ast.TypeInt, - Posx: parserS[parserpt-2].token.Pos, - }, - &ast.LiteralNode{ - Value: parserS[parserpt-0].token.Value.(int), - Typex: ast.TypeInt, - Posx: parserS[parserpt-0].token.Pos, - }, - }, - Posx: parserS[parserpt-2].token.Pos, - } - } - case 15: - //line lang.y:154 + //line lang.y:137 { parserVAL.nodeList = nil } - case 16: - //line lang.y:158 + case 15: + //line lang.y:141 { parserVAL.nodeList = append(parserS[parserpt-2].nodeList, parserS[parserpt-0].node) } - case 17: - //line lang.y:162 + case 16: + //line lang.y:145 { parserVAL.nodeList = append(parserVAL.nodeList, parserS[parserpt-0].node) } - case 18: - //line lang.y:168 + case 17: + //line lang.y:151 { parserVAL.node = &ast.LiteralNode{ Value: parserS[parserpt-0].token.Value.(string), diff --git a/config/lang/y.output b/config/lang/y.output index c1f9df4a0..d524d1e2e 100644 --- a/config/lang/y.output +++ b/config/lang/y.output @@ -51,22 +51,21 @@ state 5 state 6 - literal: STRING. (18) + literal: STRING. (17) - . reduce 18 (src line 166) + . reduce 17 (src line 149) state 7 interpolation: PROGRAM_BRACKET_LEFT.expr PROGRAM_BRACKET_RIGHT PROGRAM_BRACKET_LEFT shift 7 - IDENTIFIER shift 14 + IDENTIFIER shift 13 INTEGER shift 11 FLOAT shift 12 STRING shift 6 . error - arith goto 13 expr goto 9 interpolation goto 5 literal goto 4 @@ -81,8 +80,10 @@ state 8 state 9 interpolation: PROGRAM_BRACKET_LEFT expr.PROGRAM_BRACKET_RIGHT + expr: expr.ARITH_OP expr - PROGRAM_BRACKET_RIGHT shift 15 + PROGRAM_BRACKET_RIGHT shift 14 + ARITH_OP shift 15 . error @@ -100,9 +101,7 @@ state 10 state 11 expr: INTEGER. (9) - arith: INTEGER.ARITH_OP INTEGER - ARITH_OP shift 16 . reduce 9 (src line 103) @@ -113,110 +112,118 @@ state 12 state 13 - expr: arith. (11) - - . reduce 11 (src line 119) - - -state 14 expr: IDENTIFIER. (12) expr: IDENTIFIER.PAREN_LEFT args PAREN_RIGHT - PAREN_LEFT shift 17 - . reduce 12 (src line 123) + PAREN_LEFT shift 16 + . reduce 12 (src line 127) -state 15 +state 14 interpolation: PROGRAM_BRACKET_LEFT expr PROGRAM_BRACKET_RIGHT. (7) . reduce 7 (src line 92) -state 16 - arith: INTEGER ARITH_OP.INTEGER - - INTEGER shift 18 - . error - - -state 17 - expr: IDENTIFIER PAREN_LEFT.args PAREN_RIGHT - args: . (15) +state 15 + expr: expr ARITH_OP.expr PROGRAM_BRACKET_LEFT shift 7 - IDENTIFIER shift 14 + IDENTIFIER shift 13 INTEGER shift 11 FLOAT shift 12 STRING shift 6 - . reduce 15 (src line 153) + . error - arith goto 13 - expr goto 20 + expr goto 17 interpolation goto 5 literal goto 4 literalModeTop goto 10 literalModeValue goto 3 - args goto 19 + +state 16 + expr: IDENTIFIER PAREN_LEFT.args PAREN_RIGHT + args: . (14) + + PROGRAM_BRACKET_LEFT shift 7 + IDENTIFIER shift 13 + INTEGER shift 11 + FLOAT shift 12 + STRING shift 6 + . reduce 14 (src line 136) + + expr goto 19 + interpolation goto 5 + literal goto 4 + literalModeTop goto 10 + literalModeValue goto 3 + args goto 18 + +17: shift/reduce conflict (shift 15(0), red'n 11(0)) on ARITH_OP +state 17 + expr: expr.ARITH_OP expr + expr: expr ARITH_OP expr. (11) + + ARITH_OP shift 15 + . reduce 11 (src line 119) + state 18 - arith: INTEGER ARITH_OP INTEGER. (14) - - . reduce 14 (src line 132) - - -state 19 expr: IDENTIFIER PAREN_LEFT args.PAREN_RIGHT args: args.COMMA expr - PAREN_RIGHT shift 21 - COMMA shift 22 + PAREN_RIGHT shift 20 + COMMA shift 21 . error -state 20 - args: expr. (17) +state 19 + expr: expr.ARITH_OP expr + args: expr. (16) - . reduce 17 (src line 161) + ARITH_OP shift 15 + . reduce 16 (src line 144) + + +state 20 + expr: IDENTIFIER PAREN_LEFT args PAREN_RIGHT. (13) + + . reduce 13 (src line 131) state 21 - expr: IDENTIFIER PAREN_LEFT args PAREN_RIGHT. (13) - - . reduce 13 (src line 127) - - -state 22 args: args COMMA.expr PROGRAM_BRACKET_LEFT shift 7 - IDENTIFIER shift 14 + IDENTIFIER shift 13 INTEGER shift 11 FLOAT shift 12 STRING shift 6 . error - arith goto 13 - expr goto 23 + expr goto 22 interpolation goto 5 literal goto 4 literalModeTop goto 10 literalModeValue goto 3 -state 23 - args: args COMMA expr. (16) +state 22 + expr: expr.ARITH_OP expr + args: args COMMA expr. (15) - . reduce 16 (src line 157) + ARITH_OP shift 15 + . reduce 15 (src line 140) -15 terminals, 9 nonterminals -19 grammar rules, 24/2000 states -0 shift/reduce, 0 reduce/reduce conflicts reported -58 working sets used -memory: parser 28/30000 -19 extra closures -27 shift entries, 1 exceptions +15 terminals, 8 nonterminals +18 grammar rules, 23/2000 states +1 shift/reduce, 0 reduce/reduce conflicts reported +57 working sets used +memory: parser 30/30000 +18 extra closures +34 shift entries, 1 exceptions 13 goto entries -17 entries saved by goto default +19 entries saved by goto default Optimizer space used: output 26/30000 26 table entries, 0 zero -maximum spread: 15, maximum offset: 22 +maximum spread: 15, maximum offset: 21 From 9ddcaf15ebf65e1d64a6645335aeb222b01de416 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 26 Feb 2015 14:33:56 -0800 Subject: [PATCH 3/6] config/lang: fix shift/reduce conflict --- config/lang/lang.y | 2 ++ config/lang/y.go | 38 +++++++++++++++++++------------------- config/lang/y.output | 40 +++++++++++++++++++--------------------- 3 files changed, 40 insertions(+), 40 deletions(-) diff --git a/config/lang/lang.y b/config/lang/lang.y index 96517a76e..9e4b9baaf 100644 --- a/config/lang/lang.y +++ b/config/lang/lang.y @@ -27,6 +27,8 @@ import ( %type expr interpolation literal literalModeTop literalModeValue %type args +%left ARITH_OP + %% top: diff --git a/config/lang/y.go b/config/lang/y.go index f9198d8f7..75b450d25 100644 --- a/config/lang/y.go +++ b/config/lang/y.go @@ -50,7 +50,7 @@ const parserEofCode = 1 const parserErrCode = 2 const parserMaxDepth = 200 -//line lang.y:159 +//line lang.y:161 //line yacctab:1 var parserExca = []int{ @@ -76,7 +76,7 @@ var parserAct = []int{ var parserPact = []int{ -2, -1000, -2, -1000, -1000, -1000, -1000, -3, -1000, 3, - -2, -1000, -1000, 10, -1000, -3, -3, -8, -4, -8, + -2, -1000, -1000, 10, -1000, -3, -3, -1000, -4, -8, -1000, -3, -8, } var parserPgo = []int{ @@ -344,7 +344,7 @@ parserdefault: switch parsernt { case 1: - //line lang.y:33 + //line lang.y:35 { parserResult = &ast.LiteralNode{ Value: "", @@ -353,7 +353,7 @@ parserdefault: } } case 2: - //line lang.y:41 + //line lang.y:43 { parserResult = parserS[parserpt-0].node @@ -375,12 +375,12 @@ parserdefault: } } case 3: - //line lang.y:64 + //line lang.y:66 { parserVAL.node = parserS[parserpt-0].node } case 4: - //line lang.y:68 + //line lang.y:70 { var result []ast.Node if c, ok := parserS[parserpt-1].node.(*ast.Concat); ok { @@ -395,27 +395,27 @@ parserdefault: } } case 5: - //line lang.y:84 + //line lang.y:86 { parserVAL.node = parserS[parserpt-0].node } case 6: - //line lang.y:88 + //line lang.y:90 { parserVAL.node = parserS[parserpt-0].node } case 7: - //line lang.y:94 + //line lang.y:96 { parserVAL.node = parserS[parserpt-1].node } case 8: - //line lang.y:100 + //line lang.y:102 { parserVAL.node = parserS[parserpt-0].node } case 9: - //line lang.y:104 + //line lang.y:106 { parserVAL.node = &ast.LiteralNode{ Value: parserS[parserpt-0].token.Value.(int), @@ -424,7 +424,7 @@ parserdefault: } } case 10: - //line lang.y:112 + //line lang.y:114 { parserVAL.node = &ast.LiteralNode{ Value: parserS[parserpt-0].token.Value.(float64), @@ -433,7 +433,7 @@ parserdefault: } } case 11: - //line lang.y:120 + //line lang.y:122 { parserVAL.node = &ast.Arithmetic{ Op: parserS[parserpt-1].token.Value.(ast.ArithmeticOp), @@ -442,32 +442,32 @@ parserdefault: } } case 12: - //line lang.y:128 + //line lang.y:130 { parserVAL.node = &ast.VariableAccess{Name: parserS[parserpt-0].token.Value.(string), Posx: parserS[parserpt-0].token.Pos} } case 13: - //line lang.y:132 + //line lang.y:134 { parserVAL.node = &ast.Call{Func: parserS[parserpt-3].token.Value.(string), Args: parserS[parserpt-1].nodeList, Posx: parserS[parserpt-3].token.Pos} } case 14: - //line lang.y:137 + //line lang.y:139 { parserVAL.nodeList = nil } case 15: - //line lang.y:141 + //line lang.y:143 { parserVAL.nodeList = append(parserS[parserpt-2].nodeList, parserS[parserpt-0].node) } case 16: - //line lang.y:145 + //line lang.y:147 { parserVAL.nodeList = append(parserVAL.nodeList, parserS[parserpt-0].node) } case 17: - //line lang.y:151 + //line lang.y:153 { parserVAL.node = &ast.LiteralNode{ Value: parserS[parserpt-0].token.Value.(string), diff --git a/config/lang/y.output b/config/lang/y.output index d524d1e2e..64dfe04fc 100644 --- a/config/lang/y.output +++ b/config/lang/y.output @@ -5,7 +5,7 @@ state 0 PROGRAM_BRACKET_LEFT shift 7 STRING shift 6 - . reduce 1 (src line 32) + . reduce 1 (src line 34) interpolation goto 5 literal goto 4 @@ -26,7 +26,7 @@ state 2 PROGRAM_BRACKET_LEFT shift 7 STRING shift 6 - . reduce 2 (src line 40) + . reduce 2 (src line 42) interpolation goto 5 literal goto 4 @@ -35,25 +35,25 @@ state 2 state 3 literalModeTop: literalModeValue. (3) - . reduce 3 (src line 62) + . reduce 3 (src line 64) state 4 literalModeValue: literal. (5) - . reduce 5 (src line 82) + . reduce 5 (src line 84) state 5 literalModeValue: interpolation. (6) - . reduce 6 (src line 87) + . reduce 6 (src line 89) state 6 literal: STRING. (17) - . reduce 17 (src line 149) + . reduce 17 (src line 151) state 7 @@ -75,7 +75,7 @@ state 7 state 8 literalModeTop: literalModeTop literalModeValue. (4) - . reduce 4 (src line 67) + . reduce 4 (src line 69) state 9 @@ -93,7 +93,7 @@ state 10 PROGRAM_BRACKET_LEFT shift 7 STRING shift 6 - . reduce 8 (src line 98) + . reduce 8 (src line 100) interpolation goto 5 literal goto 4 @@ -102,13 +102,13 @@ state 10 state 11 expr: INTEGER. (9) - . reduce 9 (src line 103) + . reduce 9 (src line 105) state 12 expr: FLOAT. (10) - . reduce 10 (src line 111) + . reduce 10 (src line 113) state 13 @@ -116,13 +116,13 @@ state 13 expr: IDENTIFIER.PAREN_LEFT args PAREN_RIGHT PAREN_LEFT shift 16 - . reduce 12 (src line 127) + . reduce 12 (src line 129) state 14 interpolation: PROGRAM_BRACKET_LEFT expr PROGRAM_BRACKET_RIGHT. (7) - . reduce 7 (src line 92) + . reduce 7 (src line 94) state 15 @@ -150,7 +150,7 @@ state 16 INTEGER shift 11 FLOAT shift 12 STRING shift 6 - . reduce 14 (src line 136) + . reduce 14 (src line 138) expr goto 19 interpolation goto 5 @@ -159,13 +159,11 @@ state 16 literalModeValue goto 3 args goto 18 -17: shift/reduce conflict (shift 15(0), red'n 11(0)) on ARITH_OP state 17 expr: expr.ARITH_OP expr expr: expr ARITH_OP expr. (11) - ARITH_OP shift 15 - . reduce 11 (src line 119) + . reduce 11 (src line 121) state 18 @@ -182,13 +180,13 @@ state 19 args: expr. (16) ARITH_OP shift 15 - . reduce 16 (src line 144) + . reduce 16 (src line 146) state 20 expr: IDENTIFIER PAREN_LEFT args PAREN_RIGHT. (13) - . reduce 13 (src line 131) + . reduce 13 (src line 133) state 21 @@ -212,16 +210,16 @@ state 22 args: args COMMA expr. (15) ARITH_OP shift 15 - . reduce 15 (src line 140) + . reduce 15 (src line 142) 15 terminals, 8 nonterminals 18 grammar rules, 23/2000 states -1 shift/reduce, 0 reduce/reduce conflicts reported +0 shift/reduce, 0 reduce/reduce conflicts reported 57 working sets used memory: parser 30/30000 18 extra closures -34 shift entries, 1 exceptions +33 shift entries, 1 exceptions 13 goto entries 19 entries saved by goto default Optimizer space used: output 26/30000 From 6750318bb50bb6e21eb9d3247acd0d71acdca3a4 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 26 Feb 2015 14:34:45 -0800 Subject: [PATCH 4/6] config/lang: add test with expressions --- config/lang/eval_test.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/config/lang/eval_test.go b/config/lang/eval_test.go index 3446be9d2..6af2c714b 100644 --- a/config/lang/eval_test.go +++ b/config/lang/eval_test.go @@ -95,6 +95,21 @@ func TestEval(t *testing.T) { ast.TypeString, }, + { + "foo ${bar+1}", + &ast.BasicScope{ + VarMap: map[string]ast.Variable{ + "bar": ast.Variable{ + Value: 41, + Type: ast.TypeInt, + }, + }, + }, + false, + "foo 42", + ast.TypeString, + }, + { "foo ${rand()}", &ast.BasicScope{ From 3c4a036fb507fabe3c12f73869c316bbba3362d5 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 26 Feb 2015 14:39:04 -0800 Subject: [PATCH 5/6] config/lang: add (expr) for order of ops --- config/lang/eval_test.go | 16 +++ config/lang/lang.y | 6 +- config/lang/y.go | 73 ++++++------ config/lang/y.output | 242 ++++++++++++++++++++++----------------- 4 files changed, 199 insertions(+), 138 deletions(-) diff --git a/config/lang/eval_test.go b/config/lang/eval_test.go index 6af2c714b..8a3195fe1 100644 --- a/config/lang/eval_test.go +++ b/config/lang/eval_test.go @@ -95,6 +95,22 @@ func TestEval(t *testing.T) { ast.TypeString, }, + { + "foo ${42+2*2}", + nil, + false, + "foo 88", + ast.TypeString, + }, + + { + "foo ${42+(2*2)}", + nil, + false, + "foo 46", + ast.TypeString, + }, + { "foo ${bar+1}", &ast.BasicScope{ diff --git a/config/lang/lang.y b/config/lang/lang.y index 9e4b9baaf..c531860e5 100644 --- a/config/lang/lang.y +++ b/config/lang/lang.y @@ -98,7 +98,11 @@ interpolation: } expr: - literalModeTop + PAREN_LEFT expr PAREN_RIGHT + { + $$ = $2 + } +| literalModeTop { $$ = $1 } diff --git a/config/lang/y.go b/config/lang/y.go index 75b450d25..e7dd185ae 100644 --- a/config/lang/y.go +++ b/config/lang/y.go @@ -50,7 +50,7 @@ const parserEofCode = 1 const parserErrCode = 2 const parserMaxDepth = 200 -//line lang.y:161 +//line lang.y:165 //line yacctab:1 var parserExca = []int{ @@ -59,51 +59,51 @@ var parserExca = []int{ -2, 0, } -const parserNprod = 18 +const parserNprod = 19 const parserPrivate = 57344 var parserTokenNames []string var parserStates []string -const parserLast = 26 +const parserLast = 30 var parserAct = []int{ - 9, 7, 7, 15, 3, 20, 21, 8, 14, 13, - 11, 12, 6, 6, 15, 8, 17, 19, 16, 10, - 2, 1, 22, 18, 4, 5, + 9, 20, 16, 16, 7, 7, 3, 18, 10, 8, + 1, 17, 14, 12, 13, 6, 6, 19, 8, 22, + 15, 23, 24, 11, 2, 25, 16, 21, 4, 5, } var parserPact = []int{ - -2, -1000, -2, -1000, -1000, -1000, -1000, -3, -1000, 3, - -2, -1000, -1000, 10, -1000, -3, -3, -1000, -4, -8, - -1000, -3, -8, + 1, -1000, 1, -1000, -1000, -1000, -1000, 0, -1000, 15, + 0, 1, -1000, -1000, -1, -1000, 0, -8, 0, -1000, + -1000, 12, -9, -1000, 0, -9, } var parserPgo = []int{ - 0, 0, 25, 24, 19, 4, 23, 21, + 0, 0, 29, 28, 23, 6, 27, 10, } var parserR1 = []int{ 0, 7, 7, 4, 4, 5, 5, 2, 1, 1, - 1, 1, 1, 1, 6, 6, 6, 3, + 1, 1, 1, 1, 1, 6, 6, 6, 3, } var parserR2 = []int{ - 0, 0, 1, 1, 2, 1, 1, 3, 1, 1, - 1, 3, 1, 4, 0, 3, 1, 1, + 0, 0, 1, 1, 2, 1, 1, 3, 3, 1, + 1, 1, 3, 1, 4, 0, 3, 1, 1, } var parserChk = []int{ -1000, -7, -4, -5, -3, -2, 15, 4, -5, -1, - -4, 13, 14, 12, 5, 11, 8, -1, -6, -1, - 9, 10, -1, + 8, -4, 13, 14, 12, 5, 11, -1, 8, -1, + 9, -6, -1, 9, 10, -1, } var parserDef = []int{ - 1, -2, 2, 3, 5, 6, 17, 0, 4, 0, - 8, 9, 10, 12, 7, 0, 14, 11, 0, 16, - 13, 0, 15, + 1, -2, 2, 3, 5, 6, 18, 0, 4, 0, + 0, 9, 10, 11, 13, 7, 0, 0, 15, 12, + 8, 0, 17, 14, 0, 16, } var parserTok1 = []int{ @@ -412,10 +412,15 @@ parserdefault: case 8: //line lang.y:102 { - parserVAL.node = parserS[parserpt-0].node + parserVAL.node = parserS[parserpt-1].node } case 9: //line lang.y:106 + { + parserVAL.node = parserS[parserpt-0].node + } + case 10: + //line lang.y:110 { parserVAL.node = &ast.LiteralNode{ Value: parserS[parserpt-0].token.Value.(int), @@ -423,8 +428,8 @@ parserdefault: Posx: parserS[parserpt-0].token.Pos, } } - case 10: - //line lang.y:114 + case 11: + //line lang.y:118 { parserVAL.node = &ast.LiteralNode{ Value: parserS[parserpt-0].token.Value.(float64), @@ -432,8 +437,8 @@ parserdefault: Posx: parserS[parserpt-0].token.Pos, } } - case 11: - //line lang.y:122 + case 12: + //line lang.y:126 { parserVAL.node = &ast.Arithmetic{ Op: parserS[parserpt-1].token.Value.(ast.ArithmeticOp), @@ -441,33 +446,33 @@ parserdefault: Posx: parserS[parserpt-2].node.Pos(), } } - case 12: - //line lang.y:130 - { - parserVAL.node = &ast.VariableAccess{Name: parserS[parserpt-0].token.Value.(string), Posx: parserS[parserpt-0].token.Pos} - } case 13: //line lang.y:134 { - parserVAL.node = &ast.Call{Func: parserS[parserpt-3].token.Value.(string), Args: parserS[parserpt-1].nodeList, Posx: parserS[parserpt-3].token.Pos} + parserVAL.node = &ast.VariableAccess{Name: parserS[parserpt-0].token.Value.(string), Posx: parserS[parserpt-0].token.Pos} } case 14: - //line lang.y:139 + //line lang.y:138 { - parserVAL.nodeList = nil + parserVAL.node = &ast.Call{Func: parserS[parserpt-3].token.Value.(string), Args: parserS[parserpt-1].nodeList, Posx: parserS[parserpt-3].token.Pos} } case 15: //line lang.y:143 { - parserVAL.nodeList = append(parserS[parserpt-2].nodeList, parserS[parserpt-0].node) + parserVAL.nodeList = nil } case 16: //line lang.y:147 { - parserVAL.nodeList = append(parserVAL.nodeList, parserS[parserpt-0].node) + parserVAL.nodeList = append(parserS[parserpt-2].nodeList, parserS[parserpt-0].node) } case 17: - //line lang.y:153 + //line lang.y:151 + { + parserVAL.nodeList = append(parserVAL.nodeList, parserS[parserpt-0].node) + } + case 18: + //line lang.y:157 { parserVAL.node = &ast.LiteralNode{ Value: parserS[parserpt-0].token.Value.(string), diff --git a/config/lang/y.output b/config/lang/y.output index 64dfe04fc..17352390d 100644 --- a/config/lang/y.output +++ b/config/lang/y.output @@ -51,25 +51,26 @@ state 5 state 6 - literal: STRING. (17) + literal: STRING. (18) - . reduce 17 (src line 151) + . reduce 18 (src line 155) state 7 interpolation: PROGRAM_BRACKET_LEFT.expr PROGRAM_BRACKET_RIGHT PROGRAM_BRACKET_LEFT shift 7 - IDENTIFIER shift 13 - INTEGER shift 11 - FLOAT shift 12 + PAREN_LEFT shift 10 + IDENTIFIER shift 14 + INTEGER shift 12 + FLOAT shift 13 STRING shift 6 . error expr goto 9 interpolation goto 5 literal goto 4 - literalModeTop goto 10 + literalModeTop goto 11 literalModeValue goto 3 state 8 @@ -82,146 +83,181 @@ state 9 interpolation: PROGRAM_BRACKET_LEFT expr.PROGRAM_BRACKET_RIGHT expr: expr.ARITH_OP expr - PROGRAM_BRACKET_RIGHT shift 14 - ARITH_OP shift 15 + PROGRAM_BRACKET_RIGHT shift 15 + ARITH_OP shift 16 . error state 10 - literalModeTop: literalModeTop.literalModeValue - expr: literalModeTop. (8) + expr: PAREN_LEFT.expr PAREN_RIGHT PROGRAM_BRACKET_LEFT shift 7 - STRING shift 6 - . reduce 8 (src line 100) - - interpolation goto 5 - literal goto 4 - literalModeValue goto 8 - -state 11 - expr: INTEGER. (9) - - . reduce 9 (src line 105) - - -state 12 - expr: FLOAT. (10) - - . reduce 10 (src line 113) - - -state 13 - expr: IDENTIFIER. (12) - expr: IDENTIFIER.PAREN_LEFT args PAREN_RIGHT - - PAREN_LEFT shift 16 - . reduce 12 (src line 129) - - -state 14 - interpolation: PROGRAM_BRACKET_LEFT expr PROGRAM_BRACKET_RIGHT. (7) - - . reduce 7 (src line 94) - - -state 15 - expr: expr ARITH_OP.expr - - PROGRAM_BRACKET_LEFT shift 7 - IDENTIFIER shift 13 - INTEGER shift 11 - FLOAT shift 12 + PAREN_LEFT shift 10 + IDENTIFIER shift 14 + INTEGER shift 12 + FLOAT shift 13 STRING shift 6 . error expr goto 17 interpolation goto 5 literal goto 4 - literalModeTop goto 10 + literalModeTop goto 11 literalModeValue goto 3 -state 16 - expr: IDENTIFIER PAREN_LEFT.args PAREN_RIGHT - args: . (14) +state 11 + literalModeTop: literalModeTop.literalModeValue + expr: literalModeTop. (9) PROGRAM_BRACKET_LEFT shift 7 - IDENTIFIER shift 13 - INTEGER shift 11 - FLOAT shift 12 STRING shift 6 - . reduce 14 (src line 138) + . reduce 9 (src line 105) + + interpolation goto 5 + literal goto 4 + literalModeValue goto 8 + +state 12 + expr: INTEGER. (10) + + . reduce 10 (src line 109) + + +state 13 + expr: FLOAT. (11) + + . reduce 11 (src line 117) + + +state 14 + expr: IDENTIFIER. (13) + expr: IDENTIFIER.PAREN_LEFT args PAREN_RIGHT + + PAREN_LEFT shift 18 + . reduce 13 (src line 133) + + +state 15 + interpolation: PROGRAM_BRACKET_LEFT expr PROGRAM_BRACKET_RIGHT. (7) + + . reduce 7 (src line 94) + + +state 16 + expr: expr ARITH_OP.expr + + PROGRAM_BRACKET_LEFT shift 7 + PAREN_LEFT shift 10 + IDENTIFIER shift 14 + INTEGER shift 12 + FLOAT shift 13 + STRING shift 6 + . error expr goto 19 interpolation goto 5 literal goto 4 - literalModeTop goto 10 + literalModeTop goto 11 literalModeValue goto 3 - args goto 18 state 17 + expr: PAREN_LEFT expr.PAREN_RIGHT expr: expr.ARITH_OP expr - expr: expr ARITH_OP expr. (11) - . reduce 11 (src line 121) + PAREN_RIGHT shift 20 + ARITH_OP shift 16 + . error state 18 - expr: IDENTIFIER PAREN_LEFT args.PAREN_RIGHT - args: args.COMMA expr - - PAREN_RIGHT shift 20 - COMMA shift 21 - . error - - -state 19 - expr: expr.ARITH_OP expr - args: expr. (16) - - ARITH_OP shift 15 - . reduce 16 (src line 146) - - -state 20 - expr: IDENTIFIER PAREN_LEFT args PAREN_RIGHT. (13) - - . reduce 13 (src line 133) - - -state 21 - args: args COMMA.expr + expr: IDENTIFIER PAREN_LEFT.args PAREN_RIGHT + args: . (15) PROGRAM_BRACKET_LEFT shift 7 - IDENTIFIER shift 13 - INTEGER shift 11 - FLOAT shift 12 + PAREN_LEFT shift 10 + IDENTIFIER shift 14 + INTEGER shift 12 + FLOAT shift 13 STRING shift 6 - . error + . reduce 15 (src line 142) expr goto 22 interpolation goto 5 literal goto 4 - literalModeTop goto 10 + literalModeTop goto 11 literalModeValue goto 3 + args goto 21 + +state 19 + expr: expr.ARITH_OP expr + expr: expr ARITH_OP expr. (12) + + . reduce 12 (src line 125) + + +state 20 + expr: PAREN_LEFT expr PAREN_RIGHT. (8) + + . reduce 8 (src line 100) + + +state 21 + expr: IDENTIFIER PAREN_LEFT args.PAREN_RIGHT + args: args.COMMA expr + + PAREN_RIGHT shift 23 + COMMA shift 24 + . error + state 22 expr: expr.ARITH_OP expr - args: args COMMA expr. (15) + args: expr. (17) - ARITH_OP shift 15 - . reduce 15 (src line 142) + ARITH_OP shift 16 + . reduce 17 (src line 150) + + +state 23 + expr: IDENTIFIER PAREN_LEFT args PAREN_RIGHT. (14) + + . reduce 14 (src line 137) + + +state 24 + args: args COMMA.expr + + PROGRAM_BRACKET_LEFT shift 7 + PAREN_LEFT shift 10 + IDENTIFIER shift 14 + INTEGER shift 12 + FLOAT shift 13 + STRING shift 6 + . error + + expr goto 25 + interpolation goto 5 + literal goto 4 + literalModeTop goto 11 + literalModeValue goto 3 + +state 25 + expr: expr.ARITH_OP expr + args: args COMMA expr. (16) + + ARITH_OP shift 16 + . reduce 16 (src line 146) 15 terminals, 8 nonterminals -18 grammar rules, 23/2000 states +19 grammar rules, 26/2000 states 0 shift/reduce, 0 reduce/reduce conflicts reported 57 working sets used -memory: parser 30/30000 -18 extra closures -33 shift entries, 1 exceptions -13 goto entries -19 entries saved by goto default -Optimizer space used: output 26/30000 -26 table entries, 0 zero -maximum spread: 15, maximum offset: 21 +memory: parser 35/30000 +21 extra closures +45 shift entries, 1 exceptions +14 goto entries +23 entries saved by goto default +Optimizer space used: output 30/30000 +30 table entries, 0 zero +maximum spread: 15, maximum offset: 24 From 5848cba6ad55be9853da844854f6c0291210ac38 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 26 Feb 2015 15:17:37 -0800 Subject: [PATCH 6/6] config/lang: add modulo --- config/lang/ast/arithmetic_op.go | 1 + config/lang/builtins.go | 2 ++ config/lang/check_types.go | 5 +++++ config/lang/eval_test.go | 8 ++++++++ config/lang/lex.go | 3 +++ 5 files changed, 19 insertions(+) diff --git a/config/lang/ast/arithmetic_op.go b/config/lang/ast/arithmetic_op.go index 920b1d56e..e36dd42dc 100644 --- a/config/lang/ast/arithmetic_op.go +++ b/config/lang/ast/arithmetic_op.go @@ -9,4 +9,5 @@ const ( ArithmeticOpSub ArithmeticOpMul ArithmeticOpDiv + ArithmeticOpMod ) diff --git a/config/lang/builtins.go b/config/lang/builtins.go index e14aba0d8..bf918c9c7 100644 --- a/config/lang/builtins.go +++ b/config/lang/builtins.go @@ -77,6 +77,8 @@ func builtinIntMath() ast.Function { result *= arg case ast.ArithmeticOpDiv: result /= arg + case ast.ArithmeticOpMod: + result = result % arg } } diff --git a/config/lang/check_types.go b/config/lang/check_types.go index 3b6eefbf0..f5cf16680 100644 --- a/config/lang/check_types.go +++ b/config/lang/check_types.go @@ -131,6 +131,11 @@ func (tc *typeCheckArithmetic) TypeCheck(v *TypeCheck) (ast.Node, error) { } } + // Modulo doesn't work for floats + if mathType == ast.TypeFloat && tc.n.Op == ast.ArithmeticOpMod { + return nil, fmt.Errorf("modulo cannot be used with floats") + } + // Return type v.StackPush(mathType) diff --git a/config/lang/eval_test.go b/config/lang/eval_test.go index 8a3195fe1..602880cf2 100644 --- a/config/lang/eval_test.go +++ b/config/lang/eval_test.go @@ -71,6 +71,14 @@ func TestEval(t *testing.T) { ast.TypeString, }, + { + "foo ${42%4}", + nil, + false, + "foo 2", + ast.TypeString, + }, + { "foo ${42.0+1.0}", nil, diff --git a/config/lang/lex.go b/config/lang/lex.go index 6b30f2a4c..6e70acfbc 100644 --- a/config/lang/lex.go +++ b/config/lang/lex.go @@ -180,6 +180,9 @@ func (x *parserLex) lexModeInterpolation(yylval *parserSymType) int { case '/': yylval.token = &parserToken{Value: ast.ArithmeticOpDiv} return ARITH_OP + case '%': + yylval.token = &parserToken{Value: ast.ArithmeticOpMod} + return ARITH_OP default: x.backup() return x.lexId(yylval)