diff --git a/lang/lexer.nex b/lang/lexer.nex index 51b91b5a..8a8a9179 100644 --- a/lang/lexer.nex +++ b/lang/lexer.nex @@ -149,6 +149,11 @@ lval.str = yylex.Text() return DOT } +/\$/ { + yylex.pos(lval) // our pos + lval.str = yylex.Text() + return DOLLAR + } /bool/ { yylex.pos(lval) // our pos lval.str = yylex.Text() diff --git a/lang/lexparse_test.go b/lang/lexparse_test.go index 6dd0886b..3aae144e 100644 --- a/lang/lexparse_test.go +++ b/lang/lexparse_test.go @@ -315,6 +315,216 @@ func TestLexParse0(t *testing.T) { //exp: ???, // FIXME: add the expected AST }) } + { + exp := &StmtProg{ + Prog: []interfaces.Stmt{ + &StmtBind{ + Ident: "x1", + Value: &ExprCall{ + Name: "foo1", + Args: []interfaces.Expr{}, + }, + }, + }, + } + values = append(values, test{ + name: "func call 1", + code: ` + $x1 = foo1() + `, + fail: false, + exp: exp, + }) + } + { + exp := &StmtProg{ + Prog: []interfaces.Stmt{ + &StmtBind{ + Ident: "x1", + Value: &ExprCall{ + Name: "foo1", + Args: []interfaces.Expr{ + &ExprInt{ + V: 13, + }, + &ExprStr{ + V: "hello", + }, + }, + }, + }, + }, + } + values = append(values, test{ + name: "func call 2", + code: ` + $x1 = foo1(13, "hello") + `, + fail: false, + exp: exp, + }) + } + { + exp := &StmtProg{ + Prog: []interfaces.Stmt{ + &StmtBind{ + Ident: "x1", + Value: &ExprCall{ + Name: "pkg.foo1", + Args: []interfaces.Expr{}, + }, + }, + }, + } + values = append(values, test{ + name: "func call dotted 1", + code: ` + $x1 = pkg.foo1() + `, + fail: false, + exp: exp, + }) + } + { + exp := &StmtProg{ + Prog: []interfaces.Stmt{ + &StmtBind{ + Ident: "x1", + Value: &ExprCall{ + Name: "pkg.foo1", + Args: []interfaces.Expr{ + &ExprBool{ + V: true, + }, + &ExprStr{ + V: "hello", + }, + }, + }, + }, + }, + } + values = append(values, test{ + name: "func call dotted 2", + code: ` + $x1 = pkg.foo1(true, "hello") + `, + fail: false, + exp: exp, + }) + } + { + values = append(values, test{ + name: "func call dotted invalid 1", + code: ` + $x1 = .pkg.foo1(true, "hello") + `, + fail: true, + }) + } + { + values = append(values, test{ + name: "func call dotted invalid 2", + code: ` + $x1 = pkg.foo1.(true, "hello") + `, + fail: true, + }) + } + { + values = append(values, test{ + name: "func call dotted invalid 3", + code: ` + $x1 = .pkg.foo1.(true, "hello") + `, + fail: true, + }) + } + { + values = append(values, test{ + name: "func call dotted invalid 4", + code: ` + $x1 = pkg..foo1(true, "hello") + `, + fail: true, + }) + } + { + exp := &StmtProg{ + Prog: []interfaces.Stmt{ + &StmtBind{ + Ident: "x1", + Value: &ExprVar{ + Name: "pkg.foo1", + }, + }, + }, + } + values = append(values, test{ + name: "dotted var 1", + code: ` + $x1 = $pkg.foo1 + `, + fail: false, + exp: exp, + }) + } + { + exp := &StmtProg{ + Prog: []interfaces.Stmt{ + &StmtBind{ + Ident: "x1", + Value: &ExprVar{ + Name: "pkg.foo1.bar", + }, + }, + }, + } + values = append(values, test{ + name: "dotted var 2", + code: ` + $x1 = $pkg.foo1.bar + `, + fail: false, + exp: exp, + }) + } + { + values = append(values, test{ + name: "invalid dotted var 1", + code: ` + $x1 = $.pkg.foo1.bar + `, + fail: true, + }) + } + { + values = append(values, test{ + name: "invalid dotted var 2", + code: ` + $x1 = $pkg.foo1.bar. + `, + fail: true, + }) + } + { + values = append(values, test{ + name: "invalid dotted var 3", + code: ` + $x1 = $.pkg.foo1.bar. + `, + fail: true, + }) + } + { + values = append(values, test{ + name: "invalid dotted var 4", + code: ` + $x1 = $pkg..foo1.bar + `, + fail: true, + }) + } { exp := &StmtProg{ Prog: []interfaces.Stmt{ @@ -903,6 +1113,158 @@ func TestLexParse0(t *testing.T) { exp: exp, }) } + { + exp := &StmtProg{ + Prog: []interfaces.Stmt{ + &StmtClass{ + Name: "c1", + Body: &StmtProg{ + Prog: []interfaces.Stmt{ + &StmtRes{ + Kind: "test", + Name: &ExprStr{ + V: "t1", + }, + Contents: []StmtResContents{ + &StmtResField{ + Field: "stringptr", + Value: &ExprStr{ + V: "hello", + }, + }, + }, + }, + }, + }, + }, + &StmtInclude{ + Name: "pkg.c1", + }, + }, + } + values = append(values, test{ + name: "simple dotted class 1", + code: ` + # a dotted identifier only occurs via an imported class + class c1 { + test "t1" { + stringptr => "hello", + } + } + # a dotted identifier is allowed here if it's imported + include pkg.c1 + `, + fail: false, + exp: exp, + }) + } + { + exp := &StmtProg{ + Prog: []interfaces.Stmt{ + &StmtClass{ + Name: "c1", + Body: &StmtProg{ + Prog: []interfaces.Stmt{ + &StmtRes{ + Kind: "test", + Name: &ExprStr{ + V: "t1", + }, + Contents: []StmtResContents{ + &StmtResField{ + Field: "stringptr", + Value: &ExprStr{ + V: "hello", + }, + }, + }, + }, + }, + }, + }, + &StmtInclude{ + Name: "pkg.ns.c1", + }, + }, + } + values = append(values, test{ + name: "simple dotted class 2", + code: ` + # a dotted identifier only occurs via an imported class + class c1 { + test "t1" { + stringptr => "hello", + } + } + # a dotted identifier is allowed here if it's imported + include pkg.ns.c1 + `, + fail: false, + exp: exp, + }) + } + { + values = append(values, test{ + name: "simple dotted invalid class 1", + code: ` + # a dotted identifier only occurs via an imported class + class foo.c1 { + } + `, + fail: true, + }) + } + { + values = append(values, test{ + name: "simple dotted invalid class 2", + code: ` + # a dotted identifier only occurs via an imported class + class foo.bar.c1 { + } + `, + fail: true, + }) + } + { + values = append(values, test{ + name: "simple dotted invalid include 1", + code: ` + class .foo.c1 { + } + `, + fail: true, + }) + } + { + values = append(values, test{ + name: "simple dotted invalid include 2", + code: ` + class foo.c1. { + } + `, + fail: true, + }) + } + { + values = append(values, test{ + name: "simple dotted invalid include 3", + code: ` + class .foo.c1. { + } + `, + fail: true, + }) + } + { + values = append(values, test{ + name: "simple dotted invalid include 4", + code: ` + class foo..c1 { + } + `, + fail: true, + }) + } { exp := &StmtProg{ Prog: []interfaces.Stmt{ diff --git a/lang/parser.y b/lang/parser.y index 2dfec3bc..a2ef6ef0 100644 --- a/lang/parser.y +++ b/lang/parser.y @@ -79,7 +79,7 @@ func init() { %token OPEN_BRACK CLOSE_BRACK %token IF ELSE %token STRING BOOL INTEGER FLOAT -%token EQUALS +%token EQUALS DOLLAR %token COMMA COLON SEMICOLON %token ELVIS ROCKET ARROW DOT %token STR_IDENTIFIER BOOL_IDENTIFIER INT_IDENTIFIER FLOAT_IDENTIFIER @@ -211,7 +211,7 @@ stmt: } } // `include name` -| INCLUDE_IDENTIFIER IDENTIFIER +| INCLUDE_IDENTIFIER dotted_identifier { posLast(yylex, yyDollar) // our pos $$.stmt = &StmtInclude{ @@ -219,7 +219,7 @@ stmt: } } // `include name(...)` -| INCLUDE_IDENTIFIER IDENTIFIER OPEN_PAREN call_args CLOSE_PAREN +| INCLUDE_IDENTIFIER dotted_identifier OPEN_PAREN call_args CLOSE_PAREN { posLast(yylex, yyDollar) // our pos $$.stmt = &StmtInclude{ @@ -405,7 +405,7 @@ struct_field: } ; call: - IDENTIFIER OPEN_PAREN call_args CLOSE_PAREN + dotted_identifier OPEN_PAREN call_args CLOSE_PAREN { posLast(yylex, yyDollar) // our pos $$.expr = &ExprCall{ @@ -610,7 +610,10 @@ call: }, } } -//| VAR_IDENTIFIER OPEN_CURLY INTEGER CLOSE_CURLY +// 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{ @@ -658,7 +661,7 @@ call_args: } ; var: - VAR_IDENTIFIER + dotted_var_identifier { posLast(yylex, yyDollar) // our pos $$.expr = &ExprVar{ @@ -952,6 +955,39 @@ type_struct_field: $$.str = fmt.Sprintf("%s %s", $1.str, $2.typ.String()) } ; +dotted_identifier: + IDENTIFIER + { + posLast(yylex, yyDollar) // our pos + $$.str = $1.str + } +| dotted_identifier DOT IDENTIFIER + { + posLast(yylex, yyDollar) // our pos + $$.str = $1.str + "." + $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 + "." + $3.str + } + // eg: $ foo.bar.baz (dollar prefix + dotted identifier) +| DOLLAR dotted_identifier + { + posLast(yylex, yyDollar) // our pos + $$.str = $2.str + } +; %% // pos is a helper function used to track the position in the parser. func pos(y yyLexer, dollar yySymType) {