lang: parser, ast, interfaces: Implement include as

This adds support for `include as <identifier>` type statements which in
addition to pulling in any defined resources, it also makes the contents
of the scope of the class available to the scope of the include
statement, but prefixed by the identifier specified.

This makes passing data between scopes much more powerful, and it also
allows classes to return useful classes for subsequent use.

This also improves the SetScope procedure and adds to the Ordering
stage. It's unclear if the current Ordering stage can handle all code,
or if there exist corner-cases which are valid code, but which would
produce a wrong or imprecise topological sort.

Some extraneous scoping bugs still exist, which expose certain variables
that we should not depend on in future code.

Co-authored-by: Samuel Gélineau <gelisam@gmail.com>
This commit is contained in:
James Shubin
2024-01-06 14:13:43 -05:00
parent f92f34dc54
commit 44ee578a3a
46 changed files with 1197 additions and 186 deletions

View File

@@ -414,6 +414,57 @@ parameters, then the same class can even be called with different signatures.
Whether the output is useful and whether there is a unique type unification
solution is dependent on your code.
Classes can be included under a new scoped prefix by using the `as` field and an
identifier. When used in this manner, the captured scope of the class at its
definition site are made available in the scope of the include. Variables,
functions, and child classes are all exported.
Variables are available in the include scope:
```mcl
import "fmt"
class c1 {
test "t1" {} # gets pulled out
$x = "hello" # gets exported
}
include c1 as i1
test "print0" {
anotherstr => fmt.printf("%s", $i1.x), # hello
onlyshow => ["AnotherStr",], # displays nicer
}
```
Classes are also available in the new scope:
```mcl
import "fmt"
class c1 {
test "t1" {} # gets pulled out
$x = "hello" # gets exported
class c0 {
test "t2" {}
$x = "goodbye"
}
}
include c1 as i1
include i1.c0 as i0
test "print0" {
anotherstr => fmt.printf("%s", $i1.x), # hello
onlyshow => ["AnotherStr",], # displays nicer
}
test "print1" {
anotherstr => fmt.printf("%s", $i0.x), # goodbye
onlyshow => ["AnotherStr",], # displays nicer
}
```
Of course classes can be parameterized too, and those variables specified during
the `include`.
#### Import
The `import` statement imports a scope into the specified namespace. A scope can

View File

@@ -0,0 +1,12 @@
import "fmt"
class c1 {
test "t1" {} # gets pulled out
$x = "hello" # gets exported
}
include c1 as i1
test "print0" {
anotherstr => fmt.printf("%s", $i1.x), # hello
onlyshow => ["AnotherStr",], # displays nicer
}

View File

@@ -0,0 +1,21 @@
import "fmt"
class c1 {
test "t1" {} # gets pulled out
$x = "hello" # gets exported
class c0 {
test "t2" {}
$x = "goodbye"
}
}
include c1 as i1
include i1.c0 as i0
test "print0" {
anotherstr => fmt.printf("%s", $i1.x), # hello
onlyshow => ["AnotherStr",], # displays nicer
}
test "print1" {
anotherstr => fmt.printf("%s", $i0.x), # goodbye
onlyshow => ["AnotherStr",], # displays nicer
}

View File

@@ -65,6 +65,46 @@ const (
// MetaField is the prefix used to specify a meta parameter for the res.
MetaField = "meta"
// AllowBareClassIncluding specifies that a simple include without an
// `as` suffix, will be pulled in under the name of the included class.
// We want this on if it turns out to be common to pull in values from
// classes.
//
// If we allow bare including of classes, then we have to also prevent
// duplicate class inclusion for many cases. For example:
//
// class c1($s) {
// test $s {}
// $x = "${s}"
// }
// include c1("hey")
// include c1("there")
// test $x {}
//
// What value should $x have? We want to import two useful `test`
// resources, but with a bare import this makes `$x` ambiguous. We'd
// have to detect this and ensure this is a compile time error to use
// it. Being able to allow compatible, duplicate classes is a key
// important feature of the language, and as a result, enabling this
// would probably be disastrous. The fact that the import statement
// allows bare imports is an ergonomic consideration that is allowed
// because duplicate imports aren't essential. As an aside, the use of
// bare imports isn't recommended because it makes it more difficult to
// know where certain things are coming from.
AllowBareClassIncluding = false
// AllowBareIncludes specifies that you're allowed to use an include
// which flattens the included scope on top of the current scope. This
// means includes of the form: `include foo as *`. These are unlikely to
// get enabled for many reasons.
AllowBareIncludes = false
// AllowBareImports specifies that you're allowed to use an import which
// flattens the imported scope on top of the current scope. This means
// imports of the form: `import foo as *`. These are being provisionally
// enabled, despite being less explicit and harder to parse.
AllowBareImports = true
// AllowUserDefinedPolyFunc specifies if we allow user-defined
// polymorphic functions or not. At the moment this is not implemented.
// XXX: not implemented
@@ -101,10 +141,9 @@ const (
// classOrderingPrefix is a magic prefix used for the Ordering graph.
classOrderingPrefix = "class:"
// legacyProgSetScope enables an old version of the SetScope function
// in StmtProg. Use it for experimentation if you don't want to use the
// Ordering function for some reason. In general, this should be false!
legacyProgSetScope = false
// scopedOrderingPrefix is a magic prefix used for the Ordering graph.
// It is shared between imports and include as.
scopedOrderingPrefix = "scoped:"
// ErrNoStoredScope is an error that tells us we can't get a scope here.
ErrNoStoredScope = interfaces.Error("scope is not stored in this node")
@@ -2949,17 +2988,43 @@ func (obj *StmtProg) Ordering(produces map[string]interfaces.Node) (*pgraph.Grap
prod := make(map[string]interfaces.Node)
for _, x := range obj.Body {
if stmt, ok := x.(*StmtClass); ok {
if stmt, ok := x.(*StmtImport); ok {
if stmt.Name == "" {
return nil, nil, fmt.Errorf("missing class name")
}
uid := classOrderingPrefix + stmt.Name // ordering id
uid := scopedOrderingPrefix + stmt.Name // ordering id
if stmt.Alias == interfaces.BareSymbol {
// XXX: I think we need to parse these first...
// XXX: Somehow make sure these appear at the
// top of the topo-sort for the StmtProg...
// XXX: Maybe add edges between StmtProg and me?
continue
}
if stmt.Alias != "" {
uid = scopedOrderingPrefix + stmt.Alias // ordering id
}
n, exists := prod[uid]
if exists {
return nil, nil, fmt.Errorf("duplicate assignment to `%s`, have: %s", uid, n)
}
prod[uid] = stmt // store
}
if stmt, ok := x.(*StmtBind); ok {
if stmt.Ident == "" {
return nil, nil, fmt.Errorf("missing bind name")
}
uid := varOrderingPrefix + stmt.Ident // ordering id
n, exists := prod[uid]
if exists {
return nil, nil, fmt.Errorf("duplicate assignment to `%s`, have: %s", uid, n)
}
prod[uid] = stmt // store
}
if stmt, ok := x.(*StmtFunc); ok {
if stmt.Name == "" {
return nil, nil, fmt.Errorf("missing func name")
@@ -2971,11 +3036,27 @@ func (obj *StmtProg) Ordering(produces map[string]interfaces.Node) (*pgraph.Grap
}
prod[uid] = stmt // store
}
if stmt, ok := x.(*StmtBind); ok {
if stmt.Ident == "" {
return nil, nil, fmt.Errorf("missing bind name")
if stmt, ok := x.(*StmtClass); ok {
if stmt.Name == "" {
return nil, nil, fmt.Errorf("missing class name")
}
uid := varOrderingPrefix + stmt.Ident // ordering id
uid := classOrderingPrefix + stmt.Name // ordering id
n, exists := prod[uid]
if exists {
return nil, nil, fmt.Errorf("duplicate assignment to `%s`, have: %s", uid, n)
}
prod[uid] = stmt // store
}
if stmt, ok := x.(*StmtInclude); ok {
if stmt.Name == "" {
return nil, nil, fmt.Errorf("missing include name")
}
if stmt.Alias == "" { // not consumed
continue
}
uid := scopedOrderingPrefix + stmt.Alias // ordering id
n, exists := prod[uid]
if exists {
return nil, nil, fmt.Errorf("duplicate assignment to `%s`, have: %s", uid, n)
@@ -3482,6 +3563,8 @@ func (obj *StmtProg) SetScope(scope *interfaces.Scope) error {
newVariables := make(map[string]string)
newFunctions := make(map[string]string)
newClasses := make(map[string]string)
// TODO: If we added .Ordering() for *StmtImport, we could combine this
// loop with the main nodeOrder sorted topological ordering loop below!
for _, x := range obj.Body {
imp, ok := x.(*StmtImport)
if !ok {
@@ -3518,7 +3601,10 @@ func (obj *StmtProg) SetScope(scope *interfaces.Scope) error {
// TODO: do this in a deterministic (sorted) order
for name, x := range importedScope.Variables {
newName := alias + interfaces.ModuleSep + name
if alias == "*" {
if alias == interfaces.BareSymbol {
if !AllowBareImports {
return fmt.Errorf("bare imports disabled at compile time for import of `%s`", imp.Name)
}
newName = name
}
if previous, exists := newVariables[newName]; exists {
@@ -3530,7 +3616,10 @@ func (obj *StmtProg) SetScope(scope *interfaces.Scope) error {
}
for name, x := range importedScope.Functions {
newName := alias + interfaces.ModuleSep + name
if alias == "*" {
if alias == interfaces.BareSymbol {
if !AllowBareImports {
return fmt.Errorf("bare imports disabled at compile time for import of `%s`", imp.Name)
}
newName = name
}
if previous, exists := newFunctions[newName]; exists {
@@ -3542,7 +3631,10 @@ func (obj *StmtProg) SetScope(scope *interfaces.Scope) error {
}
for name, x := range importedScope.Classes {
newName := alias + interfaces.ModuleSep + name
if alias == "*" {
if alias == interfaces.BareSymbol {
if !AllowBareImports {
return fmt.Errorf("bare imports disabled at compile time for import of `%s`", imp.Name)
}
newName = name
}
if previous, exists := newClasses[newName]; exists {
@@ -3558,147 +3650,6 @@ func (obj *StmtProg) SetScope(scope *interfaces.Scope) error {
aliases[alias] = struct{}{}
}
// collect all the bind statements in the first pass
// this allows them to appear out of order in this scope
binds := make(map[string]struct{}) // bind existence in this scope
for _, x := range obj.Body {
bind, ok := x.(*StmtBind)
if !ok {
continue
}
// check for duplicates *in this scope*
if _, exists := binds[bind.Ident]; exists {
return fmt.Errorf("var `%s` already exists in this scope", bind.Ident)
}
binds[bind.Ident] = struct{}{} // mark as found in scope
// add to scope, (overwriting, aka shadowing is ok)
newScope.Variables[bind.Ident] = &ExprTopLevel{
Definition: &ExprSingleton{
Definition: bind.Value,
mutex: &sync.Mutex{}, // TODO: call Init instead
},
CapturedScope: newScope,
}
if obj.data.Debug { // TODO: is this message ever useful?
obj.data.Logf("prog: set scope: bind collect: (%+v): %+v (%T) is %p", bind.Ident, bind.Value, bind.Value, bind.Value)
}
}
// now collect all the functions, and group by name (if polyfunc is ok)
functions := make(map[string][]*StmtFunc)
for _, x := range obj.Body {
fn, ok := x.(*StmtFunc)
if !ok {
continue
}
_, exists := functions[fn.Name]
if !exists {
functions[fn.Name] = []*StmtFunc{} // initialize
}
// check for duplicates *in this scope*
if exists && !AllowUserDefinedPolyFunc {
return fmt.Errorf("func `%s` already exists in this scope", fn.Name)
}
// collect functions (if multiple, this is a polyfunc)
functions[fn.Name] = append(functions[fn.Name], fn)
}
for name, fnList := range functions {
if obj.data.Debug { // TODO: is this message ever useful?
obj.data.Logf("prog: set scope: collect: (%+v -> %d): %+v (%T)", name, len(fnList), fnList[0].Func, fnList[0].Func)
}
// add to scope, (overwriting, aka shadowing is ok)
if len(fnList) == 1 {
fn := fnList[0].Func // local reference to avoid changing it in the loop...
// add to scope, (overwriting, aka shadowing is ok)
newScope.Functions[name] = &ExprPoly{ // XXX: is this ExprPoly approach optimal?
Definition: &ExprTopLevel{
Definition: fn, // store the *ExprFunc
CapturedScope: newScope,
},
}
continue
}
// build polyfunc's
// XXX: not implemented
return fmt.Errorf("user-defined polyfuncs of length %d are not supported", len(fnList))
}
// now collect any classes
// TODO: if we ever allow poly classes, then group in lists by name
classes := make(map[string]struct{})
for _, x := range obj.Body {
class, ok := x.(*StmtClass)
if !ok {
continue
}
// check for duplicates *in this scope*
if _, exists := classes[class.Name]; exists {
return fmt.Errorf("class `%s` already exists in this scope", class.Name)
}
classes[class.Name] = struct{}{} // mark as found in scope
// add to scope, (overwriting, aka shadowing is ok)
newScope.Classes[class.Name] = class
}
obj.scope = newScope // save a reference in case we're read by an import
// This is the legacy variant of this function that doesn't allow
// out-of-order code. It also returns obscure error messages for some
// cases, such as double-recursion. It's left here for reference.
if legacyProgSetScope {
// first set the scope on the classes, since it gets used in include...
for _, stmt := range obj.Body {
//if _, ok := stmt.(*StmtClass); !ok {
// continue
//}
_, ok1 := stmt.(*StmtClass)
_, ok2 := stmt.(*StmtFunc) // TODO: is this correct?
_, ok3 := stmt.(*StmtBind) // TODO: is this correct?
if !ok1 && !ok2 && !ok3 { // if all are false, we skip
continue
}
if obj.data.Debug {
obj.data.Logf("prog: set scope: pass 1: %+v", stmt)
}
if err := stmt.SetScope(newScope); err != nil {
return err
}
}
// now set the child scopes...
for _, stmt := range obj.Body {
// NOTE: We used to skip over *StmtClass here for recursion...
// Skip over *StmtClass here, since we already did it above...
if _, ok := stmt.(*StmtClass); ok {
continue
}
if _, ok := stmt.(*StmtFunc); ok { // TODO: is this correct?
continue
}
if _, ok := stmt.(*StmtBind); ok { // TODO: is this correct?
continue
}
if obj.data.Debug {
obj.data.Logf("prog: set scope: pass 2: %+v", stmt)
}
if err := stmt.SetScope(newScope); err != nil {
return err
}
}
return nil
}
// TODO: this could be called once at the top-level, and then cached...
// TODO: it currently gets called inside child programs, which is slow!
orderingGraph, _, err := obj.Ordering(nil) // XXX: pass in globals from scope?
@@ -3713,22 +3664,27 @@ func (obj *StmtProg) SetScope(scope *interfaces.Scope) error {
if err := orderingGraph.ExecGraphviz("/tmp/graphviz-ordering.dot"); err != nil {
obj.data.Logf("graphviz: errored: %+v", err)
}
//if err := orderingGraphFiltered.ExecGraphviz("/tmp/graphviz-ordering-filtered.dot"); err != nil {
// obj.data.Logf("graphviz: errored: %+v", err)
//}
// Only generate the top-level one, to prevent overwriting this!
orderingGraphSingleton = false
}
//nodeOrder, err := orderingGraphFiltered.TopologicalSort()
nodeOrder, err := orderingGraph.TopologicalSort()
if err != nil {
// TODO: print the cycle in a prettier way (with names?)
if obj.data.Debug {
obj.data.Logf("set scope: not a dag:\n%s", orderingGraph.Sprint())
//obj.data.Logf("set scope: not a dag:\n%s", orderingGraphFiltered.Sprint())
}
return errwrap.Wrapf(err, "recursive reference while setting scope")
}
// XXX: implement ValidTopoSortOrder!
//topoSanity := (RequireTopologicalOrdering || TopologicalOrderingWarning)
//if topoSanity && !orderingGraph.ValidTopoSortOrder(nodeOrder) {
//if topoSanity && !orderingGraphFiltered.ValidTopoSortOrder(nodeOrder) {
// msg := "code is out of order, you're insane!"
// if TopologicalOrderingWarning {
// obj.data.Logf(msg)
@@ -3770,12 +3726,29 @@ func (obj *StmtProg) SetScope(scope *interfaces.Scope) error {
obj.data.Logf("prog: set scope: ordering: %+v", stmts)
}
// Track all the bind statements, functions, and classes. This is used
// for duplicate checking. These might appear out-of-order as code, but
// are iterated in the topoligically sorted node order. When we collect
// all the functions, we group by name (if polyfunc is ok) and we also
// do something similar for classes.
// TODO: if we ever allow poly classes, then group in lists by name
binds := make(map[string]struct{}) // bind existence in this scope
functions := make(map[string][]*StmtFunc)
classes := make(map[string]struct{})
//includes := make(map[string]struct{}) // duplicates are allowed
// Optimization: In addition to importantly skipping the parts of the
// graph that don't belong in this StmtProg, this also causes
// un-consumed statements to be skipped. As a result, this simplifies
// the graph significantly in cases of unused code, because they're not
// given a chance to SetScope even though they're in the StmtProg list.
for _, x := range nodeOrder { // these are in the correct order for SetScope
// In the below loop which we iterate over in the correct scope order,
// we build up the scope (loopScope) as we go, so that subsequent uses
// of the scope include earlier definitions and scope additions.
loopScope := newScope.Copy()
funcCount := make(map[string]int) // count the occurrences of a func
for _, x := range nodeOrder { // these are in the correct order for SetScope
stmt, ok := x.(interfaces.Stmt)
if !ok {
continue
@@ -3787,13 +3760,212 @@ func (obj *StmtProg) SetScope(scope *interfaces.Scope) error {
// Skip any unwanted additions that we pulled in.
continue
}
if obj.data.Debug {
obj.data.Logf("prog: set scope: order: %+v", stmt)
}
if err := stmt.SetScope(newScope); err != nil {
capturedScope := loopScope.Copy()
if err := stmt.SetScope(capturedScope); err != nil {
return err
}
if bind, ok := x.(*StmtBind); ok {
// check for duplicates *in this scope*
if _, exists := binds[bind.Ident]; exists {
return fmt.Errorf("var `%s` already exists in this scope", bind.Ident)
}
binds[bind.Ident] = struct{}{} // mark as found in scope
// add to scope, (overwriting, aka shadowing is ok)
loopScope.Variables[bind.Ident] = &ExprTopLevel{
Definition: &ExprSingleton{
Definition: bind.Value,
mutex: &sync.Mutex{}, // TODO: call Init instead
},
CapturedScope: capturedScope,
}
if obj.data.Debug { // TODO: is this message ever useful?
obj.data.Logf("prog: set scope: bind collect: (%+v): %+v (%T) is %p", bind.Ident, bind.Value, bind.Value, bind.Value)
}
continue // optional
}
if fn, ok := x.(*StmtFunc); ok {
_, exists := functions[fn.Name]
if !exists {
functions[fn.Name] = []*StmtFunc{} // initialize
}
// check for duplicates *in this scope*
if exists && !AllowUserDefinedPolyFunc {
return fmt.Errorf("func `%s` already exists in this scope", fn.Name)
}
count := 1 // XXX: number of overloaded definitions of the same name (get from ordering eventually)
funcCount[fn.Name]++
// collect functions (if multiple, this is a polyfunc)
functions[fn.Name] = append(functions[fn.Name], fn)
if funcCount[fn.Name] < count {
continue // delay SetScope for later...
}
fnList := functions[fn.Name] // []*StmtFunc
if obj.data.Debug { // TODO: is this message ever useful?
obj.data.Logf("prog: set scope: collect: (%+v -> %d): %+v (%T)", fn.Name, len(fnList), fnList[0].Func, fnList[0].Func)
}
// add to scope, (overwriting, aka shadowing is ok)
if len(fnList) == 1 {
f := fnList[0].Func // local reference to avoid changing it in the loop...
// add to scope, (overwriting, aka shadowing is ok)
loopScope.Functions[fn.Name] = &ExprPoly{ // XXX: is this ExprPoly approach optimal?
Definition: &ExprTopLevel{
Definition: f, // store the *ExprFunc
CapturedScope: capturedScope,
},
}
continue
}
// build polyfunc's
// XXX: not implemented
return fmt.Errorf("user-defined polyfuncs of length %d are not supported", len(fnList))
}
if class, ok := x.(*StmtClass); ok {
// check for duplicates *in this scope*
if _, exists := classes[class.Name]; exists {
return fmt.Errorf("class `%s` already exists in this scope", class.Name)
}
classes[class.Name] = struct{}{} // mark as found in scope
// add to scope, (overwriting, aka shadowing is ok)
loopScope.Classes[class.Name] = class
continue
}
// now collect any include contents
if include, ok := x.(*StmtInclude); ok {
// We actually don't want to check for duplicates, that
// is allowed, if we `include foo as bar` twice it will
// currently not work, but if possible, we can allow it.
// check for duplicates *in this scope*
//if _, exists := includes[include.Name]; exists {
// return fmt.Errorf("include `%s` already exists in this scope", include.Name)
//}
alias := ""
if AllowBareClassIncluding {
alias = include.Name // this is what we would call the include
}
if include.Alias != "" { // this is what the user decided as the name
alias = include.Alias // use alias if specified
}
if alias == "" {
continue // there isn't anything to do here
}
// NOTE: This gets caught in ordering instead of here...
// deal with alias duplicates and * includes and so on...
if _, exists := aliases[alias]; exists {
// TODO: track separately to give a better error message here
return fmt.Errorf("import/include alias `%s` already exists in this scope", alias)
}
if include.class == nil {
// programming error
return fmt.Errorf("programming error: class `%s` not found", include.Name)
}
// This includes any variable from the top-level scope
// that is visible (and captured) inside the class, and
// re-exported when included with `as`. This is the
// "tricky case", but it turns out it's better this way.
// Example:
//
// $x = "i am x" # i am now top-level
// class c1() {
// $whatever = fmt.printf("i can see: %s", $x)
// }
// include c1 as i1
// test $i1.x {} # tricky
// test $i1.whatever {} # easy
//
// We want to allow the tricky case to prevent needing
// to write code like: `$x = $x` inside of class c1 to
// get the same effect.
//includedScope := include.class.Body.scope // conceptually
prog, ok := include.class.Body.(*StmtProg)
if !ok {
return fmt.Errorf("programming error: prog not found in class Body")
}
// XXX: .Copy() ?
includedScope := prog.scope
// read from stored scope which was previously saved in SetScope
// add to scope, (overwriting, aka shadowing is ok)
// rename scope values, adding the alias prefix
// check that we don't overwrite a new value from another include
// TODO: do this in a deterministic (sorted) order
for name, x := range includedScope.Variables {
newName := alias + interfaces.ModuleSep + name
if alias == interfaces.BareSymbol { // not supported by parser atm!
if !AllowBareIncludes {
return fmt.Errorf("bare includes disabled at compile time for include of `%s`", include.Name)
}
newName = name
}
if previous, exists := newVariables[newName]; exists {
// don't overwrite in same scope
return fmt.Errorf("can't squash variable `%s` from `%s` by include of `%s`", newName, previous, include.Name)
}
newVariables[newName] = include.Name
loopScope.Variables[newName] = x // merge
}
for name, x := range includedScope.Functions {
newName := alias + interfaces.ModuleSep + name
if alias == interfaces.BareSymbol { // not supported by parser atm!
if !AllowBareIncludes {
return fmt.Errorf("bare includes disabled at compile time for include of `%s`", include.Name)
}
newName = name
}
if previous, exists := newFunctions[newName]; exists {
// don't overwrite in same scope
return fmt.Errorf("can't squash function `%s` from `%s` by include of `%s`", newName, previous, include.Name)
}
newFunctions[newName] = include.Name
loopScope.Functions[newName] = x
}
for name, x := range includedScope.Classes {
newName := alias + interfaces.ModuleSep + name
if alias == interfaces.BareSymbol { // not supported by parser atm!
if !AllowBareIncludes {
return fmt.Errorf("bare includes disabled at compile time for include of `%s`", include.Name)
}
newName = name
}
if previous, exists := newClasses[newName]; exists {
// don't overwrite in same scope
return fmt.Errorf("can't squash class `%s` from `%s` by include of `%s`", newName, previous, include.Name)
}
newClasses[newName] = include.Name
loopScope.Classes[newName] = x
}
// everything has been merged, move on to next include...
//includes[include.Name] = struct{}{} // don't mark as found in scope
aliases[alias] = struct{}{} // do track these as a bonus
}
}
obj.scope = loopScope // save a reference in case we're read by an import
if obj.data.Debug {
obj.data.Logf("prog: set scope: finished")
}
@@ -4254,7 +4426,13 @@ func (obj *StmtClass) SetScope(scope *interfaces.Scope) error {
if scope == nil {
scope = interfaces.EmptyScope()
}
// We want to capture what was in scope at the definition site of the
// class so that when we `include` the class, the body of the class is
// expanded with the variables which were in scope at the definition
// site and not the variables which were in scope at the include site.
obj.scope = scope // store for later
return nil
}
@@ -4296,8 +4474,9 @@ type StmtInclude struct {
class *StmtClass // copy of class that we're using
orig *StmtInclude // original pointer to this
Name string
Args []interfaces.Expr
Name string
Args []interfaces.Expr
Alias string
}
// String returns a short representation of this statement.
@@ -4368,9 +4547,10 @@ func (obj *StmtInclude) Interpolate() (interfaces.Stmt, error) {
}
return &StmtInclude{
//class: obj.class, // TODO: is this necessary?
orig: orig,
Name: obj.Name,
Args: args,
orig: orig,
Name: obj.Name,
Args: args,
Alias: obj.Alias,
}, nil
}
@@ -4403,9 +4583,10 @@ func (obj *StmtInclude) Copy() (interfaces.Stmt, error) {
}
return &StmtInclude{
//class: obj.class, // TODO: is this necessary?
orig: orig,
Name: obj.Name,
Args: args,
orig: orig,
Name: obj.Name,
Args: args,
Alias: obj.Alias,
}, nil
}
@@ -4422,17 +4603,34 @@ func (obj *StmtInclude) Ordering(produces map[string]interfaces.Node) (*pgraph.G
if obj.Name == "" {
return nil, nil, fmt.Errorf("missing class name")
}
uid := classOrderingPrefix + obj.Name // ordering id
cons := make(map[interfaces.Node]string)
cons[obj] = uid
uid := classOrderingPrefix + obj.Name // ordering id
node, exists := produces[uid]
if exists {
edge := &pgraph.SimpleEdge{Name: "stmtinclude"}
edge := &pgraph.SimpleEdge{Name: "stmtinclude1"}
graph.AddEdge(node, obj, edge) // prod -> cons
}
// equivalent to: strings.Contains(obj.Name, interfaces.ModuleSep)
if split := strings.Split(obj.Name, interfaces.ModuleSep); len(split) > 1 {
// we contain a dot
uid = scopedOrderingPrefix + split[0] // just the first prefix
// TODO: do we also want this second edge??
node, exists := produces[uid]
if exists {
edge := &pgraph.SimpleEdge{Name: "stmtinclude2"}
graph.AddEdge(node, obj, edge) // prod -> cons
}
}
// It's okay to replace the normal `class` prefix, because we have the
// fancier `scoped:` prefix which matches more generally...
// TODO: we _can_ produce two uid's here, is it okay we only offer one?
cons := make(map[interfaces.Node]string)
cons[obj] = uid
for _, node := range obj.Args {
g, c, err := node.Ordering(produces)
if err != nil {
@@ -4545,7 +4743,6 @@ func (obj *StmtInclude) SetScope(scope *interfaces.Scope) error {
},
CapturedScope: newScope,
}
}
// recursion detection
@@ -4702,6 +4899,12 @@ func (obj *StmtImport) Ordering(produces map[string]interfaces.Node) (*pgraph.Gr
}
graph.AddVertex(obj)
// Since we always run the imports before anything else in the StmtProg,
// we don't need to do anything special in here.
// TODO: If this statement is true, add this in so that imports can be
// done in the same iteration through StmtProg in SetScope with all of
// the other statements.
cons := make(map[interfaces.Node]string)
return graph, cons, nil
}
@@ -7580,15 +7783,31 @@ func (obj *ExprCall) Ordering(produces map[string]interfaces.Node) (*pgraph.Grap
uid = varOrderingPrefix + obj.Name // ordering id
}
cons := make(map[interfaces.Node]string)
cons[obj] = uid
node, exists := produces[uid]
if exists {
edge := &pgraph.SimpleEdge{Name: "exprcallname"}
edge := &pgraph.SimpleEdge{Name: "exprcallname1"}
graph.AddEdge(node, obj, edge) // prod -> cons
}
// equivalent to: strings.Contains(obj.Name, interfaces.ModuleSep)
if split := strings.Split(obj.Name, interfaces.ModuleSep); len(split) > 1 {
// we contain a dot
uid = scopedOrderingPrefix + split[0] // just the first prefix
// TODO: do we also want this second edge??
node, exists := produces[uid]
if exists {
edge := &pgraph.SimpleEdge{Name: "exprcallname2"}
graph.AddEdge(node, obj, edge) // prod -> cons
}
}
// It's okay to replace the normal `func` or `var` prefix, because we
// have the fancier `scoped:` prefix which matches more generally...
// TODO: we _can_ produce two uid's here, is it okay we only offer one?
cons := make(map[interfaces.Node]string)
cons[obj] = uid
for _, node := range obj.Args {
g, c, err := node.Ordering(produces)
if err != nil {
@@ -8363,15 +8582,31 @@ func (obj *ExprVar) Ordering(produces map[string]interfaces.Node) (*pgraph.Graph
}
uid := varOrderingPrefix + obj.Name // ordering id
cons := make(map[interfaces.Node]string)
cons[obj] = uid
node, exists := produces[uid]
if exists {
edge := &pgraph.SimpleEdge{Name: "exprvar"}
edge := &pgraph.SimpleEdge{Name: "exprvar1"}
graph.AddEdge(node, obj, edge) // prod -> cons
}
// equivalent to: strings.Contains(obj.Name, interfaces.ModuleSep)
if split := strings.Split(obj.Name, interfaces.ModuleSep); len(split) > 1 {
// we contain a dot
uid = scopedOrderingPrefix + split[0] // just the first prefix
// TODO: do we also want this second edge??
node, exists := produces[uid]
if exists {
edge := &pgraph.SimpleEdge{Name: "exprvar2"}
graph.AddEdge(node, obj, edge) // prod -> cons
}
}
// It's okay to replace the normal `var` prefix, because we have the
// fancier `scoped:` prefix which matches more generally...
// TODO: we _can_ produce two uid's here, is it okay we only offer one?
cons := make(map[interfaces.Node]string)
cons[obj] = uid
return graph, cons, nil
}

View File

@@ -235,7 +235,7 @@ type Data struct {
// from the variables, which could actually contain lambda functions.
type Scope struct {
Variables map[string]Expr
Functions map[string]Expr // the Expr will usually be an *ExprFunc
Functions map[string]Expr // the Expr will usually be an *ExprFunc (actually it's usually (or always) an *ExprSingleton, which wraps an *ExprFunc now)
Classes map[string]Stmt
Chain []Node // chain of previously seen node's

View File

@@ -28,6 +28,12 @@ const (
// also used with `ModuleSep` for scoped variables like `$foo.bar.baz`.
VarPrefix = "$"
// BareSymbol is the character used primarily for imports to specify
// that we want to import the entire contents and flatten them into our
// current scope. It should probably be removed entirely to force
// explicit imports.
BareSymbol = "*"
// PanicResKind is the kind string used for the panic resource.
PanicResKind = "_panic"
)

View File

@@ -4,4 +4,4 @@
$x = "wow"
$x = 99 # woops, but also a change of type :P
-- OUTPUT --
# err: errSetScope: var `x` already exists in this scope
# err: errSetScope: could not generate ordering: duplicate assignment to `var:x`, have: bind(x)

View File

@@ -3,4 +3,4 @@
$x = "hello"
$x = "world" # woops
-- OUTPUT --
# err: errSetScope: var `x` already exists in this scope
# err: errSetScope: could not generate ordering: duplicate assignment to `var:x`, have: bind(x)

View File

@@ -0,0 +1,30 @@
-- main.mcl --
class c1 {
test "t1" {}
$y = "hello"
class c0 {
test "t2" {}
$x = "goodbye"
}
}
include c1 as i1 # has $y
include i1.c0 as i0 # has $x ...and $y
test $i0.x {} # ok
test $i1.y {} # ok
panic($i0.x != "goodbye")
panic($i1.y != "hello")
# the really tricky case
# XXX: works atm, but not supported for now: could not set scope: variable i0.y not in scope
# We currently re-export anything in the parent scope as available from our
# current child scope, which makes this variable visible. Unfortunately, it does
# not have the correct dependency (edge) present in the Ordering system, so it
# is flaky depending on luck of the toposort.
#test $i0.y {}
-- OUTPUT --
Vertex: test[goodbye]
Vertex: test[hello]
Vertex: test[t1]
Vertex: test[t2]

View File

@@ -0,0 +1,39 @@
-- main.mcl --
class c1($b) {
test "t1" {}
if $b {
test "t2" {}
} else {
test "t3" {}
}
class c0 {
test "t4" {}
if $b {
test "t5" {}
} else {
test "t6" {}
}
$x = if $b {
"hello"
} else {
"goodbye"
}
}
}
include c1(true) as i1
include i1.c0 as i01
include c1(false) as i2
include i2.c0 as i02
test $i01.x {}
test $i02.x {}
-- OUTPUT --
Vertex: test[hello]
Vertex: test[goodbye]
Vertex: test[t1]
Vertex: test[t2]
Vertex: test[t3]
Vertex: test[t4]
Vertex: test[t5]
Vertex: test[t6]

View File

@@ -0,0 +1,17 @@
-- main.mcl --
class c1($b) {
if $b { # scope doesn't leak up and out of `if` statement!
class inner() {
test "t1" {}
}
} else {
class inner() {
test "t2" {}
}
}
}
include c1 as i1
include i1.inner
-- OUTPUT --
# err: errSetScope: class `c1` expected 1 args but got 0

View File

@@ -0,0 +1,11 @@
-- main.mcl --
class c1() {
$x = "got: ${s}"
}
# TODO: can this be allowed?
include c1 as i1
include c1 as i1
test $i1.x {}
-- OUTPUT --
# err: errSetScope: could not generate ordering: duplicate assignment to `scoped:i1`, have: include(c1)

View File

@@ -0,0 +1,11 @@
-- main.mcl --
class c1($s) {
$x = "got: ${s}"
}
# TODO: can this be allowed?
include c1("hey") as i1
include c1("hey") as i1
test $i1.x {}
-- OUTPUT --
# err: errSetScope: could not generate ordering: duplicate assignment to `scoped:i1`, have: include(c1)

View File

@@ -0,0 +1,10 @@
-- main.mcl --
class c1($s) {
$x = "got: ${s}"
}
include c1("hey") as i1
include c1("there") as i1
test $i1.x {}
-- OUTPUT --
# err: errSetScope: could not generate ordering: duplicate assignment to `scoped:i1`, have: include(c1)

View File

@@ -0,0 +1,14 @@
-- main.mcl --
class c1 {
$x = "outside"
test "t1" {}
func f1($x) {
"hello" + $x
}
}
include c1 as i1
test i1.f1("world") {}
-- OUTPUT --
Vertex: test[helloworld]
Vertex: test[t1]

View File

@@ -0,0 +1,14 @@
-- main.mcl --
class c1 {
$x = "world"
test "t1" {}
func f1($y) {
"hello" + $x
}
}
include c1 as i1
test i1.f1("whatever") {}
-- OUTPUT --
Vertex: test[helloworld]
Vertex: test[t1]

View File

@@ -0,0 +1,13 @@
-- main.mcl --
class c1 {
test "t1" {}
func f1() {
"hello"
}
}
include c1 as i1
test i1.f1() {}
-- OUTPUT --
Vertex: test[hello]
Vertex: test[t1]

View File

@@ -0,0 +1,27 @@
-- main.mcl --
class c1($b) {
test "t1" {}
if $b {
test "t2" {}
} else {
test "t3" {}
}
func f1() {
if $b {
"hello"
} else {
"goodbye"
}
}
}
include c1(true) as i1
include c1(false) as i2
test i1.f1() {}
test i2.f1() {}
-- OUTPUT --
Vertex: test[hello]
Vertex: test[goodbye]
Vertex: test[t1]
Vertex: test[t2]
Vertex: test[t3]

View File

@@ -0,0 +1,42 @@
-- main.mcl --
class c0($b) {
test "t1" {}
if $b {
test "t2" {}
} else {
test "t3" {}
}
func f0() {
if $b {
"hello"
} else {
"goodbye"
}
}
}
class c1($b) {
test "t4" {}
if $b {
test "t5" {}
} else {
test "t6" {}
}
include c0($b) as i0
func f1() { i0.f0() }
}
include c1(true) as i1
include c1(false) as i2
test i1.f1() {}
test i2.f1() {}
test i1.i0.f0() {} # I think these might work directly too. Do we want them to?
test i2.i0.f0() {}
-- OUTPUT --
Vertex: test[goodbye]
Vertex: test[hello]
Vertex: test[t1]
Vertex: test[t2]
Vertex: test[t3]
Vertex: test[t4]
Vertex: test[t5]
Vertex: test[t6]

View File

@@ -0,0 +1,10 @@
-- main.mcl --
import "fmt" as i1
class c1($s) {
$x = fmt.printf("got: %s", $s)
}
include c1("hey") as i1
test $i1.x {}
-- OUTPUT --
# err: errSetScope: could not generate ordering: duplicate assignment to `scoped:i1`, have: import(fmt)

View File

@@ -0,0 +1,10 @@
-- main.mcl --
import "fmt"
class c1($s) {
$x = fmt.printf("got: %s", $s)
}
include c1("hey") as fmt
test $fmt.x {}
-- OUTPUT --
# err: errSetScope: could not generate ordering: duplicate assignment to `scoped:fmt`, have: import(fmt)

View File

@@ -0,0 +1,14 @@
-- main.mcl --
class c1 {
$x = "outside"
test "t1" {}
$f1 = func($x) {
"hello" + $x
}
}
include c1 as i1
test $i1.f1("world") {}
-- OUTPUT --
Vertex: test[helloworld]
Vertex: test[t1]

View File

@@ -0,0 +1,14 @@
-- main.mcl --
class c1 {
$x = "world"
test "t1" {}
$f1 = func($y) {
"hello" + $x
}
}
include c1 as i1
test $i1.f1("whatever") {}
-- OUTPUT --
Vertex: test[helloworld]
Vertex: test[t1]

View File

@@ -0,0 +1,13 @@
-- main.mcl --
class c1 {
test "t1" {}
$f1 = func() {
"hello"
}
}
include c1 as i1
test $i1.f1() {}
-- OUTPUT --
Vertex: test[hello]
Vertex: test[t1]

View File

@@ -0,0 +1,27 @@
-- main.mcl --
class c1($b) {
test "t1" {}
if $b {
test "t2" {}
} else {
test "t3" {}
}
$f1 = func() {
if $b {
"hello"
} else {
"goodbye"
}
}
}
include c1(true) as i1
include c1(false) as i2
test $i1.f1() {}
test $i2.f1() {}
-- OUTPUT --
Vertex: test[hello]
Vertex: test[goodbye]
Vertex: test[t1]
Vertex: test[t2]
Vertex: test[t3]

View File

@@ -0,0 +1,44 @@
-- main.mcl --
class c0($b) {
test "t1" {}
if $b {
test "t2" {}
} else {
test "t3" {}
}
$f0 = func() {
if $b {
"hello"
} else {
"goodbye"
}
}
#$f0 = "hey"
}
class c1($b) {
test "t4" {}
if $b {
test "t5" {}
} else {
test "t6" {}
}
include c0($b) as i0
$x = $i0.f0
}
include c1(true) as i1
include c1(false) as i2
test $i1.x() {}
test $i1.i0.f0() {}
test $i2.x() {}
test $i1.i0.f0() {} # I think these should work directly too. Do we want them to?
test $i2.i0.f0() {}
-- OUTPUT --
Vertex: test[goodbye]
Vertex: test[hello]
Vertex: test[t1]
Vertex: test[t2]
Vertex: test[t3]
Vertex: test[t4]
Vertex: test[t5]
Vertex: test[t6]

View File

@@ -0,0 +1,11 @@
-- main.mcl --
class c1 {
test "t1" {}
$x = "hello"
}
include c1 as i1
test $i1.x {}
-- OUTPUT --
Vertex: test[hello]
Vertex: test[t1]

View File

@@ -0,0 +1,25 @@
-- main.mcl --
class c1($b) {
test "t1" {}
if $b {
test "t2" {}
} else {
test "t3" {}
}
$x = if $b {
"hello"
} else {
"goodbye"
}
}
include c1(true) as i1
include c1(false) as i2
test $i1.x {}
test $i2.x {}
-- OUTPUT --
Vertex: test[hello]
Vertex: test[goodbye]
Vertex: test[t1]
Vertex: test[t2]
Vertex: test[t3]

View File

@@ -0,0 +1,40 @@
-- main.mcl --
class c0($b) {
test "t1" {}
if $b {
test "t2" {}
} else {
test "t3" {}
}
$y = if $b {
"hello"
} else {
"goodbye"
}
}
class c1($b) {
test "t4" {}
if $b {
test "t5" {}
} else {
test "t6" {}
}
include c0($b) as i0
$x = $i0.y
}
include c1(true) as i1
include c1(false) as i2
test $i1.x {}
test $i2.x {}
test $i1.i0.y {} # I think these should work directly too. Do we want them to?
test $i2.i0.y {}
-- OUTPUT --
Vertex: test[goodbye]
Vertex: test[hello]
Vertex: test[t1]
Vertex: test[t2]
Vertex: test[t3]
Vertex: test[t4]
Vertex: test[t5]
Vertex: test[t6]

View File

@@ -0,0 +1,8 @@
-- main.mcl --
$wat = "bad1"
class c1($wat) {
test $wat {}
}
include c1("hello")
-- OUTPUT --
Vertex: test[hello]

View File

@@ -0,0 +1,22 @@
-- main.mcl --
class c1() {
$x = "i am x"
}
class c2() {
include c1 as g1
#$y = "i am y"
$z = "i am y and " + $g1.x
}
include c2 as f1
test $f1.z {} # yep
#test $f1.x {} # no
test $f1.g1.x {} # yep
-- OUTPUT --
Vertex: test[i am y and i am x]
Vertex: test[i am x]

View File

@@ -0,0 +1,20 @@
-- main.mcl --
$x = "i am x" # i am top-level
class c2() {
#$y = "i am y"
$z = "i am y and " + $x
$newx = $x + " and hello"
}
include c2 as f1
test $f1.z {}
test $f1.x {} # tricky
test $f1.newx {}
-- OUTPUT --
Vertex: test[i am x]
Vertex: test[i am x and hello]
Vertex: test[i am y and i am x]

View File

@@ -0,0 +1,20 @@
-- main.mcl --
$x = "i am x" # i am top-level
class c2() {
$z = "i am y and " + $x
# Since $x is pulled in from top-level automatically, we don't allow the
# re-definition by shadowing of the same variable.
$x = $x # not allowed
#$x = $x + "wow" # allowed?
}
include c2 as f1
test $f1.z {}
test $f1.x {} # tricky
test $f1.newx {}
-- OUTPUT --
# err: errSetScope: recursive reference while setting scope: not a dag

View File

@@ -0,0 +1,26 @@
-- main.mcl --
$x1 = "i am x1" # i am top-level
$x2 = "i am x2" # i am top-level
class c2() {
$z = "i am y and " + $x1
$x1 = "hey" # shadow
}
include c2 as f1
test $f1.z {}
test $f1.x1 {}
# the really tricky case
# XXX: works atm, but not supported for now: could not set scope: variable f1.x2 not in scope
# We currently re-export anything in the parent scope as available from our
# current child scope, which makes this variable visible. Unfortunately, it does
# not have the correct dependency (edge) present in the Ordering system, so it
# is flaky depending on luck of the toposort.
#test $f1.x2 {}
-- OUTPUT --
Vertex: test[hey]
Vertex: test[i am y and hey]

View File

@@ -0,0 +1,31 @@
-- main.mcl --
$x = "i am x" # i am top-level
class c2() {
$y = "i am y and " + $x
#$x = $x # might be allowed, i don't _really_ care, but i prefer not
# We need to be able to re-define our new $x that shadows the parent $x,
# but it should be able to incorporate the parent (top-level) $x into
# the new value that this new $x here becomes.
# XXX: not supported for now: could not set scope: not a dag
# Sam suggested the RHS $x should have a special keyword to make it
# refer to the parent scope. I suggested when $x is also on the LHS,
# then this magic keyword should be implied. And only possible in the
# case for variables on the RHS that are the same var as on the LHS.
#$x = $x + " and this is shadowed" # this is important
}
include c2 as f1
test $x {}
test $f1.y {}
# the really tricky case
# XXX: not supported for now: could not set scope: not a dag
#test $f1.x {}
-- OUTPUT --
Vertex: test[i am x]
Vertex: test[i am y and i am x]

View File

@@ -0,0 +1,20 @@
-- main.mcl --
$x = "i am x" # i am top-level
class c2() {
$z = "i am y and " + $x
#$x = $x # not allowed
$tmpx = $x # this $x is actually from below, not from the parent scope!
#$x = $x + "wow" # allowed?
$x = $tmpx + "wow" # circular with itself ($x) in this scope!
}
include c2 as f1
test $f1.z {}
test $f1.x {} # tricky
test $f1.newx {}
-- OUTPUT --
# err: errSetScope: recursive reference while setting scope: not a dag

View File

@@ -0,0 +1,15 @@
-- metadata.yaml --
#files: "files/" # these are some extra files we can use (is the default)
-- main.mcl --
import "y.mcl" as g
test $g.y {} # should work
#test $g.x {} # should fail
test $g.f.x {} # should maybe work
-- x.mcl --
$x = "this is x.mcl"
-- y.mcl --
import "x.mcl" as f
$y = $f.x + " and this is y.mcl"
-- OUTPUT --
Vertex: test[this is x.mcl]
Vertex: test[this is x.mcl and this is y.mcl]

View File

@@ -0,0 +1,14 @@
-- metadata.yaml --
#files: "files/" # these are some extra files we can use (is the default)
-- main.mcl --
import "y.mcl" as g
#test $g.y {} # should work
test $g.x {} # should fail
test $g.f.x {} # should maybe work
-- x.mcl --
$x = "this is x.mcl"
-- y.mcl --
import "x.mcl" as f
$y = $f.x + " and this is y.mcl"
-- OUTPUT --
# err: errSetScope: variable g.x not in scope

View File

@@ -0,0 +1,15 @@
-- metadata.yaml --
#files: "files/" # these are some extra files we can use (is the default)
-- main.mcl --
import "y.mcl" as g
test $g.y {} # should work
#test $g.x {} # should fail
test $g.f.x {} # should maybe work
-- x.mcl --
$x = "this is x.mcl"
-- y.mcl --
import "x.mcl" as f
$y = $f.x + " and this is y.mcl"
-- OUTPUT --
Vertex: test[this is x.mcl and this is y.mcl]
Vertex: test[this is x.mcl]

View File

@@ -0,0 +1,7 @@
-- main.mcl --
# set scope ordering test
$x = "hey"
$y = $x
test $y {}
-- OUTPUT --
Vertex: test[hey]

View File

@@ -0,0 +1,7 @@
-- main.mcl --
# set scope ordering test
$y = $x
$x = "hey"
test $y {}
-- OUTPUT --
Vertex: test[hey]

View File

@@ -0,0 +1,9 @@
-- main.mcl --
# reverse order
test $i0.f0 {}
include c0 as i0
class c0() {
$f0 = "hello"
}
-- OUTPUT --
Vertex: test[hello]

View File

@@ -0,0 +1,9 @@
-- main.mcl --
# reverse order
test $i0.f0 {}
include c0("hello") as i0
class c0($s) {
$f0 = $s
}
-- OUTPUT --
Vertex: test[hello]

View File

@@ -85,8 +85,14 @@
return MINUS
}
/\*/ {
// This is used as the multiplication symbol, but also
// (for now) the bare import feature, eg: `import as *`.
yylex.pos(lval) // our pos
lval.str = yylex.Text()
// sanity check... these should be the same!
if x, y := lval.str, interfaces.BareSymbol; x != y {
panic(fmt.Sprintf("MULTIPLY does not match BareSymbol (%s != %s)", x, y))
}
return MULTIPLY
}
/\// {

View File

@@ -1670,12 +1670,12 @@ func TestLexParse0(t *testing.T) {
exp: exp,
})
}
{
if ast.AllowBareImports {
exp := &ast.StmtProg{
Body: []interfaces.Stmt{
&ast.StmtImport{
Name: "foo1",
Alias: "*",
Alias: interfaces.BareSymbol,
},
},
}

View File

@@ -295,6 +295,27 @@ stmt:
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
{