This adds a giant missing piece of the language: proper function values! It is lovely to now understand why early programming language designers didn't implement these, but a joy to now reap the benefits of them. In adding these, many other changes had to be made to get them to "fit" correctly. This improved the code and fixed a number of bugs. Unfortunately this touched many areas of the code, and since I was learning how to do all of this for the first time, I've squashed most of my work into a single commit. Some more information: * This adds over 70 new tests to verify the new functionality. * Functions, global variables, and classes can all be implemented natively in mcl and built into core packages. * A new compiler step called "Ordering" was added. It is called by the SetScope step, and determines statement ordering and shadowing precedence formally. It helped remove at least one bug and provided the additional analysis required to properly capture variables when implementing function generators and closures. * The type unification code was improved to handle the new cases. * Light copying of Node's allowed our function graphs to be more optimal and share common vertices and edges. For example, if two different closures capture a variable $x, they'll both use the same copy when running the function, since the compiler can prove if they're identical. * Some areas still need improvements, but this is ready for mainstream testing and use!
1397 lines
31 KiB
Plaintext
1397 lines
31 KiB
Plaintext
// Mgmt
|
|
// Copyright (C) 2013-2019+ James Shubin and the project contributors
|
|
// Written by James Shubin <james@shubin.ca> 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 <http://www.gnu.org/licenses/>.
|
|
|
|
%{
|
|
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() { <expr> }`
|
|
// `func name(<arg>) { <expr> }`
|
|
// `func name(<arg>, <arg>) { <expr> }`
|
|
| 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(...) <type> { <expr> }`
|
|
| 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 { <prog> }`
|
|
| 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(<arg>) { <prog> }`
|
|
// `class name(<arg>, <arg>) { <prog> }`
|
|
| 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() { <expr> }`
|
|
// `func(<arg>) { <expr> }`
|
|
// `func(<arg>, <arg>) { <expr> }`
|
|
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(...) <type> { <expr> }`
|
|
| 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 <type>`
|
|
| 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>`
|
|
type
|
|
{
|
|
$$.arg = &Arg{
|
|
Type: $1.typ,
|
|
}
|
|
}
|
|
// `$x <type>`
|
|
// 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],
|
|
}
|
|
}
|
|
}
|