1534 lines
36 KiB
Plaintext
1534 lines
36 KiB
Plaintext
// Mgmt
|
|
// Copyright (C) 2013-2024+ 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 <https://www.gnu.org/licenses/>.
|
|
//
|
|
// Additional permission under GNU GPL version 3 section 7
|
|
//
|
|
// If you modify this program, or any covered work, by linking or combining it
|
|
// with embedded mcl code and modules (and that the embedded mcl code and
|
|
// modules which link with this program, contain a copy of their source code in
|
|
// the authoritative form) containing parts covered by the terms of any other
|
|
// license, the licensors of this program grant you additional permission to
|
|
// convey the resulting work. Furthermore, the licensors of this program grant
|
|
// the original author, James Shubin, additional permission to update this
|
|
// additional permission if he deems it necessary to achieve the goals of this
|
|
// additional permission.
|
|
|
|
%{
|
|
package parser
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/purpleidea/mgmt/lang/ast"
|
|
"github.com/purpleidea/mgmt/lang/funcs"
|
|
"github.com/purpleidea/mgmt/lang/funcs/operators"
|
|
"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
|
|
|
|
typ *types.Type
|
|
|
|
stmts []interfaces.Stmt
|
|
stmt interfaces.Stmt
|
|
|
|
exprs []interfaces.Expr
|
|
expr interfaces.Expr
|
|
|
|
mapKVs []*ast.ExprMapKV
|
|
mapKV *ast.ExprMapKV
|
|
|
|
structFields []*ast.ExprStructField
|
|
structField *ast.ExprStructField
|
|
|
|
args []*interfaces.Arg
|
|
arg *interfaces.Arg
|
|
|
|
resContents []ast.StmtResContents // interface
|
|
resField *ast.StmtResField
|
|
resEdge *ast.StmtResEdge
|
|
resMeta *ast.StmtResMeta
|
|
|
|
edgeHalfList []*ast.StmtEdgeHalf
|
|
edgeHalf *ast.StmtEdgeHalf
|
|
}
|
|
|
|
%token OPEN_CURLY CLOSE_CURLY
|
|
%token OPEN_PAREN CLOSE_PAREN
|
|
%token OPEN_BRACK CLOSE_BRACK
|
|
%token IF ELSE
|
|
%token BOOL STRING INTEGER FLOAT
|
|
%token EQUALS DOLLAR
|
|
%token COMMA COLON SEMICOLON
|
|
%token ELVIS DEFAULT ROCKET ARROW DOT
|
|
%token BOOL_IDENTIFIER STR_IDENTIFIER INT_IDENTIFIER FLOAT_IDENTIFIER
|
|
%token MAP_IDENTIFIER STRUCT_IDENTIFIER VARIANT_IDENTIFIER
|
|
%token IDENTIFIER CAPITALIZED_IDENTIFIER
|
|
%token FUNC_IDENTIFIER
|
|
%token CLASS_IDENTIFIER INCLUDE_IDENTIFIER
|
|
%token IMPORT_IDENTIFIER AS_IDENTIFIER
|
|
%token COMMENT ERROR
|
|
%token PANIC_IDENTIFIER
|
|
|
|
// 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 ARROW // XXX: is %nonassoc correct for this?
|
|
%nonassoc DEFAULT // XXX: is %nonassoc correct for this?
|
|
%nonassoc OPEN_BRACK // XXX: is %nonassoc correct for this?
|
|
%nonassoc IN // XXX: 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 = &ast.StmtProg{
|
|
Body: []interfaces.Stmt{},
|
|
}
|
|
}
|
|
| prog stmt
|
|
{
|
|
posLast(yylex, yyDollar) // our pos
|
|
// TODO: should we just skip comments for now?
|
|
//if _, ok := $2.stmt.(*ast.StmtComment); !ok {
|
|
//}
|
|
if stmt, ok := $1.stmt.(*ast.StmtProg); ok {
|
|
stmts := stmt.Body
|
|
stmts = append(stmts, $2.stmt)
|
|
$$.stmt = &ast.StmtProg{
|
|
Body: stmts,
|
|
}
|
|
}
|
|
}
|
|
;
|
|
stmt:
|
|
COMMENT
|
|
{
|
|
posLast(yylex, yyDollar) // our pos
|
|
$$.stmt = &ast.StmtComment{
|
|
Value: $1.str,
|
|
}
|
|
}
|
|
| bind
|
|
{
|
|
posLast(yylex, yyDollar) // our pos
|
|
$$.stmt = $1.stmt
|
|
}
|
|
| panic
|
|
{
|
|
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 = &ast.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 = &ast.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 = &ast.StmtFunc{
|
|
Name: $2.str,
|
|
Func: &ast.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 := &ast.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)
|
|
}
|
|
var typ *types.Type
|
|
if isFullyTyped {
|
|
typ = &types.Type{
|
|
Kind: types.KindFunc,
|
|
Map: m,
|
|
Ord: ord,
|
|
Out: $6.typ,
|
|
}
|
|
// XXX: We might still need to do this for now...
|
|
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 = &ast.StmtFunc{
|
|
Name: $2.str,
|
|
Func: fn,
|
|
Type: typ, // sam says add the type here instead...
|
|
}
|
|
}
|
|
// `class name { <prog> }`
|
|
| CLASS_IDENTIFIER colon_identifier OPEN_CURLY prog CLOSE_CURLY
|
|
{
|
|
posLast(yylex, yyDollar) // our pos
|
|
$$.stmt = &ast.StmtClass{
|
|
Name: $2.str,
|
|
Args: nil,
|
|
Body: $4.stmt,
|
|
}
|
|
}
|
|
// `class name(<arg>) { <prog> }`
|
|
// `class name(<arg>, <arg>) { <prog> }`
|
|
| CLASS_IDENTIFIER colon_identifier OPEN_PAREN args CLOSE_PAREN OPEN_CURLY prog CLOSE_CURLY
|
|
{
|
|
posLast(yylex, yyDollar) // our pos
|
|
$$.stmt = &ast.StmtClass{
|
|
Name: $2.str,
|
|
Args: $4.args,
|
|
Body: $7.stmt,
|
|
}
|
|
}
|
|
// `include name`
|
|
| INCLUDE_IDENTIFIER dotted_identifier
|
|
{
|
|
posLast(yylex, yyDollar) // our pos
|
|
$$.stmt = &ast.StmtInclude{
|
|
Name: $2.str,
|
|
}
|
|
}
|
|
// `include name(...)`
|
|
| INCLUDE_IDENTIFIER dotted_identifier OPEN_PAREN call_args CLOSE_PAREN
|
|
{
|
|
posLast(yylex, yyDollar) // our pos
|
|
$$.stmt = &ast.StmtInclude{
|
|
Name: $2.str,
|
|
Args: $4.exprs,
|
|
}
|
|
}
|
|
// `include name as foo`
|
|
// TODO: should we support: `include name as *`
|
|
| INCLUDE_IDENTIFIER dotted_identifier AS_IDENTIFIER IDENTIFIER
|
|
{
|
|
posLast(yylex, yyDollar) // our pos
|
|
$$.stmt = &ast.StmtInclude{
|
|
Name: $2.str,
|
|
Alias: $4.str,
|
|
}
|
|
}
|
|
// `include name(...) as foo`
|
|
// TODO: should we support: `include name(...) as *`
|
|
| INCLUDE_IDENTIFIER dotted_identifier OPEN_PAREN call_args CLOSE_PAREN AS_IDENTIFIER IDENTIFIER
|
|
{
|
|
posLast(yylex, yyDollar) // our pos
|
|
$$.stmt = &ast.StmtInclude{
|
|
Name: $2.str,
|
|
Args: $4.exprs,
|
|
Alias: $7.str,
|
|
}
|
|
}
|
|
// `import "name"`
|
|
| IMPORT_IDENTIFIER STRING
|
|
{
|
|
posLast(yylex, yyDollar) // our pos
|
|
$$.stmt = &ast.StmtImport{
|
|
Name: $2.str,
|
|
//Alias: "",
|
|
}
|
|
}
|
|
// `import "name" as alias`
|
|
| IMPORT_IDENTIFIER STRING AS_IDENTIFIER IDENTIFIER
|
|
{
|
|
posLast(yylex, yyDollar) // our pos
|
|
$$.stmt = &ast.StmtImport{
|
|
Name: $2.str,
|
|
Alias: $4.str,
|
|
}
|
|
}
|
|
// `import "name" as *`
|
|
| IMPORT_IDENTIFIER STRING AS_IDENTIFIER MULTIPLY
|
|
{
|
|
posLast(yylex, yyDollar) // our pos
|
|
$$.stmt = &ast.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 = &ast.ExprBool{
|
|
V: $1.bool,
|
|
}
|
|
}
|
|
| STRING
|
|
{
|
|
posLast(yylex, yyDollar) // our pos
|
|
$$.expr = &ast.ExprStr{
|
|
V: $1.str,
|
|
}
|
|
}
|
|
| INTEGER
|
|
{
|
|
posLast(yylex, yyDollar) // our pos
|
|
$$.expr = &ast.ExprInt{
|
|
V: $1.int,
|
|
}
|
|
}
|
|
| FLOAT
|
|
{
|
|
posLast(yylex, yyDollar) // our pos
|
|
$$.expr = &ast.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 = &ast.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 = &ast.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 = &ast.ExprMap{
|
|
KVs: $2.mapKVs,
|
|
}
|
|
}
|
|
;
|
|
map_kvs:
|
|
/* end of list */
|
|
{
|
|
posLast(yylex, yyDollar) // our pos
|
|
$$.mapKVs = []*ast.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 = &ast.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 = &ast.ExprStruct{
|
|
Fields: $3.structFields,
|
|
}
|
|
}
|
|
;
|
|
struct_fields:
|
|
/* end of list */
|
|
{
|
|
posLast(yylex, yyDollar) // our pos
|
|
$$.structFields = []*ast.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 = &ast.ExprStructField{
|
|
Name: $1.str,
|
|
Value: $3.expr,
|
|
}
|
|
}
|
|
;
|
|
call:
|
|
// fmt.printf(...)
|
|
// iter.map(...)
|
|
dotted_identifier OPEN_PAREN call_args CLOSE_PAREN
|
|
{
|
|
posLast(yylex, yyDollar) // our pos
|
|
$$.expr = &ast.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
|
|
| dotted_var_identifier OPEN_PAREN call_args CLOSE_PAREN
|
|
{
|
|
posLast(yylex, yyDollar) // our pos
|
|
$$.expr = &ast.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 = &ast.ExprCall{
|
|
Name: operators.OperatorFuncName,
|
|
Args: []interfaces.Expr{
|
|
&ast.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 = &ast.ExprCall{
|
|
Name: operators.OperatorFuncName,
|
|
Args: []interfaces.Expr{
|
|
&ast.ExprStr{ // operator first
|
|
V: $2.str,
|
|
},
|
|
$1.expr,
|
|
$3.expr,
|
|
},
|
|
}
|
|
}
|
|
| expr MULTIPLY expr
|
|
{
|
|
posLast(yylex, yyDollar) // our pos
|
|
$$.expr = &ast.ExprCall{
|
|
Name: operators.OperatorFuncName,
|
|
Args: []interfaces.Expr{
|
|
&ast.ExprStr{ // operator first
|
|
V: $2.str,
|
|
},
|
|
$1.expr,
|
|
$3.expr,
|
|
},
|
|
}
|
|
}
|
|
| expr DIVIDE expr
|
|
{
|
|
posLast(yylex, yyDollar) // our pos
|
|
$$.expr = &ast.ExprCall{
|
|
Name: operators.OperatorFuncName,
|
|
Args: []interfaces.Expr{
|
|
&ast.ExprStr{ // operator first
|
|
V: $2.str,
|
|
},
|
|
$1.expr,
|
|
$3.expr,
|
|
},
|
|
}
|
|
}
|
|
| expr EQ expr
|
|
{
|
|
posLast(yylex, yyDollar) // our pos
|
|
$$.expr = &ast.ExprCall{
|
|
Name: operators.OperatorFuncName,
|
|
Args: []interfaces.Expr{
|
|
&ast.ExprStr{ // operator first
|
|
V: $2.str,
|
|
},
|
|
$1.expr,
|
|
$3.expr,
|
|
},
|
|
}
|
|
}
|
|
| expr NEQ expr
|
|
{
|
|
posLast(yylex, yyDollar) // our pos
|
|
$$.expr = &ast.ExprCall{
|
|
Name: operators.OperatorFuncName,
|
|
Args: []interfaces.Expr{
|
|
&ast.ExprStr{ // operator first
|
|
V: $2.str,
|
|
},
|
|
$1.expr,
|
|
$3.expr,
|
|
},
|
|
}
|
|
}
|
|
| expr LT expr
|
|
{
|
|
posLast(yylex, yyDollar) // our pos
|
|
$$.expr = &ast.ExprCall{
|
|
Name: operators.OperatorFuncName,
|
|
Args: []interfaces.Expr{
|
|
&ast.ExprStr{ // operator first
|
|
V: $2.str,
|
|
},
|
|
$1.expr,
|
|
$3.expr,
|
|
},
|
|
}
|
|
}
|
|
| expr GT expr
|
|
{
|
|
posLast(yylex, yyDollar) // our pos
|
|
$$.expr = &ast.ExprCall{
|
|
Name: operators.OperatorFuncName,
|
|
Args: []interfaces.Expr{
|
|
&ast.ExprStr{ // operator first
|
|
V: $2.str,
|
|
},
|
|
$1.expr,
|
|
$3.expr,
|
|
},
|
|
}
|
|
}
|
|
| expr LTE expr
|
|
{
|
|
posLast(yylex, yyDollar) // our pos
|
|
$$.expr = &ast.ExprCall{
|
|
Name: operators.OperatorFuncName,
|
|
Args: []interfaces.Expr{
|
|
&ast.ExprStr{ // operator first
|
|
V: $2.str,
|
|
},
|
|
$1.expr,
|
|
$3.expr,
|
|
},
|
|
}
|
|
}
|
|
| expr GTE expr
|
|
{
|
|
posLast(yylex, yyDollar) // our pos
|
|
$$.expr = &ast.ExprCall{
|
|
Name: operators.OperatorFuncName,
|
|
Args: []interfaces.Expr{
|
|
&ast.ExprStr{ // operator first
|
|
V: $2.str,
|
|
},
|
|
$1.expr,
|
|
$3.expr,
|
|
},
|
|
}
|
|
}
|
|
| expr AND expr
|
|
{
|
|
posLast(yylex, yyDollar) // our pos
|
|
$$.expr = &ast.ExprCall{
|
|
Name: operators.OperatorFuncName,
|
|
Args: []interfaces.Expr{
|
|
&ast.ExprStr{ // operator first
|
|
V: $2.str,
|
|
},
|
|
$1.expr,
|
|
$3.expr,
|
|
},
|
|
}
|
|
}
|
|
| expr OR expr
|
|
{
|
|
posLast(yylex, yyDollar) // our pos
|
|
$$.expr = &ast.ExprCall{
|
|
Name: operators.OperatorFuncName,
|
|
Args: []interfaces.Expr{
|
|
&ast.ExprStr{ // operator first
|
|
V: $2.str,
|
|
},
|
|
$1.expr,
|
|
$3.expr,
|
|
},
|
|
}
|
|
}
|
|
| NOT expr
|
|
{
|
|
posLast(yylex, yyDollar) // our pos
|
|
$$.expr = &ast.ExprCall{
|
|
Name: operators.OperatorFuncName,
|
|
Args: []interfaces.Expr{
|
|
&ast.ExprStr{ // operator first
|
|
V: $1.str,
|
|
},
|
|
$2.expr,
|
|
},
|
|
}
|
|
}
|
|
// lookup an index in a list or a key in a map
|
|
// lookup($foo, $key)
|
|
// `$foo[$key]` // no default specifier
|
|
| expr OPEN_BRACK expr CLOSE_BRACK
|
|
{
|
|
posLast(yylex, yyDollar) // our pos
|
|
$$.expr = &ast.ExprCall{
|
|
Name: funcs.LookupFuncName,
|
|
Args: []interfaces.Expr{
|
|
$1.expr, // the list or map
|
|
$3.expr, // the index or key is an expr
|
|
//$6.expr, // the default
|
|
},
|
|
}
|
|
}
|
|
// lookup an index in a list or a key in a map with a default
|
|
// lookup_default($foo, $key, $default)
|
|
// `$foo[$key] || "default"`
|
|
| expr OPEN_BRACK expr CLOSE_BRACK DEFAULT expr
|
|
{
|
|
posLast(yylex, yyDollar) // our pos
|
|
$$.expr = &ast.ExprCall{
|
|
Name: funcs.LookupDefaultFuncName,
|
|
Args: []interfaces.Expr{
|
|
$1.expr, // the list or map
|
|
$3.expr, // the index or key is an expr
|
|
$6.expr, // the default
|
|
},
|
|
}
|
|
}
|
|
// lookup a field in a struct
|
|
// _struct_lookup($foo, "field")
|
|
// $foo->field
|
|
| expr ARROW IDENTIFIER
|
|
{
|
|
posLast(yylex, yyDollar) // our pos
|
|
$$.expr = &ast.ExprCall{
|
|
Name: funcs.StructLookupFuncName,
|
|
Args: []interfaces.Expr{
|
|
$1.expr, // the struct
|
|
&ast.ExprStr{
|
|
V: $3.str, // the field is always an str
|
|
},
|
|
//$5.expr, // the default
|
|
},
|
|
}
|
|
}
|
|
// lookup a field in a struct with a default
|
|
// _struct_lookup_optional($foo, "field", "default")
|
|
// $foo->field || "default"
|
|
| expr ARROW IDENTIFIER DEFAULT expr
|
|
{
|
|
posLast(yylex, yyDollar) // our pos
|
|
$$.expr = &ast.ExprCall{
|
|
Name: funcs.StructLookupOptionalFuncName,
|
|
Args: []interfaces.Expr{
|
|
$1.expr, // the struct
|
|
&ast.ExprStr{
|
|
V: $3.str, // the field is always an str
|
|
},
|
|
$5.expr, // the default
|
|
},
|
|
}
|
|
}
|
|
| expr IN expr
|
|
{
|
|
posLast(yylex, yyDollar) // our pos
|
|
$$.expr = &ast.ExprCall{
|
|
Name: funcs.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 = &ast.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 = &ast.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 = &ast.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 = []*interfaces.Arg{}
|
|
}
|
|
| args COMMA arg
|
|
{
|
|
posLast(yylex, yyDollar) // our pos
|
|
$$.args = append($1.args, $3.arg)
|
|
}
|
|
| arg
|
|
{
|
|
posLast(yylex, yyDollar) // our pos
|
|
$$.args = append([]*interfaces.Arg{}, $1.arg)
|
|
}
|
|
;
|
|
arg:
|
|
// `$x`
|
|
var_identifier
|
|
{
|
|
$$.arg = &interfaces.Arg{
|
|
Name: $1.str,
|
|
}
|
|
}
|
|
// `$x <type>`
|
|
| var_identifier type
|
|
{
|
|
$$.arg = &interfaces.Arg{
|
|
Name: $1.str,
|
|
Type: $2.typ,
|
|
}
|
|
}
|
|
;
|
|
bind:
|
|
// `$s = "hey"`
|
|
var_identifier EQUALS expr
|
|
{
|
|
posLast(yylex, yyDollar) // our pos
|
|
$$.stmt = &ast.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
|
|
// XXX: We still need to do this for now it seems...
|
|
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 = &ast.StmtBind{
|
|
Ident: $1.str,
|
|
Value: expr,
|
|
Type: $2.typ, // sam says add the type here instead...
|
|
}
|
|
}
|
|
;
|
|
panic:
|
|
// panic("some error")
|
|
// generates:
|
|
// if panic("some error") {
|
|
// _panic "_panic" {} # resource
|
|
//}
|
|
PANIC_IDENTIFIER OPEN_PAREN call_args CLOSE_PAREN
|
|
{
|
|
posLast(yylex, yyDollar) // our pos
|
|
call := &ast.ExprCall{
|
|
Name: $1.str, // the function name
|
|
Args: $3.exprs,
|
|
//Var: false, // default
|
|
}
|
|
name := &ast.ExprStr{
|
|
V: $1.str, // any constant, non-empty name
|
|
}
|
|
res := &ast.StmtRes{
|
|
Kind: interfaces.PanicResKind,
|
|
Name: name,
|
|
Contents: []ast.StmtResContents{},
|
|
}
|
|
$$.stmt = &ast.StmtIf{
|
|
Condition: call,
|
|
ThenBranch: res,
|
|
//ElseBranch: nil,
|
|
}
|
|
}
|
|
;
|
|
/* 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 = &ast.StmtBind{
|
|
Ident: $1.str,
|
|
Value: $3.stmt,
|
|
}
|
|
}
|
|
;
|
|
*/
|
|
resource:
|
|
// `file "/tmp/hello" { ... }` or `aws:ec2 "/tmp/hello" { ... }`
|
|
colon_identifier expr OPEN_CURLY resource_body CLOSE_CURLY
|
|
{
|
|
posLast(yylex, yyDollar) // our pos
|
|
$$.stmt = &ast.StmtRes{
|
|
Kind: $1.str,
|
|
Name: $2.expr,
|
|
Contents: $4.resContents,
|
|
}
|
|
}
|
|
;
|
|
resource_body:
|
|
/* end of list */
|
|
{
|
|
posLast(yylex, yyDollar) // our pos
|
|
$$.resContents = []ast.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 = &ast.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 = &ast.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 = &ast.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 = &ast.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(ast.MetaField) {
|
|
// this will ultimately cause a parser error to occur...
|
|
yylex.Error(fmt.Sprintf("%s: %s", ErrParseResFieldInvalid, $1.str))
|
|
}
|
|
$$.resMeta = &ast.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(ast.MetaField) {
|
|
// this will ultimately cause a parser error to occur...
|
|
yylex.Error(fmt.Sprintf("%s: %s", ErrParseResFieldInvalid, $1.str))
|
|
}
|
|
$$.resMeta = &ast.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(ast.MetaField) {
|
|
// this will ultimately cause a parser error to occur...
|
|
yylex.Error(fmt.Sprintf("%s: %s", ErrParseResFieldInvalid, $1.str))
|
|
}
|
|
$$.resMeta = &ast.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(ast.MetaField) {
|
|
// this will ultimately cause a parser error to occur...
|
|
yylex.Error(fmt.Sprintf("%s: %s", ErrParseResFieldInvalid, $1.str))
|
|
}
|
|
$$.resMeta = &ast.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 = &ast.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 = &ast.StmtEdge{
|
|
EdgeHalfList: []*ast.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 = []*ast.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 = &ast.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 = &ast.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
|
|
|
|
names := make(map[string]struct{})
|
|
strs := []string{}
|
|
for _, arg := range $3.args {
|
|
s := fmt.Sprintf("%s %s", arg.Name, arg.Type.String())
|
|
if _, exists := names[arg.Name]; exists {
|
|
// duplicate field name used
|
|
err := fmt.Errorf("duplicate struct field of `%s`", s)
|
|
// this will ultimately cause a parser error to occur...
|
|
yylex.Error(fmt.Sprintf("%s: %+v", ErrParseSetType, err))
|
|
break // we must skip, because code continues!
|
|
}
|
|
names[arg.Name] = struct{}{}
|
|
strs = append(strs, s)
|
|
}
|
|
|
|
$$.typ = types.NewType(fmt.Sprintf("%s{%s}", $1.str, strings.Join(strs, "; ")))
|
|
}
|
|
| 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
|
|
$$.args = []*interfaces.Arg{}
|
|
}
|
|
| type_struct_fields SEMICOLON type_struct_field
|
|
{
|
|
posLast(yylex, yyDollar) // our pos
|
|
$$.args = append($1.args, $3.arg)
|
|
}
|
|
| type_struct_field
|
|
{
|
|
posLast(yylex, yyDollar) // our pos
|
|
$$.args = append([]*interfaces.Arg{}, $1.arg)
|
|
}
|
|
;
|
|
type_struct_field:
|
|
IDENTIFIER type
|
|
{
|
|
posLast(yylex, yyDollar) // our pos
|
|
$$.arg = &interfaces.Arg{ // re-use the Arg struct
|
|
Name: $1.str,
|
|
Type: $2.typ,
|
|
}
|
|
}
|
|
;
|
|
type_func_args:
|
|
/* end of list */
|
|
{
|
|
posLast(yylex, yyDollar) // our pos
|
|
$$.args = []*interfaces.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([]*interfaces.Arg{}, $1.arg)
|
|
//$$.args = []*interfaces.Arg{$1.arg} // TODO: is this equivalent?
|
|
}
|
|
;
|
|
type_func_arg:
|
|
// `<type>`
|
|
type
|
|
{
|
|
$$.arg = &interfaces.Arg{
|
|
Type: $1.typ,
|
|
}
|
|
}
|
|
// `$x <type>`
|
|
// XXX: should we allow specifying the arg name here?
|
|
| var_identifier type
|
|
{
|
|
$$.arg = &interfaces.Arg{
|
|
Name: $1.str,
|
|
Type: $2.typ,
|
|
}
|
|
}
|
|
;
|
|
undotted_identifier:
|
|
IDENTIFIER
|
|
{
|
|
posLast(yylex, yyDollar) // our pos
|
|
$$.str = $1.str
|
|
}
|
|
// a function could be named map()!
|
|
| MAP_IDENTIFIER
|
|
{
|
|
posLast(yylex, yyDollar) // our pos
|
|
$$.str = $1.str
|
|
}
|
|
;
|
|
var_identifier:
|
|
// eg: $ foo (dollar prefix + identifier)
|
|
DOLLAR undotted_identifier
|
|
{
|
|
posLast(yylex, yyDollar) // our pos
|
|
$$.str = $2.str // don't include the leading $
|
|
}
|
|
;
|
|
colon_identifier:
|
|
// eg: `foo`
|
|
IDENTIFIER
|
|
{
|
|
posLast(yylex, yyDollar) // our pos
|
|
$$.str = $1.str
|
|
}
|
|
// eg: `foo:bar` (used in `docker:image` or `class base:inner:deeper`)
|
|
| colon_identifier COLON IDENTIFIER
|
|
{
|
|
posLast(yylex, yyDollar) // our pos
|
|
$$.str = $1.str + $2.str + $3.str
|
|
}
|
|
;
|
|
dotted_identifier:
|
|
undotted_identifier
|
|
{
|
|
posLast(yylex, yyDollar) // our pos
|
|
$$.str = $1.str
|
|
}
|
|
| dotted_identifier DOT undotted_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.bar.baz (dollar prefix + dotted identifier)
|
|
DOLLAR dotted_identifier
|
|
{
|
|
posLast(yylex, yyDollar) // our pos
|
|
$$.str = $2.str // don't include the leading $
|
|
}
|
|
;
|
|
capitalized_res_identifier:
|
|
CAPITALIZED_IDENTIFIER
|
|
{
|
|
posLast(yylex, yyDollar) // our pos
|
|
$$.str = $1.str
|
|
}
|
|
| capitalized_res_identifier COLON CAPITALIZED_IDENTIFIER
|
|
{
|
|
posLast(yylex, yyDollar) // our pos
|
|
$$.str = $1.str + $2.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)
|
|
}
|
|
|
|
// posLast 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],
|
|
}
|
|
}
|
|
}
|