diff --git a/config/lang/ast/call.go b/config/lang/ast/call.go new file mode 100644 index 000000000..35fc03f50 --- /dev/null +++ b/config/lang/ast/call.go @@ -0,0 +1,7 @@ +package ast + +// Call represents a function call. +type Call struct { + Func string + Args []Node +} diff --git a/config/lang/ast/variable_access.go b/config/lang/ast/variable_access.go index bf22ce356..5d63dcba1 100644 --- a/config/lang/ast/variable_access.go +++ b/config/lang/ast/variable_access.go @@ -1,6 +1,14 @@ package ast +import ( + "fmt" +) + // VariableAccess represents a variable access. type VariableAccess struct { Name string } + +func (n *VariableAccess) GoString() string { + return fmt.Sprintf("*%#v", *n) +} diff --git a/config/lang/lang.y b/config/lang/lang.y index 52db6ee34..f346799fd 100644 --- a/config/lang/lang.y +++ b/config/lang/lang.y @@ -12,13 +12,16 @@ import ( %} %union { - node ast.Node - str string + node ast.Node + nodeList []ast.Node + str string } %token STRING IDENTIFIER PROGRAM_BRACKET_LEFT PROGRAM_BRACKET_RIGHT +%token PAREN_LEFT PAREN_RIGHT COMMA %type expr interpolation literal +%type args %% @@ -27,6 +30,10 @@ top: { parserResult = $1 } +| interpolation + { + parserResult = $1 + } | literal interpolation { parserResult = &ast.Concat{ @@ -41,14 +48,31 @@ interpolation: } expr: - IDENTIFIER - { - $$ = &ast.VariableAccess{Name: $1} - } -| literal + literal { $$ = $1 } +| IDENTIFIER + { + $$ = &ast.VariableAccess{Name: $1} + } +| IDENTIFIER PAREN_LEFT args PAREN_RIGHT + { + $$ = &ast.Call{Func: $1, Args: $3} + } + +args: + { + $$ = nil + } +| args COMMA expr + { + $$ = append($1, $3) + } +| expr + { + $$ = append($$, $1) + } literal: STRING diff --git a/config/lang/lex.go b/config/lang/lex.go index 6f0705c84..365001199 100644 --- a/config/lang/lex.go +++ b/config/lang/lex.go @@ -38,9 +38,21 @@ func (x *parserLex) Lex(yylval *parserSymType) int { return PROGRAM_BRACKET_LEFT } + if x.interpolationDepth == 0 { + // We're just a normal string that isn't part of any + // interpolation yet. + x.backup() + return x.lexString(yylval, false) + } + + // Ignore all whitespace + if unicode.IsSpace(c) { + continue + } + // If we see a double quote and we're in an interpolation, then // we are lexing a string. - if c == '"' && x.interpolationDepth > 0 { + if c == '"' { return x.lexString(yylval, true) } @@ -48,16 +60,15 @@ func (x *parserLex) Lex(yylval *parserSymType) int { case '}': x.interpolationDepth-- return PROGRAM_BRACKET_RIGHT + case '(': + return PAREN_LEFT + case ')': + return PAREN_RIGHT + case ',': + return COMMA default: x.backup() - if x.interpolationDepth > 0 { - // We're within an interpolation. - return x.lexId(yylval) - } else { - // We're just a normal string that isn't part of any - // interpolation yet. - return x.lexString(yylval, false) - } + return x.lexId(yylval) } } } diff --git a/config/lang/lex_test.go b/config/lang/lex_test.go index ab5a18b9b..3546bc885 100644 --- a/config/lang/lex_test.go +++ b/config/lang/lex_test.go @@ -29,6 +29,22 @@ func TestLex(t *testing.T) { "foo ${\"bar\"}", []int{STRING, PROGRAM_BRACKET_LEFT, STRING, PROGRAM_BRACKET_RIGHT, lexEOF}, }, + + { + "${bar(baz)}", + []int{PROGRAM_BRACKET_LEFT, + IDENTIFIER, PAREN_LEFT, IDENTIFIER, PAREN_RIGHT, + PROGRAM_BRACKET_RIGHT, lexEOF}, + }, + + { + "${bar(baz, foo)}", + []int{PROGRAM_BRACKET_LEFT, + IDENTIFIER, PAREN_LEFT, + IDENTIFIER, COMMA, IDENTIFIER, + PAREN_RIGHT, + PROGRAM_BRACKET_RIGHT, lexEOF}, + }, } for _, tc := range cases { diff --git a/config/lang/parse_test.go b/config/lang/parse_test.go index 38507582a..7e7114f57 100644 --- a/config/lang/parse_test.go +++ b/config/lang/parse_test.go @@ -54,6 +54,35 @@ func TestParse(t *testing.T) { }, }, }, + + { + "${foo(bar)}", + false, + &ast.Call{ + Func: "foo", + Args: []ast.Node{ + &ast.VariableAccess{ + Name: "bar", + }, + }, + }, + }, + + { + "${foo(bar, baz)}", + false, + &ast.Call{ + Func: "foo", + Args: []ast.Node{ + &ast.VariableAccess{ + Name: "bar", + }, + &ast.VariableAccess{ + Name: "baz", + }, + }, + }, + }, } for _, tc := range cases { diff --git a/config/lang/y.go b/config/lang/y.go index a2b2e81fd..a0ef3d105 100644 --- a/config/lang/y.go +++ b/config/lang/y.go @@ -10,21 +10,28 @@ import ( //line lang.y:14 type parserSymType struct { - yys int - node ast.Node - str string + yys int + node ast.Node + nodeList []ast.Node + str string } const STRING = 57346 const IDENTIFIER = 57347 const PROGRAM_BRACKET_LEFT = 57348 const PROGRAM_BRACKET_RIGHT = 57349 +const PAREN_LEFT = 57350 +const PAREN_RIGHT = 57351 +const COMMA = 57352 var parserToknames = []string{ "STRING", "IDENTIFIER", "PROGRAM_BRACKET_LEFT", "PROGRAM_BRACKET_RIGHT", + "PAREN_LEFT", + "PAREN_RIGHT", + "COMMA", } var parserStatenames = []string{} @@ -32,7 +39,7 @@ const parserEofCode = 1 const parserErrCode = 2 const parserMaxDepth = 200 -//line lang.y:59 +//line lang.y:83 //line yacctab:1 var parserExca = []int{ @@ -41,41 +48,47 @@ var parserExca = []int{ -2, 0, } -const parserNprod = 7 +const parserNprod = 12 const parserPrivate = 57344 var parserTokenNames []string var parserStates []string -const parserLast = 10 +const parserLast = 18 var parserAct = []int{ - 9, 3, 7, 2, 5, 3, 1, 4, 6, 8, + 7, 14, 15, 11, 10, 4, 5, 5, 4, 9, + 3, 1, 13, 6, 8, 2, 16, 12, } var parserPact = []int{ - 1, -1000, -2, -1000, -1000, -3, -7, -1000, -1000, -1000, + 1, -1000, 0, -1000, -1000, 4, -1000, -3, -1000, -5, + -1000, 4, -8, -1000, -1000, 4, -1000, } var parserPgo = []int{ - 0, 8, 7, 3, 6, + 0, 0, 10, 14, 17, 11, } var parserR1 = []int{ - 0, 4, 4, 2, 1, 1, 3, + 0, 5, 5, 5, 2, 1, 1, 1, 4, 4, + 4, 3, } var parserR2 = []int{ - 0, 1, 2, 3, 1, 1, 1, + 0, 1, 1, 2, 3, 1, 1, 4, 0, 3, + 1, 1, } var parserChk = []int{ - -1000, -4, -3, 4, -2, 6, -1, 5, -3, 7, + -1000, -5, -3, -2, 4, 6, -2, -1, -3, 5, + 7, 8, -4, -1, 9, 10, -1, } var parserDef = []int{ - 0, -2, 1, 6, 2, 0, 0, 4, 5, 3, + 0, -2, 1, 2, 11, 0, 3, 0, 5, 6, + 4, 8, 0, 10, 7, 0, 9, } var parserTok1 = []int{ @@ -83,7 +96,7 @@ var parserTok1 = []int{ } var parserTok2 = []int{ - 2, 3, 4, 5, 6, 7, + 2, 3, 4, 5, 6, 7, 8, 9, 10, } var parserTok3 = []int{ 0, @@ -315,34 +328,59 @@ parserdefault: switch parsernt { case 1: - //line lang.y:27 + //line lang.y:30 { parserResult = parserS[parserpt-0].node } case 2: - //line lang.y:31 + //line lang.y:34 + { + parserResult = parserS[parserpt-0].node + } + case 3: + //line lang.y:38 { parserResult = &ast.Concat{ Exprs: []ast.Node{parserS[parserpt-1].node, parserS[parserpt-0].node}, } } - case 3: - //line lang.y:39 + case 4: + //line lang.y:46 { parserVAL.node = parserS[parserpt-1].node } - case 4: - //line lang.y:45 - { - parserVAL.node = &ast.VariableAccess{Name: parserS[parserpt-0].str} - } case 5: - //line lang.y:49 + //line lang.y:52 { parserVAL.node = parserS[parserpt-0].node } case 6: - //line lang.y:55 + //line lang.y:56 + { + parserVAL.node = &ast.VariableAccess{Name: parserS[parserpt-0].str} + } + case 7: + //line lang.y:60 + { + parserVAL.node = &ast.Call{Func: parserS[parserpt-3].str, Args: parserS[parserpt-1].nodeList} + } + case 8: + //line lang.y:65 + { + parserVAL.nodeList = nil + } + case 9: + //line lang.y:69 + { + parserVAL.nodeList = append(parserS[parserpt-2].nodeList, parserS[parserpt-0].node) + } + case 10: + //line lang.y:73 + { + parserVAL.nodeList = append(parserVAL.nodeList, parserS[parserpt-0].node) + } + case 11: + //line lang.y:79 { parserVAL.node = &ast.LiteralNode{Value: parserS[parserpt-0].str, Type: ast.TypeString} } diff --git a/config/lang/y.output b/config/lang/y.output index 1fe8babd4..f098e493d 100644 --- a/config/lang/y.output +++ b/config/lang/y.output @@ -2,9 +2,11 @@ state 0 $accept: .top $end - STRING shift 3 + STRING shift 4 + PROGRAM_BRACKET_LEFT shift 5 . error + interpolation goto 3 literal goto 2 top goto 1 @@ -20,66 +22,123 @@ state 2 top: literal.interpolation PROGRAM_BRACKET_LEFT shift 5 - . reduce 1 (src line 25) + . reduce 1 (src line 28) - interpolation goto 4 + interpolation goto 6 state 3 - literal: STRING. (6) + top: interpolation. (2) - . reduce 6 (src line 53) + . reduce 2 (src line 33) state 4 - top: literal interpolation. (2) + literal: STRING. (11) - . reduce 2 (src line 30) + . reduce 11 (src line 77) state 5 interpolation: PROGRAM_BRACKET_LEFT.expr PROGRAM_BRACKET_RIGHT - STRING shift 3 - IDENTIFIER shift 7 + STRING shift 4 + IDENTIFIER shift 9 . error - expr goto 6 + expr goto 7 literal goto 8 state 6 - interpolation: PROGRAM_BRACKET_LEFT expr.PROGRAM_BRACKET_RIGHT + top: literal interpolation. (3) - PROGRAM_BRACKET_RIGHT shift 9 - . error + . reduce 3 (src line 37) state 7 - expr: IDENTIFIER. (4) + interpolation: PROGRAM_BRACKET_LEFT expr.PROGRAM_BRACKET_RIGHT - . reduce 4 (src line 43) + PROGRAM_BRACKET_RIGHT shift 10 + . error state 8 expr: literal. (5) - . reduce 5 (src line 48) + . reduce 5 (src line 50) state 9 - interpolation: PROGRAM_BRACKET_LEFT expr PROGRAM_BRACKET_RIGHT. (3) + expr: IDENTIFIER. (6) + expr: IDENTIFIER.PAREN_LEFT args PAREN_RIGHT - . reduce 3 (src line 37) + PAREN_LEFT shift 11 + . reduce 6 (src line 55) -7 terminals, 5 nonterminals -7 grammar rules, 10/2000 states +state 10 + interpolation: PROGRAM_BRACKET_LEFT expr PROGRAM_BRACKET_RIGHT. (4) + + . reduce 4 (src line 44) + + +state 11 + expr: IDENTIFIER PAREN_LEFT.args PAREN_RIGHT + args: . (8) + + STRING shift 4 + IDENTIFIER shift 9 + . reduce 8 (src line 64) + + expr goto 13 + literal goto 8 + args goto 12 + +state 12 + expr: IDENTIFIER PAREN_LEFT args.PAREN_RIGHT + args: args.COMMA expr + + PAREN_RIGHT shift 14 + COMMA shift 15 + . error + + +state 13 + args: expr. (10) + + . reduce 10 (src line 72) + + +state 14 + expr: IDENTIFIER PAREN_LEFT args PAREN_RIGHT. (7) + + . reduce 7 (src line 59) + + +state 15 + args: args COMMA.expr + + STRING shift 4 + IDENTIFIER shift 9 + . error + + expr goto 16 + literal goto 8 + +state 16 + args: args COMMA expr. (9) + + . reduce 9 (src line 68) + + +10 terminals, 6 nonterminals +12 grammar rules, 17/2000 states 0 shift/reduce, 0 reduce/reduce conflicts reported -54 working sets used -memory: parser 5/30000 -1 extra closures -5 shift entries, 1 exceptions -5 goto entries -0 entries saved by goto default -Optimizer space used: output 10/30000 -10 table entries, 0 zero -maximum spread: 7, maximum offset: 7 +55 working sets used +memory: parser 11/30000 +6 extra closures +13 shift entries, 1 exceptions +9 goto entries +2 entries saved by goto default +Optimizer space used: output 18/30000 +18 table entries, 0 zero +maximum spread: 10, maximum offset: 15