From 73b11045f2e0227a76d55f3fade120ccfafb9bcd Mon Sep 17 00:00:00 2001 From: James Shubin Date: Sat, 22 Sep 2018 11:02:29 -0400 Subject: [PATCH] lang: Add lexing/parsing of import statements This adds the basic import statement, and its associated variants. It also adds the import structure which is the result of parsing. --- lang/lexer.nex | 10 ++++ lang/lexparse_test.go | 118 ++++++++++++++++++++++++++++++++++++++++++ lang/parser.y | 28 ++++++++++ lang/structs.go | 67 ++++++++++++++++++++++++ 4 files changed, 223 insertions(+) diff --git a/lang/lexer.nex b/lang/lexer.nex index 3b6e9753..265a9b6e 100644 --- a/lang/lexer.nex +++ b/lang/lexer.nex @@ -194,6 +194,16 @@ lval.str = yylex.Text() return INCLUDE_IDENTIFIER } +/import/ { + yylex.pos(lval) // our pos + lval.str = yylex.Text() + return IMPORT_IDENTIFIER + } +/as/ { + yylex.pos(lval) // our pos + lval.str = yylex.Text() + return AS_IDENTIFIER + } /variant/ { yylex.pos(lval) // our pos lval.str = yylex.Text() diff --git a/lang/lexparse_test.go b/lang/lexparse_test.go index 5aecfd63..caeba97e 100644 --- a/lang/lexparse_test.go +++ b/lang/lexparse_test.go @@ -1488,6 +1488,124 @@ func TestLexParse0(t *testing.T) { exp: exp, }) } + { + exp := &StmtProg{ + Prog: []interfaces.Stmt{ + &StmtImport{ + Name: "foo1", + Alias: "", + }, + }, + } + values = append(values, test{ + name: "simple import 1", + code: ` + import "foo1" + `, + fail: false, + exp: exp, + }) + } + { + exp := &StmtProg{ + Prog: []interfaces.Stmt{ + &StmtImport{ + Name: "foo1", + Alias: "bar", + }, + }, + } + values = append(values, test{ + name: "simple import 2", + code: ` + import "foo1" as bar + `, + fail: false, + exp: exp, + }) + } + { + exp := &StmtProg{ + Prog: []interfaces.Stmt{ + &StmtImport{ + Name: "foo1", + Alias: "", + }, + &StmtImport{ + Name: "foo2", + Alias: "bar", + }, + &StmtImport{ + Name: "foo3", + Alias: "", + }, + }, + } + values = append(values, test{ + name: "simple import 3", + code: ` + import "foo1" + import "foo2" as bar + import "foo3" + `, + fail: false, + exp: exp, + }) + } + { + exp := &StmtProg{ + Prog: []interfaces.Stmt{ + &StmtImport{ + Name: "foo1", + Alias: "*", + }, + }, + } + values = append(values, test{ + name: "simple import 4", + code: ` + import "foo1" as * + `, + fail: false, + exp: exp, + }) + } + { + exp := &StmtProg{ + Prog: []interfaces.Stmt{ + &StmtClass{ + Name: "c1", + Body: &StmtProg{ + Prog: []interfaces.Stmt{ + &StmtImport{ + Name: "foo", + Alias: "bar", + }, + &StmtImport{ + Name: "baz", + Alias: "", + }, + }, + }, + }, + &StmtInclude{ + Name: "c1", + }, + }, + } + values = append(values, test{ + name: "simple import inside class 1", + code: ` + class c1 { + import "foo" as bar + import "baz" + } + include c1 + `, + fail: false, + exp: exp, + }) + } names := []string{} for index, test := range values { // run all the tests diff --git a/lang/parser.y b/lang/parser.y index 80c3efdb..1af39ca2 100644 --- a/lang/parser.y +++ b/lang/parser.y @@ -87,6 +87,7 @@ func init() { %token VAR_IDENTIFIER_HX CAPITALIZED_IDENTIFIER %token RES_IDENTIFIER CAPITALIZED_RES_IDENTIFIER %token CLASS_IDENTIFIER INCLUDE_IDENTIFIER +%token IMPORT_IDENTIFIER AS_IDENTIFIER %token COMMENT ERROR // precedence table @@ -228,6 +229,33 @@ stmt: 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 diff --git a/lang/structs.go b/lang/structs.go index c35790e3..65882412 100644 --- a/lang/structs.go +++ b/lang/structs.go @@ -1849,6 +1849,73 @@ func (obj *StmtInclude) Output() (*interfaces.Output, error) { return obj.class.Output() } +// StmtImport adds the exported scope definitions of a module into the current +// scope. It can be used anywhere a statement is allowed, and can even be nested +// inside a class definition. By convention, it is commonly used at the top of a +// file. As with any statement, it produces output, but that output is empty. To +// benefit from its inclusion, reference the scope definitions you want. +type StmtImport struct { + Name string + Alias string +} + +// Apply is a general purpose iterator method that operates on any AST node. It +// is not used as the primary AST traversal function because it is less readable +// and easy to reason about than manually implementing traversal for each node. +// Nevertheless, it is a useful facility for operations that might only apply to +// a select number of node types, since they won't need extra noop iterators... +func (obj *StmtImport) Apply(fn func(interfaces.Node) error) error { return fn(obj) } + +// Init initializes this branch of the AST, and returns an error if it fails to +// validate. +func (obj *StmtImport) Init(*interfaces.Data) error { return nil } + +// Interpolate returns a new node (aka a copy) once it has been expanded. This +// generally increases the size of the AST when it is used. It calls Interpolate +// on any child elements and builds the new node with those new node contents. +func (obj *StmtImport) Interpolate() (interfaces.Stmt, error) { + return &StmtImport{ + Name: obj.Name, + Alias: obj.Alias, + }, nil +} + +// SetScope stores the scope for later use in this resource and it's children, +// which it propagates this downwards to. +func (obj *StmtImport) SetScope(*interfaces.Scope) error { return nil } + +// Unify returns the list of invariants that this node produces. It recursively +// calls Unify on any children elements that exist in the AST, and returns the +// collection to the caller. +func (obj *StmtImport) Unify() ([]interfaces.Invariant, error) { + if obj.Name == "" { + return nil, fmt.Errorf("missing import name") + } + + return []interfaces.Invariant{}, nil +} + +// Graph returns the reactive function graph which is expressed by this node. It +// includes any vertices produced by this node, and the appropriate edges to any +// vertices that are produced by its children. Nodes which fulfill the Expr +// interface directly produce vertices (and possible children) where as nodes +// that fulfill the Stmt interface do not produces vertices, where as their +// children might. This particular statement just returns an empty graph. +func (obj *StmtImport) Graph() (*pgraph.Graph, error) { + graph, err := pgraph.NewGraph("import") + return graph, errwrap.Wrapf(err, "could not create graph") +} + +// Output returns the output that this include produces. This output is what +// is used to build the output graph. This only exists for statements. The +// analogous function for expressions is Value. Those Value functions might get +// called by this Output function if they are needed to produce the output. This +// import statement itself produces no output, as it is only used to populate +// the scope so that others can use that to produce values and output. +func (obj *StmtImport) Output() (*interfaces.Output, error) { + return (&interfaces.Output{}).Empty(), nil +} + // StmtComment is a representation of a comment. It is currently unused. It // probably makes sense to make a third kind of Node (not a Stmt or an Expr) so // that comments can still be part of the AST (for eventual automatic code