// Mgmt // Copyright (C) 2013-2019+ James Shubin and the project contributors // Written by James Shubin and the project contributors // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . %{ package lang import ( "fmt" "strings" "github.com/purpleidea/mgmt/lang/interfaces" "github.com/purpleidea/mgmt/lang/types" "github.com/purpleidea/mgmt/util" ) const ( errstrParseAdditionalEquals = "additional equals in bind statement" errstrParseExpectingComma = "expecting trailing comma" ) func init() { yyErrorVerbose = true // set the global that enables showing full errors } %} %union { row int col int //err error // TODO: if we ever match ERROR in the parser bool bool str string int int64 // this is the .int as seen in lexer.nex float float64 strSlice []string typ *types.Type stmts []interfaces.Stmt stmt interfaces.Stmt exprs []interfaces.Expr expr interfaces.Expr mapKVs []*ExprMapKV mapKV *ExprMapKV structFields []*ExprStructField structField *ExprStructField args []*Arg arg *Arg resContents []StmtResContents // interface resField *StmtResField resEdge *StmtResEdge resMeta *StmtResMeta edgeHalfList []*StmtEdgeHalf edgeHalf *StmtEdgeHalf } %token OPEN_CURLY CLOSE_CURLY %token OPEN_PAREN CLOSE_PAREN %token OPEN_BRACK CLOSE_BRACK %token IF ELSE %token STRING BOOL INTEGER FLOAT %token EQUALS DOLLAR %token COMMA COLON SEMICOLON %token ELVIS ROCKET ARROW DOT %token STR_IDENTIFIER BOOL_IDENTIFIER INT_IDENTIFIER FLOAT_IDENTIFIER %token MAP_IDENTIFIER STRUCT_IDENTIFIER VARIANT_IDENTIFIER VAR_IDENTIFIER %token VAR_IDENTIFIER_HX %token RES_IDENTIFIER %token IDENTIFIER CAPITALIZED_IDENTIFIER %token FUNC_IDENTIFIER %token CLASS_IDENTIFIER INCLUDE_IDENTIFIER %token IMPORT_IDENTIFIER AS_IDENTIFIER %token COMMENT ERROR // precedence table // "Operator precedence is determined by the line ordering of the declarations; // the higher the line number of the declaration (lower on the page or screen), // the higher the precedence." // From: https://www.gnu.org/software/bison/manual/html_node/Infix-Calc.html // FIXME: a yacc specialist should check the precedence and add more tests! %left AND OR %nonassoc LT GT LTE GTE EQ NEQ // TODO: is %nonassoc correct for all of these? %left PLUS MINUS %left MULTIPLY DIVIDE %right NOT //%right EXP // exponentiation %nonassoc IN // TODO: is %nonassoc correct for this? %error IDENTIFIER STRING OPEN_CURLY IDENTIFIER ROCKET BOOL CLOSE_CURLY: errstrParseExpectingComma %error IDENTIFIER STRING OPEN_CURLY IDENTIFIER ROCKET STRING CLOSE_CURLY: errstrParseExpectingComma %error IDENTIFIER STRING OPEN_CURLY IDENTIFIER ROCKET INTEGER CLOSE_CURLY: errstrParseExpectingComma %error IDENTIFIER STRING OPEN_CURLY IDENTIFIER ROCKET FLOAT CLOSE_CURLY: errstrParseExpectingComma %error VAR_IDENTIFIER EQ BOOL: errstrParseAdditionalEquals %error VAR_IDENTIFIER EQ STRING: errstrParseAdditionalEquals %error VAR_IDENTIFIER EQ INTEGER: errstrParseAdditionalEquals %error VAR_IDENTIFIER EQ FLOAT: errstrParseAdditionalEquals %% top: prog { posLast(yylex, yyDollar) // our pos // store the AST in the struct that we previously passed in lp := cast(yylex) lp.ast = $1.stmt // this is equivalent to: //lp := yylex.(*Lexer).parseResult //lp.(*lexParseAST).ast = $1.stmt } ; prog: /* end of list */ { posLast(yylex, yyDollar) // our pos $$.stmt = &StmtProg{ Prog: []interfaces.Stmt{}, } } | prog stmt { posLast(yylex, yyDollar) // our pos // TODO: should we just skip comments for now? //if _, ok := $2.stmt.(*StmtComment); !ok { //} if stmt, ok := $1.stmt.(*StmtProg); ok { stmts := stmt.Prog stmts = append(stmts, $2.stmt) $$.stmt = &StmtProg{ Prog: stmts, } } } ; stmt: COMMENT { posLast(yylex, yyDollar) // our pos $$.stmt = &StmtComment{ Value: $1.str, } } | bind { posLast(yylex, yyDollar) // our pos $$.stmt = $1.stmt } | resource { posLast(yylex, yyDollar) // our pos $$.stmt = $1.stmt } | edge { posLast(yylex, yyDollar) // our pos $$.stmt = $1.stmt } | IF expr OPEN_CURLY prog CLOSE_CURLY { posLast(yylex, yyDollar) // our pos $$.stmt = &StmtIf{ Condition: $2.expr, ThenBranch: $4.stmt, //ElseBranch: nil, } } | IF expr OPEN_CURLY prog CLOSE_CURLY ELSE OPEN_CURLY prog CLOSE_CURLY { posLast(yylex, yyDollar) // our pos $$.stmt = &StmtIf{ Condition: $2.expr, ThenBranch: $4.stmt, ElseBranch: $8.stmt, } } // this is the named version, iow, a user-defined function (statement) // `func name() { }` // `func name() { }` // `func name(, ) { }` | FUNC_IDENTIFIER IDENTIFIER OPEN_PAREN args CLOSE_PAREN OPEN_CURLY expr CLOSE_CURLY { posLast(yylex, yyDollar) // our pos $$.stmt = &StmtFunc{ Name: $2.str, Func: &ExprFunc{ Args: $4.args, //Return: nil, Body: $7.expr, }, } } // `func name(...) { }` | FUNC_IDENTIFIER IDENTIFIER OPEN_PAREN args CLOSE_PAREN type OPEN_CURLY expr CLOSE_CURLY { posLast(yylex, yyDollar) // our pos fn := &ExprFunc{ Args: $4.args, Return: $6.typ, // return type is known Body: $8.expr, } isFullyTyped := $6.typ != nil // true if set m := make(map[string]*types.Type) ord := []string{} for _, a := range $4.args { if a.Type == nil { // at least one is unknown, can't run SetType... isFullyTyped = false break } m[a.Name] = a.Type ord = append(ord, a.Name) } if isFullyTyped { typ := &types.Type{ Kind: types.KindFunc, Map: m, Ord: ord, Out: $6.typ, } if err := fn.SetType(typ); err != nil { // this will ultimately cause a parser error to occur... yylex.Error(fmt.Sprintf("%s: %+v", ErrParseSetType, err)) } } $$.stmt = &StmtFunc{ Name: $2.str, Func: fn, } } // `class name { }` | CLASS_IDENTIFIER IDENTIFIER OPEN_CURLY prog CLOSE_CURLY { posLast(yylex, yyDollar) // our pos $$.stmt = &StmtClass{ Name: $2.str, Args: nil, Body: $4.stmt, } } // `class name() { }` // `class name(, ) { }` | CLASS_IDENTIFIER IDENTIFIER OPEN_PAREN args CLOSE_PAREN OPEN_CURLY prog CLOSE_CURLY { posLast(yylex, yyDollar) // our pos $$.stmt = &StmtClass{ Name: $2.str, Args: $4.args, Body: $7.stmt, } } // `include name` | INCLUDE_IDENTIFIER dotted_identifier { posLast(yylex, yyDollar) // our pos $$.stmt = &StmtInclude{ Name: $2.str, } } // `include name(...)` | INCLUDE_IDENTIFIER dotted_identifier OPEN_PAREN call_args CLOSE_PAREN { posLast(yylex, yyDollar) // our pos $$.stmt = &StmtInclude{ Name: $2.str, Args: $4.exprs, } } // `import "name"` | IMPORT_IDENTIFIER STRING { posLast(yylex, yyDollar) // our pos $$.stmt = &StmtImport{ Name: $2.str, //Alias: "", } } // `import "name" as alias` | IMPORT_IDENTIFIER STRING AS_IDENTIFIER IDENTIFIER { posLast(yylex, yyDollar) // our pos $$.stmt = &StmtImport{ Name: $2.str, Alias: $4.str, } } // `import "name" as *` | IMPORT_IDENTIFIER STRING AS_IDENTIFIER MULTIPLY { posLast(yylex, yyDollar) // our pos $$.stmt = &StmtImport{ Name: $2.str, Alias: $4.str, } } /* // resource bind | rbind { posLast(yylex, yyDollar) // our pos $$.stmt = $1.stmt } */ ; expr: BOOL { posLast(yylex, yyDollar) // our pos $$.expr = &ExprBool{ V: $1.bool, } } | STRING { posLast(yylex, yyDollar) // our pos $$.expr = &ExprStr{ V: $1.str, } } | INTEGER { posLast(yylex, yyDollar) // our pos $$.expr = &ExprInt{ V: $1.int, } } | FLOAT { posLast(yylex, yyDollar) // our pos $$.expr = &ExprFloat{ V: $1.float, } } | list { posLast(yylex, yyDollar) // our pos // TODO: list could be squashed in here directly... $$.expr = $1.expr } | map { posLast(yylex, yyDollar) // our pos // TODO: map could be squashed in here directly... $$.expr = $1.expr } | struct { posLast(yylex, yyDollar) // our pos // TODO: struct could be squashed in here directly... $$.expr = $1.expr } | call { posLast(yylex, yyDollar) // our pos // TODO: call could be squashed in here directly... $$.expr = $1.expr } | var { posLast(yylex, yyDollar) // our pos // TODO: var could be squashed in here directly... $$.expr = $1.expr } | func { posLast(yylex, yyDollar) // our pos // TODO: var could be squashed in here directly... $$.expr = $1.expr } | IF expr OPEN_CURLY expr CLOSE_CURLY ELSE OPEN_CURLY expr CLOSE_CURLY { posLast(yylex, yyDollar) // our pos $$.expr = &ExprIf{ Condition: $2.expr, ThenBranch: $4.expr, ElseBranch: $8.expr, } } // parenthesis wrap an expression for precedence | OPEN_PAREN expr CLOSE_PAREN { posLast(yylex, yyDollar) // our pos $$.expr = $2.expr } ; list: // `[42, 0, -13]` OPEN_BRACK list_elements CLOSE_BRACK { posLast(yylex, yyDollar) // our pos $$.expr = &ExprList{ Elements: $2.exprs, } } ; list_elements: /* end of list */ { posLast(yylex, yyDollar) // our pos $$.exprs = []interfaces.Expr{} } | list_elements list_element { posLast(yylex, yyDollar) // our pos $$.exprs = append($1.exprs, $2.expr) } ; list_element: expr COMMA { posLast(yylex, yyDollar) // our pos $$.expr = $1.expr } ; map: // `{"hello" => "there", "world" => "big",}` OPEN_CURLY map_kvs CLOSE_CURLY { posLast(yylex, yyDollar) // our pos $$.expr = &ExprMap{ KVs: $2.mapKVs, } } ; map_kvs: /* end of list */ { posLast(yylex, yyDollar) // our pos $$.mapKVs = []*ExprMapKV{} } | map_kvs map_kv { posLast(yylex, yyDollar) // our pos $$.mapKVs = append($1.mapKVs, $2.mapKV) } ; map_kv: expr ROCKET expr COMMA { posLast(yylex, yyDollar) // our pos $$.mapKV = &ExprMapKV{ Key: $1.expr, Val: $3.expr, } } ; struct: // `struct{answer => 0, truth => false, hello => "world",}` STRUCT_IDENTIFIER OPEN_CURLY struct_fields CLOSE_CURLY { posLast(yylex, yyDollar) // our pos $$.expr = &ExprStruct{ Fields: $3.structFields, } } ; struct_fields: /* end of list */ { posLast(yylex, yyDollar) // our pos $$.structFields = []*ExprStructField{} } | struct_fields struct_field { posLast(yylex, yyDollar) // our pos $$.structFields = append($1.structFields, $2.structField) } ; struct_field: IDENTIFIER ROCKET expr COMMA { posLast(yylex, yyDollar) // our pos $$.structField = &ExprStructField{ Name: $1.str, Value: $3.expr, } } ; call: // fmt.printf(...) dotted_identifier OPEN_PAREN call_args CLOSE_PAREN { posLast(yylex, yyDollar) // our pos $$.expr = &ExprCall{ Name: $1.str, Args: $3.exprs, //Var: false, // default } } // calling a function that's stored in a variable (a lambda) // `$foo(4, "hey")` # call function value | VAR_IDENTIFIER OPEN_PAREN call_args CLOSE_PAREN { posLast(yylex, yyDollar) // our pos $$.expr = &ExprCall{ Name: $1.str, Args: $3.exprs, // Instead of `Var: true`, we could have added a `$` // prefix to the Name, but I felt this was more elegant. Var: true, // lambda } } | expr PLUS expr { posLast(yylex, yyDollar) // our pos $$.expr = &ExprCall{ Name: operatorFuncName, Args: []interfaces.Expr{ &ExprStr{ // operator first V: $2.str, // for PLUS this is a `+` character }, $1.expr, $3.expr, }, } } | expr MINUS expr { posLast(yylex, yyDollar) // our pos $$.expr = &ExprCall{ Name: operatorFuncName, Args: []interfaces.Expr{ &ExprStr{ // operator first V: $2.str, }, $1.expr, $3.expr, }, } } | expr MULTIPLY expr { posLast(yylex, yyDollar) // our pos $$.expr = &ExprCall{ Name: operatorFuncName, Args: []interfaces.Expr{ &ExprStr{ // operator first V: $2.str, }, $1.expr, $3.expr, }, } } | expr DIVIDE expr { posLast(yylex, yyDollar) // our pos $$.expr = &ExprCall{ Name: operatorFuncName, Args: []interfaces.Expr{ &ExprStr{ // operator first V: $2.str, }, $1.expr, $3.expr, }, } } | expr EQ expr { posLast(yylex, yyDollar) // our pos $$.expr = &ExprCall{ Name: operatorFuncName, Args: []interfaces.Expr{ &ExprStr{ // operator first V: $2.str, }, $1.expr, $3.expr, }, } } | expr NEQ expr { posLast(yylex, yyDollar) // our pos $$.expr = &ExprCall{ Name: operatorFuncName, Args: []interfaces.Expr{ &ExprStr{ // operator first V: $2.str, }, $1.expr, $3.expr, }, } } | expr LT expr { posLast(yylex, yyDollar) // our pos $$.expr = &ExprCall{ Name: operatorFuncName, Args: []interfaces.Expr{ &ExprStr{ // operator first V: $2.str, }, $1.expr, $3.expr, }, } } | expr GT expr { posLast(yylex, yyDollar) // our pos $$.expr = &ExprCall{ Name: operatorFuncName, Args: []interfaces.Expr{ &ExprStr{ // operator first V: $2.str, }, $1.expr, $3.expr, }, } } | expr LTE expr { posLast(yylex, yyDollar) // our pos $$.expr = &ExprCall{ Name: operatorFuncName, Args: []interfaces.Expr{ &ExprStr{ // operator first V: $2.str, }, $1.expr, $3.expr, }, } } | expr GTE expr { posLast(yylex, yyDollar) // our pos $$.expr = &ExprCall{ Name: operatorFuncName, Args: []interfaces.Expr{ &ExprStr{ // operator first V: $2.str, }, $1.expr, $3.expr, }, } } | expr AND expr { posLast(yylex, yyDollar) // our pos $$.expr = &ExprCall{ Name: operatorFuncName, Args: []interfaces.Expr{ &ExprStr{ // operator first V: $2.str, }, $1.expr, $3.expr, }, } } | expr OR expr { posLast(yylex, yyDollar) // our pos $$.expr = &ExprCall{ Name: operatorFuncName, Args: []interfaces.Expr{ &ExprStr{ // operator first V: $2.str, }, $1.expr, $3.expr, }, } } | NOT expr { posLast(yylex, yyDollar) // our pos $$.expr = &ExprCall{ Name: operatorFuncName, Args: []interfaces.Expr{ &ExprStr{ // operator first V: $1.str, }, $2.expr, }, } } | VAR_IDENTIFIER_HX // get the N-th historical value, eg: $foo{3} is equivalent to: history($foo, 3) { posLast(yylex, yyDollar) // our pos $$.expr = &ExprCall{ Name: historyFuncName, Args: []interfaces.Expr{ &ExprVar{ Name: $1.str, }, &ExprInt{ V: $1.int, }, }, } } // TODO: fix conflicts with this method, and replace the above VAR_IDENTIFIER_HX // TODO: allow $pkg.ns.foo{4}, instead of $foo = $pkg.ns.foo ; $foo{4} // TODO: use this: dotted_var_identifier OPEN_BRACK INTEGER CLOSE_BRACK instead? //| dotted_var_identifier OPEN_CURLY INTEGER CLOSE_CURLY // { // posLast(yylex, yyDollar) // our pos // $$.expr = &ExprCall{ // Name: historyFuncName, // Args: []interfaces.Expr{ // &ExprVar{ // Name: $1.str, // }, // &ExprInt{ // V: $3.int, // }, // }, // } // } | expr IN expr { posLast(yylex, yyDollar) // our pos $$.expr = &ExprCall{ Name: containsFuncName, Args: []interfaces.Expr{ $1.expr, $3.expr, }, } } ; // list order gets us the position of the arg, but named params would work too! // this is also used by the include statement when the called class uses args! call_args: /* end of list */ { posLast(yylex, yyDollar) // our pos $$.exprs = []interfaces.Expr{} } // seems that "left recursion" works here... thanks parser generator! | call_args COMMA expr { posLast(yylex, yyDollar) // our pos $$.exprs = append($1.exprs, $3.expr) } | expr { posLast(yylex, yyDollar) // our pos $$.exprs = append([]interfaces.Expr{}, $1.expr) } ; var: dotted_var_identifier { posLast(yylex, yyDollar) // our pos $$.expr = &ExprVar{ Name: $1.str, } } ; func: // this is the lambda version, iow, a function as a value (expression) // `func() { }` // `func() { }` // `func(, ) { }` FUNC_IDENTIFIER OPEN_PAREN args CLOSE_PAREN OPEN_CURLY expr CLOSE_CURLY { posLast(yylex, yyDollar) // our pos $$.expr = &ExprFunc{ Args: $3.args, //Return: nil, Body: $6.expr, } } // `func(...) { }` | FUNC_IDENTIFIER OPEN_PAREN args CLOSE_PAREN type OPEN_CURLY expr CLOSE_CURLY { posLast(yylex, yyDollar) // our pos $$.expr = &ExprFunc{ Args: $3.args, Return: $5.typ, // return type is known Body: $7.expr, } isFullyTyped := $5.typ != nil // true if set m := make(map[string]*types.Type) ord := []string{} for _, a := range $3.args { if a.Type == nil { // at least one is unknown, can't run SetType... isFullyTyped = false break } m[a.Name] = a.Type ord = append(ord, a.Name) } if isFullyTyped { typ := &types.Type{ Kind: types.KindFunc, Map: m, Ord: ord, Out: $5.typ, } if err := $$.expr.SetType(typ); err != nil { // this will ultimately cause a parser error to occur... yylex.Error(fmt.Sprintf("%s: %+v", ErrParseSetType, err)) } } } ; args: /* end of list */ { posLast(yylex, yyDollar) // our pos $$.args = []*Arg{} } | args COMMA arg { posLast(yylex, yyDollar) // our pos $$.args = append($1.args, $3.arg) } | arg { posLast(yylex, yyDollar) // our pos $$.args = append([]*Arg{}, $1.arg) } ; arg: // `$x` VAR_IDENTIFIER { $$.arg = &Arg{ Name: $1.str, } } // `$x ` | VAR_IDENTIFIER type { $$.arg = &Arg{ Name: $1.str, Type: $2.typ, } } ; bind: // `$s = "hey"` VAR_IDENTIFIER EQUALS expr { posLast(yylex, yyDollar) // our pos $$.stmt = &StmtBind{ Ident: $1.str, Value: $3.expr, } } // `$x bool = true` // `$x int = if true { 42 } else { 13 }` | VAR_IDENTIFIER type EQUALS expr { posLast(yylex, yyDollar) // our pos var expr interfaces.Expr = $4.expr if err := expr.SetType($2.typ); err != nil { // this will ultimately cause a parser error to occur... yylex.Error(fmt.Sprintf("%s: %+v", ErrParseSetType, err)) } $$.stmt = &StmtBind{ Ident: $1.str, Value: expr, } } ; /* TODO: do we want to include this? // resource bind rbind: VAR_IDENTIFIER EQUALS resource { posLast(yylex, yyDollar) // our pos // XXX: this kind of bind is different than the others, because // it can only really be used for send->recv stuff, eg: // foo.SomeString -> bar.SomeOtherString $$.expr = &StmtBind{ Ident: $1.str, Value: $3.stmt, } } ; */ resource: // `file "/tmp/hello" { ... }` or `aws:ec2 "/tmp/hello" { ... }` RES_IDENTIFIER expr OPEN_CURLY resource_body CLOSE_CURLY { posLast(yylex, yyDollar) // our pos $$.stmt = &StmtRes{ Kind: $1.str, Name: $2.expr, Contents: $4.resContents, } } // note: this is a simplified version of the above if the lexer picks it // note: must not include underscores, but that is checked after parsing | IDENTIFIER expr OPEN_CURLY resource_body CLOSE_CURLY { posLast(yylex, yyDollar) // our pos $$.stmt = &StmtRes{ Kind: $1.str, Name: $2.expr, Contents: $4.resContents, } } ; resource_body: /* end of list */ { posLast(yylex, yyDollar) // our pos $$.resContents = []StmtResContents{} } | resource_body resource_field { posLast(yylex, yyDollar) // our pos $$.resContents = append($1.resContents, $2.resField) } | resource_body conditional_resource_field { posLast(yylex, yyDollar) // our pos $$.resContents = append($1.resContents, $2.resField) } | resource_body resource_edge { posLast(yylex, yyDollar) // our pos $$.resContents = append($1.resContents, $2.resEdge) } | resource_body conditional_resource_edge { posLast(yylex, yyDollar) // our pos $$.resContents = append($1.resContents, $2.resEdge) } | resource_body resource_meta { posLast(yylex, yyDollar) // our pos $$.resContents = append($1.resContents, $2.resMeta) } | resource_body conditional_resource_meta { posLast(yylex, yyDollar) // our pos $$.resContents = append($1.resContents, $2.resMeta) } | resource_body resource_meta_struct { posLast(yylex, yyDollar) // our pos $$.resContents = append($1.resContents, $2.resMeta) } | resource_body conditional_resource_meta_struct { posLast(yylex, yyDollar) // our pos $$.resContents = append($1.resContents, $2.resMeta) } ; resource_field: IDENTIFIER ROCKET expr COMMA { posLast(yylex, yyDollar) // our pos $$.resField = &StmtResField{ Field: $1.str, Value: $3.expr, } } ; conditional_resource_field: // content => $present ?: "hello", IDENTIFIER ROCKET expr ELVIS expr COMMA { posLast(yylex, yyDollar) // our pos $$.resField = &StmtResField{ Field: $1.str, Value: $5.expr, Condition: $3.expr, } } ; resource_edge: // Before => Test["t1"], CAPITALIZED_IDENTIFIER ROCKET edge_half COMMA { posLast(yylex, yyDollar) // our pos $$.resEdge = &StmtResEdge{ Property: $1.str, EdgeHalf: $3.edgeHalf, } } ; conditional_resource_edge: // Before => $present ?: Test["t1"], CAPITALIZED_IDENTIFIER ROCKET expr ELVIS edge_half COMMA { posLast(yylex, yyDollar) // our pos $$.resEdge = &StmtResEdge{ Property: $1.str, EdgeHalf: $5.edgeHalf, Condition: $3.expr, } } ; resource_meta: // Meta:noop => true, CAPITALIZED_IDENTIFIER COLON IDENTIFIER ROCKET expr COMMA { posLast(yylex, yyDollar) // our pos if strings.ToLower($1.str) != strings.ToLower(MetaField) { // this will ultimately cause a parser error to occur... yylex.Error(fmt.Sprintf("%s: %s", ErrParseResFieldInvalid, $1.str)) } $$.resMeta = &StmtResMeta{ Property: $3.str, MetaExpr: $5.expr, } } ; conditional_resource_meta: // Meta:limit => $present ?: 4, CAPITALIZED_IDENTIFIER COLON IDENTIFIER ROCKET expr ELVIS expr COMMA { posLast(yylex, yyDollar) // our pos if strings.ToLower($1.str) != strings.ToLower(MetaField) { // this will ultimately cause a parser error to occur... yylex.Error(fmt.Sprintf("%s: %s", ErrParseResFieldInvalid, $1.str)) } $$.resMeta = &StmtResMeta{ Property: $3.str, MetaExpr: $7.expr, Condition: $5.expr, } } ; resource_meta_struct: // Meta => struct{meta => true, retry => 3,}, CAPITALIZED_IDENTIFIER ROCKET expr COMMA { posLast(yylex, yyDollar) // our pos if strings.ToLower($1.str) != strings.ToLower(MetaField) { // this will ultimately cause a parser error to occur... yylex.Error(fmt.Sprintf("%s: %s", ErrParseResFieldInvalid, $1.str)) } $$.resMeta = &StmtResMeta{ Property: $1.str, MetaExpr: $3.expr, } } ; conditional_resource_meta_struct: // Meta => $present ?: struct{poll => 60, sema => ["foo:1", "bar:3",],}, CAPITALIZED_IDENTIFIER ROCKET expr ELVIS expr COMMA { posLast(yylex, yyDollar) // our pos if strings.ToLower($1.str) != strings.ToLower(MetaField) { // this will ultimately cause a parser error to occur... yylex.Error(fmt.Sprintf("%s: %s", ErrParseResFieldInvalid, $1.str)) } $$.resMeta = &StmtResMeta{ Property: $1.str, MetaExpr: $5.expr, Condition: $3.expr, } } ; edge: // TODO: we could technically prevent single edge_half pieces from being // parsed, but it's probably more work than is necessary... // Test["t1"] -> Test["t2"] -> Test["t3"] # chain or pair edge_half_list { posLast(yylex, yyDollar) // our pos $$.stmt = &StmtEdge{ EdgeHalfList: $1.edgeHalfList, //Notify: false, // unused here } } // Test["t1"].foo_send -> Test["t2"].blah_recv # send/recv | edge_half_sendrecv ARROW edge_half_sendrecv { posLast(yylex, yyDollar) // our pos $$.stmt = &StmtEdge{ EdgeHalfList: []*StmtEdgeHalf{ $1.edgeHalf, $3.edgeHalf, }, //Notify: false, // unused here, it is implied (i think) } } ; edge_half_list: edge_half { posLast(yylex, yyDollar) // our pos $$.edgeHalfList = []*StmtEdgeHalf{$1.edgeHalf} } | edge_half_list ARROW edge_half { posLast(yylex, yyDollar) // our pos $$.edgeHalfList = append($1.edgeHalfList, $3.edgeHalf) } ; edge_half: // eg: Test["t1"] capitalized_res_identifier OPEN_BRACK expr CLOSE_BRACK { posLast(yylex, yyDollar) // our pos $$.edgeHalf = &StmtEdgeHalf{ Kind: $1.str, Name: $3.expr, //SendRecv: "", // unused } } ; edge_half_sendrecv: // eg: Test["t1"].foo_send capitalized_res_identifier OPEN_BRACK expr CLOSE_BRACK DOT IDENTIFIER { posLast(yylex, yyDollar) // our pos $$.edgeHalf = &StmtEdgeHalf{ Kind: $1.str, Name: $3.expr, SendRecv: $6.str, } } ; type: BOOL_IDENTIFIER { posLast(yylex, yyDollar) // our pos $$.typ = types.NewType($1.str) // "bool" } | STR_IDENTIFIER { posLast(yylex, yyDollar) // our pos $$.typ = types.NewType($1.str) // "str" } | INT_IDENTIFIER { posLast(yylex, yyDollar) // our pos $$.typ = types.NewType($1.str) // "int" } | FLOAT_IDENTIFIER { posLast(yylex, yyDollar) // our pos $$.typ = types.NewType($1.str) // "float" } | OPEN_BRACK CLOSE_BRACK type // list: []int or [][]str (with recursion) { posLast(yylex, yyDollar) // our pos $$.typ = types.NewType("[]" + $3.typ.String()) } | MAP_IDENTIFIER OPEN_CURLY type COLON type CLOSE_CURLY // map: map{str: int} or map{str: []int} { posLast(yylex, yyDollar) // our pos $$.typ = types.NewType(fmt.Sprintf("map{%s: %s}", $3.typ.String(), $5.typ.String())) } | STRUCT_IDENTIFIER OPEN_CURLY type_struct_fields CLOSE_CURLY // struct: struct{} or struct{a bool} or struct{a bool; bb int} { posLast(yylex, yyDollar) // our pos $$.typ = types.NewType(fmt.Sprintf("%s{%s}", $1.str, strings.Join($3.strSlice, "; "))) } | FUNC_IDENTIFIER OPEN_PAREN type_func_args CLOSE_PAREN type // XXX: should we allow named args in the type signature? // func: func() float or func(bool) str or func(a bool, bb int) float { posLast(yylex, yyDollar) // our pos m := make(map[string]*types.Type) ord := []string{} for i, a := range $3.args { if a.Type == nil { // at least one is unknown, can't run SetType... // this means there is a programming error here! err := fmt.Errorf("type is unspecified for arg #%d", i) // this will ultimately cause a parser error to occur... yylex.Error(fmt.Sprintf("%s: %+v", ErrParseSetType, err)) break // safety } name := a.Name if name == "" { name = util.NumToAlpha(i) // if unspecified... } if util.StrInList(name, ord) { // duplicate arg name used err := fmt.Errorf("duplicate arg name of `%s`", name) // this will ultimately cause a parser error to occur... yylex.Error(fmt.Sprintf("%s: %+v", ErrParseSetType, err)) break // safety } m[name] = a.Type ord = append(ord, name) } $$.typ = &types.Type{ Kind: types.KindFunc, Map: m, Ord: ord, Out: $5.typ, } } | VARIANT_IDENTIFIER { posLast(yylex, yyDollar) // our pos $$.typ = types.NewType($1.str) // "variant" } ; type_struct_fields: /* end of list */ { posLast(yylex, yyDollar) // our pos $$.strSlice = []string{} } | type_struct_fields SEMICOLON type_struct_field { posLast(yylex, yyDollar) // our pos $$.strSlice = append($1.strSlice, $3.str) } | type_struct_field { posLast(yylex, yyDollar) // our pos $$.strSlice = []string{$1.str} } ; type_struct_field: IDENTIFIER type { posLast(yylex, yyDollar) // our pos $$.str = fmt.Sprintf("%s %s", $1.str, $2.typ.String()) } ; type_func_args: /* end of list */ { posLast(yylex, yyDollar) // our pos $$.args = []*Arg{} } | type_func_args COMMA type_func_arg { posLast(yylex, yyDollar) // our pos $$.args = append($1.args, $3.arg) } | type_func_arg { posLast(yylex, yyDollar) // our pos $$.args = append([]*Arg{}, $1.arg) //$$.args = []*Arg{$1.arg} // TODO: is this equivalent? } ; type_func_arg: // `` type { $$.arg = &Arg{ Type: $1.typ, } } // `$x ` // XXX: should we allow specifying the arg name here? | VAR_IDENTIFIER type { $$.arg = &Arg{ Name: $1.str, Type: $2.typ, } } ; dotted_identifier: IDENTIFIER { posLast(yylex, yyDollar) // our pos $$.str = $1.str } | dotted_identifier DOT IDENTIFIER { posLast(yylex, yyDollar) // our pos $$.str = $1.str + interfaces.ModuleSep + $3.str } ; // there are different ways the lexer/parser might choose to represent this... dotted_var_identifier: // eg: $foo (no dots) VAR_IDENTIFIER { posLast(yylex, yyDollar) // our pos $$.str = $1.str } // eg: $foo . bar.baz (identifier + dotted identifier) | VAR_IDENTIFIER DOT dotted_identifier { posLast(yylex, yyDollar) // our pos $$.str = $1.str + interfaces.ModuleSep + $3.str } // eg: $ foo.bar.baz (dollar prefix + dotted identifier) | DOLLAR dotted_identifier { posLast(yylex, yyDollar) // our pos $$.str = $2.str } ; capitalized_res_identifier: CAPITALIZED_IDENTIFIER { posLast(yylex, yyDollar) // our pos $$.str = $1.str } | capitalized_res_identifier COLON IDENTIFIER { posLast(yylex, yyDollar) // our pos $$.str = $1.str + ":" + $3.str } ; %% // pos is a helper function used to track the position in the parser. func pos(y yyLexer, dollar yySymType) { lp := cast(y) lp.row = dollar.row lp.col = dollar.col // FIXME: in some cases the before last value is most meaningful... //lp.row = append(lp.row, dollar.row) //lp.col = append(lp.col, dollar.col) //log.Printf("parse: %d x %d", lp.row, lp.col) return } // cast is used to pull out the parser run-specific struct we store our AST in. // this is usually called in the parser. func cast(y yyLexer) *lexParseAST { x := y.(*Lexer).parseResult return x.(*lexParseAST) } // postLast pulls out the "last token" and does a pos with that. This is a hack! func posLast(y yyLexer, dollars []yySymType) { // pick the last token in the set matched by the parser pos(y, dollars[len(dollars)-1]) // our pos } // cast is used to pull out the parser run-specific struct we store our AST in. // this is usually called in the lexer. func (yylex *Lexer) cast() *lexParseAST { return yylex.parseResult.(*lexParseAST) } // pos is a helper function used to track the position in the lexer. func (yylex *Lexer) pos(lval *yySymType) { lval.row = yylex.Line() lval.col = yylex.Column() // TODO: we could use: `s := yylex.Text()` to calculate a delta length! //log.Printf("lexer: %d x %d", lval.row, lval.col) } // Error is the error handler which gets called on a parsing error. func (yylex *Lexer) Error(str string) { lp := yylex.cast() if str != "" { // This error came from the parser. It is usually also set when // the lexer fails, because it ends up generating ERROR tokens, // which most parsers usually don't match and store in the AST. err := ErrParseError // TODO: add more specific types... if strings.HasSuffix(str, ErrParseAdditionalEquals.Error()) { err = ErrParseAdditionalEquals } else if strings.HasSuffix(str, ErrParseExpectingComma.Error()) { err = ErrParseExpectingComma } else if strings.HasPrefix(str, ErrParseSetType.Error()) { err = ErrParseSetType } lp.parseErr = &LexParseErr{ Err: err, Str: str, // FIXME: get these values, by tracking pos in parser... // FIXME: currently, the values we get are mostly wrong! Row: lp.row, //lp.row[len(lp.row)-1], Col: lp.col, //lp.col[len(lp.col)-1], } } }