From cf50fb3568025df5629e01a4e586bf2667790d79 Mon Sep 17 00:00:00 2001 From: James Shubin Date: Tue, 3 Jul 2018 20:54:00 -0400 Subject: [PATCH] lang: Allow dotted identifiers This adds support for dotted identifiers in include statements, var expressions and function call expressions. The dotted identifiers are used to refer to classes, bind statements, and function definitions (respectively) that are included in the scope by import statements. --- lang/lexer.nex | 5 + lang/lexparse_test.go | 362 ++++++++++++++++++++++++++++++++++++++++++ lang/parser.y | 48 +++++- 3 files changed, 409 insertions(+), 6 deletions(-) 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) {