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:
@@ -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
|
Whether the output is useful and whether there is a unique type unification
|
||||||
solution is dependent on your code.
|
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
|
#### Import
|
||||||
|
|
||||||
The `import` statement imports a scope into the specified namespace. A scope can
|
The `import` statement imports a scope into the specified namespace. A scope can
|
||||||
|
|||||||
12
examples/lang/class-include-as0.mcl
Normal file
12
examples/lang/class-include-as0.mcl
Normal 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
|
||||||
|
}
|
||||||
21
examples/lang/class-include-as1.mcl
Normal file
21
examples/lang/class-include-as1.mcl
Normal 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
|
||||||
|
}
|
||||||
@@ -65,6 +65,46 @@ const (
|
|||||||
// MetaField is the prefix used to specify a meta parameter for the res.
|
// MetaField is the prefix used to specify a meta parameter for the res.
|
||||||
MetaField = "meta"
|
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
|
// AllowUserDefinedPolyFunc specifies if we allow user-defined
|
||||||
// polymorphic functions or not. At the moment this is not implemented.
|
// polymorphic functions or not. At the moment this is not implemented.
|
||||||
// XXX: not implemented
|
// XXX: not implemented
|
||||||
@@ -101,10 +141,9 @@ const (
|
|||||||
// classOrderingPrefix is a magic prefix used for the Ordering graph.
|
// classOrderingPrefix is a magic prefix used for the Ordering graph.
|
||||||
classOrderingPrefix = "class:"
|
classOrderingPrefix = "class:"
|
||||||
|
|
||||||
// legacyProgSetScope enables an old version of the SetScope function
|
// scopedOrderingPrefix is a magic prefix used for the Ordering graph.
|
||||||
// in StmtProg. Use it for experimentation if you don't want to use the
|
// It is shared between imports and include as.
|
||||||
// Ordering function for some reason. In general, this should be false!
|
scopedOrderingPrefix = "scoped:"
|
||||||
legacyProgSetScope = false
|
|
||||||
|
|
||||||
// ErrNoStoredScope is an error that tells us we can't get a scope here.
|
// ErrNoStoredScope is an error that tells us we can't get a scope here.
|
||||||
ErrNoStoredScope = interfaces.Error("scope is not stored in this node")
|
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)
|
prod := make(map[string]interfaces.Node)
|
||||||
for _, x := range obj.Body {
|
for _, x := range obj.Body {
|
||||||
if stmt, ok := x.(*StmtClass); ok {
|
if stmt, ok := x.(*StmtImport); ok {
|
||||||
if stmt.Name == "" {
|
if stmt.Name == "" {
|
||||||
return nil, nil, fmt.Errorf("missing class 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]
|
n, exists := prod[uid]
|
||||||
if exists {
|
if exists {
|
||||||
return nil, nil, fmt.Errorf("duplicate assignment to `%s`, have: %s", uid, n)
|
return nil, nil, fmt.Errorf("duplicate assignment to `%s`, have: %s", uid, n)
|
||||||
}
|
}
|
||||||
prod[uid] = stmt // store
|
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, ok := x.(*StmtFunc); ok {
|
||||||
if stmt.Name == "" {
|
if stmt.Name == "" {
|
||||||
return nil, nil, fmt.Errorf("missing func 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
|
prod[uid] = stmt // store
|
||||||
}
|
}
|
||||||
if stmt, ok := x.(*StmtBind); ok {
|
|
||||||
if stmt.Ident == "" {
|
if stmt, ok := x.(*StmtClass); ok {
|
||||||
return nil, nil, fmt.Errorf("missing bind name")
|
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]
|
n, exists := prod[uid]
|
||||||
if exists {
|
if exists {
|
||||||
return nil, nil, fmt.Errorf("duplicate assignment to `%s`, have: %s", uid, n)
|
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)
|
newVariables := make(map[string]string)
|
||||||
newFunctions := make(map[string]string)
|
newFunctions := make(map[string]string)
|
||||||
newClasses := 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 {
|
for _, x := range obj.Body {
|
||||||
imp, ok := x.(*StmtImport)
|
imp, ok := x.(*StmtImport)
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -3518,7 +3601,10 @@ func (obj *StmtProg) SetScope(scope *interfaces.Scope) error {
|
|||||||
// TODO: do this in a deterministic (sorted) order
|
// TODO: do this in a deterministic (sorted) order
|
||||||
for name, x := range importedScope.Variables {
|
for name, x := range importedScope.Variables {
|
||||||
newName := alias + interfaces.ModuleSep + name
|
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
|
newName = name
|
||||||
}
|
}
|
||||||
if previous, exists := newVariables[newName]; exists {
|
if previous, exists := newVariables[newName]; exists {
|
||||||
@@ -3530,7 +3616,10 @@ func (obj *StmtProg) SetScope(scope *interfaces.Scope) error {
|
|||||||
}
|
}
|
||||||
for name, x := range importedScope.Functions {
|
for name, x := range importedScope.Functions {
|
||||||
newName := alias + interfaces.ModuleSep + name
|
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
|
newName = name
|
||||||
}
|
}
|
||||||
if previous, exists := newFunctions[newName]; exists {
|
if previous, exists := newFunctions[newName]; exists {
|
||||||
@@ -3542,7 +3631,10 @@ func (obj *StmtProg) SetScope(scope *interfaces.Scope) error {
|
|||||||
}
|
}
|
||||||
for name, x := range importedScope.Classes {
|
for name, x := range importedScope.Classes {
|
||||||
newName := alias + interfaces.ModuleSep + name
|
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
|
newName = name
|
||||||
}
|
}
|
||||||
if previous, exists := newClasses[newName]; exists {
|
if previous, exists := newClasses[newName]; exists {
|
||||||
@@ -3558,147 +3650,6 @@ func (obj *StmtProg) SetScope(scope *interfaces.Scope) error {
|
|||||||
aliases[alias] = struct{}{}
|
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: this could be called once at the top-level, and then cached...
|
||||||
// TODO: it currently gets called inside child programs, which is slow!
|
// TODO: it currently gets called inside child programs, which is slow!
|
||||||
orderingGraph, _, err := obj.Ordering(nil) // XXX: pass in globals from scope?
|
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 {
|
if err := orderingGraph.ExecGraphviz("/tmp/graphviz-ordering.dot"); err != nil {
|
||||||
obj.data.Logf("graphviz: errored: %+v", err)
|
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!
|
// Only generate the top-level one, to prevent overwriting this!
|
||||||
orderingGraphSingleton = false
|
orderingGraphSingleton = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//nodeOrder, err := orderingGraphFiltered.TopologicalSort()
|
||||||
nodeOrder, err := orderingGraph.TopologicalSort()
|
nodeOrder, err := orderingGraph.TopologicalSort()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// TODO: print the cycle in a prettier way (with names?)
|
// TODO: print the cycle in a prettier way (with names?)
|
||||||
if obj.data.Debug {
|
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", orderingGraph.Sprint())
|
||||||
|
//obj.data.Logf("set scope: not a dag:\n%s", orderingGraphFiltered.Sprint())
|
||||||
}
|
}
|
||||||
return errwrap.Wrapf(err, "recursive reference while setting scope")
|
return errwrap.Wrapf(err, "recursive reference while setting scope")
|
||||||
}
|
}
|
||||||
|
|
||||||
// XXX: implement ValidTopoSortOrder!
|
// XXX: implement ValidTopoSortOrder!
|
||||||
//topoSanity := (RequireTopologicalOrdering || TopologicalOrderingWarning)
|
//topoSanity := (RequireTopologicalOrdering || TopologicalOrderingWarning)
|
||||||
//if topoSanity && !orderingGraph.ValidTopoSortOrder(nodeOrder) {
|
//if topoSanity && !orderingGraphFiltered.ValidTopoSortOrder(nodeOrder) {
|
||||||
// msg := "code is out of order, you're insane!"
|
// msg := "code is out of order, you're insane!"
|
||||||
// if TopologicalOrderingWarning {
|
// if TopologicalOrderingWarning {
|
||||||
// obj.data.Logf(msg)
|
// 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)
|
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
|
// Optimization: In addition to importantly skipping the parts of the
|
||||||
// graph that don't belong in this StmtProg, this also causes
|
// graph that don't belong in this StmtProg, this also causes
|
||||||
// un-consumed statements to be skipped. As a result, this simplifies
|
// un-consumed statements to be skipped. As a result, this simplifies
|
||||||
// the graph significantly in cases of unused code, because they're not
|
// 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.
|
// 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)
|
stmt, ok := x.(interfaces.Stmt)
|
||||||
if !ok {
|
if !ok {
|
||||||
continue
|
continue
|
||||||
@@ -3787,13 +3760,212 @@ func (obj *StmtProg) SetScope(scope *interfaces.Scope) error {
|
|||||||
// Skip any unwanted additions that we pulled in.
|
// Skip any unwanted additions that we pulled in.
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if obj.data.Debug {
|
|
||||||
obj.data.Logf("prog: set scope: order: %+v", stmt)
|
capturedScope := loopScope.Copy()
|
||||||
}
|
if err := stmt.SetScope(capturedScope); err != nil {
|
||||||
if err := stmt.SetScope(newScope); err != nil {
|
|
||||||
return err
|
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 {
|
if obj.data.Debug {
|
||||||
obj.data.Logf("prog: set scope: finished")
|
obj.data.Logf("prog: set scope: finished")
|
||||||
}
|
}
|
||||||
@@ -4254,7 +4426,13 @@ func (obj *StmtClass) SetScope(scope *interfaces.Scope) error {
|
|||||||
if scope == nil {
|
if scope == nil {
|
||||||
scope = interfaces.EmptyScope()
|
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
|
obj.scope = scope // store for later
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4296,8 +4474,9 @@ type StmtInclude struct {
|
|||||||
class *StmtClass // copy of class that we're using
|
class *StmtClass // copy of class that we're using
|
||||||
orig *StmtInclude // original pointer to this
|
orig *StmtInclude // original pointer to this
|
||||||
|
|
||||||
Name string
|
Name string
|
||||||
Args []interfaces.Expr
|
Args []interfaces.Expr
|
||||||
|
Alias string
|
||||||
}
|
}
|
||||||
|
|
||||||
// String returns a short representation of this statement.
|
// String returns a short representation of this statement.
|
||||||
@@ -4368,9 +4547,10 @@ func (obj *StmtInclude) Interpolate() (interfaces.Stmt, error) {
|
|||||||
}
|
}
|
||||||
return &StmtInclude{
|
return &StmtInclude{
|
||||||
//class: obj.class, // TODO: is this necessary?
|
//class: obj.class, // TODO: is this necessary?
|
||||||
orig: orig,
|
orig: orig,
|
||||||
Name: obj.Name,
|
Name: obj.Name,
|
||||||
Args: args,
|
Args: args,
|
||||||
|
Alias: obj.Alias,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4403,9 +4583,10 @@ func (obj *StmtInclude) Copy() (interfaces.Stmt, error) {
|
|||||||
}
|
}
|
||||||
return &StmtInclude{
|
return &StmtInclude{
|
||||||
//class: obj.class, // TODO: is this necessary?
|
//class: obj.class, // TODO: is this necessary?
|
||||||
orig: orig,
|
orig: orig,
|
||||||
Name: obj.Name,
|
Name: obj.Name,
|
||||||
Args: args,
|
Args: args,
|
||||||
|
Alias: obj.Alias,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4422,17 +4603,34 @@ func (obj *StmtInclude) Ordering(produces map[string]interfaces.Node) (*pgraph.G
|
|||||||
if obj.Name == "" {
|
if obj.Name == "" {
|
||||||
return nil, nil, fmt.Errorf("missing class name")
|
return nil, nil, fmt.Errorf("missing class name")
|
||||||
}
|
}
|
||||||
uid := classOrderingPrefix + obj.Name // ordering id
|
|
||||||
|
|
||||||
cons := make(map[interfaces.Node]string)
|
uid := classOrderingPrefix + obj.Name // ordering id
|
||||||
cons[obj] = uid
|
|
||||||
|
|
||||||
node, exists := produces[uid]
|
node, exists := produces[uid]
|
||||||
if exists {
|
if exists {
|
||||||
edge := &pgraph.SimpleEdge{Name: "stmtinclude"}
|
edge := &pgraph.SimpleEdge{Name: "stmtinclude1"}
|
||||||
graph.AddEdge(node, obj, edge) // prod -> cons
|
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 {
|
for _, node := range obj.Args {
|
||||||
g, c, err := node.Ordering(produces)
|
g, c, err := node.Ordering(produces)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -4545,7 +4743,6 @@ func (obj *StmtInclude) SetScope(scope *interfaces.Scope) error {
|
|||||||
},
|
},
|
||||||
CapturedScope: newScope,
|
CapturedScope: newScope,
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// recursion detection
|
// recursion detection
|
||||||
@@ -4702,6 +4899,12 @@ func (obj *StmtImport) Ordering(produces map[string]interfaces.Node) (*pgraph.Gr
|
|||||||
}
|
}
|
||||||
graph.AddVertex(obj)
|
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)
|
cons := make(map[interfaces.Node]string)
|
||||||
return graph, cons, nil
|
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
|
uid = varOrderingPrefix + obj.Name // ordering id
|
||||||
}
|
}
|
||||||
|
|
||||||
cons := make(map[interfaces.Node]string)
|
|
||||||
cons[obj] = uid
|
|
||||||
|
|
||||||
node, exists := produces[uid]
|
node, exists := produces[uid]
|
||||||
if exists {
|
if exists {
|
||||||
edge := &pgraph.SimpleEdge{Name: "exprcallname"}
|
edge := &pgraph.SimpleEdge{Name: "exprcallname1"}
|
||||||
graph.AddEdge(node, obj, edge) // prod -> cons
|
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 {
|
for _, node := range obj.Args {
|
||||||
g, c, err := node.Ordering(produces)
|
g, c, err := node.Ordering(produces)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -8363,15 +8582,31 @@ func (obj *ExprVar) Ordering(produces map[string]interfaces.Node) (*pgraph.Graph
|
|||||||
}
|
}
|
||||||
uid := varOrderingPrefix + obj.Name // ordering id
|
uid := varOrderingPrefix + obj.Name // ordering id
|
||||||
|
|
||||||
cons := make(map[interfaces.Node]string)
|
|
||||||
cons[obj] = uid
|
|
||||||
|
|
||||||
node, exists := produces[uid]
|
node, exists := produces[uid]
|
||||||
if exists {
|
if exists {
|
||||||
edge := &pgraph.SimpleEdge{Name: "exprvar"}
|
edge := &pgraph.SimpleEdge{Name: "exprvar1"}
|
||||||
graph.AddEdge(node, obj, edge) // prod -> cons
|
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
|
return graph, cons, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -235,7 +235,7 @@ type Data struct {
|
|||||||
// from the variables, which could actually contain lambda functions.
|
// from the variables, which could actually contain lambda functions.
|
||||||
type Scope struct {
|
type Scope struct {
|
||||||
Variables map[string]Expr
|
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
|
Classes map[string]Stmt
|
||||||
|
|
||||||
Chain []Node // chain of previously seen node's
|
Chain []Node // chain of previously seen node's
|
||||||
|
|||||||
@@ -28,6 +28,12 @@ const (
|
|||||||
// also used with `ModuleSep` for scoped variables like `$foo.bar.baz`.
|
// also used with `ModuleSep` for scoped variables like `$foo.bar.baz`.
|
||||||
VarPrefix = "$"
|
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 is the kind string used for the panic resource.
|
||||||
PanicResKind = "_panic"
|
PanicResKind = "_panic"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -4,4 +4,4 @@
|
|||||||
$x = "wow"
|
$x = "wow"
|
||||||
$x = 99 # woops, but also a change of type :P
|
$x = 99 # woops, but also a change of type :P
|
||||||
-- OUTPUT --
|
-- OUTPUT --
|
||||||
# err: errSetScope: var `x` already exists in this scope
|
# err: errSetScope: could not generate ordering: duplicate assignment to `var:x`, have: bind(x)
|
||||||
|
|||||||
@@ -3,4 +3,4 @@
|
|||||||
$x = "hello"
|
$x = "hello"
|
||||||
$x = "world" # woops
|
$x = "world" # woops
|
||||||
-- OUTPUT --
|
-- OUTPUT --
|
||||||
# err: errSetScope: var `x` already exists in this scope
|
# err: errSetScope: could not generate ordering: duplicate assignment to `var:x`, have: bind(x)
|
||||||
|
|||||||
@@ -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]
|
||||||
@@ -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]
|
||||||
@@ -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
|
||||||
@@ -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)
|
||||||
@@ -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)
|
||||||
@@ -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)
|
||||||
@@ -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]
|
||||||
@@ -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]
|
||||||
@@ -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]
|
||||||
@@ -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]
|
||||||
@@ -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]
|
||||||
@@ -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)
|
||||||
@@ -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)
|
||||||
@@ -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]
|
||||||
@@ -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]
|
||||||
@@ -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]
|
||||||
@@ -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]
|
||||||
@@ -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]
|
||||||
@@ -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]
|
||||||
@@ -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]
|
||||||
@@ -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]
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
-- main.mcl --
|
||||||
|
$wat = "bad1"
|
||||||
|
class c1($wat) {
|
||||||
|
test $wat {}
|
||||||
|
}
|
||||||
|
include c1("hello")
|
||||||
|
-- OUTPUT --
|
||||||
|
Vertex: test[hello]
|
||||||
22
lang/interpret_test/TestAstFunc2/import-scope-classes1.txtar
Normal file
22
lang/interpret_test/TestAstFunc2/import-scope-classes1.txtar
Normal 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]
|
||||||
20
lang/interpret_test/TestAstFunc2/import-scope-classes2.txtar
Normal file
20
lang/interpret_test/TestAstFunc2/import-scope-classes2.txtar
Normal 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]
|
||||||
20
lang/interpret_test/TestAstFunc2/import-scope-classes3.txtar
Normal file
20
lang/interpret_test/TestAstFunc2/import-scope-classes3.txtar
Normal 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
|
||||||
26
lang/interpret_test/TestAstFunc2/import-scope-classes4.txtar
Normal file
26
lang/interpret_test/TestAstFunc2/import-scope-classes4.txtar
Normal 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]
|
||||||
31
lang/interpret_test/TestAstFunc2/import-scope-classes5.txtar
Normal file
31
lang/interpret_test/TestAstFunc2/import-scope-classes5.txtar
Normal 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]
|
||||||
20
lang/interpret_test/TestAstFunc2/import-scope-classes6.txtar
Normal file
20
lang/interpret_test/TestAstFunc2/import-scope-classes6.txtar
Normal 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
|
||||||
15
lang/interpret_test/TestAstFunc2/import-scope-vars1.txtar
Normal file
15
lang/interpret_test/TestAstFunc2/import-scope-vars1.txtar
Normal 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]
|
||||||
@@ -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
|
||||||
15
lang/interpret_test/TestAstFunc2/import-scope-vars2.txtar
Normal file
15
lang/interpret_test/TestAstFunc2/import-scope-vars2.txtar
Normal 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]
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
-- main.mcl --
|
||||||
|
# set scope ordering test
|
||||||
|
$x = "hey"
|
||||||
|
$y = $x
|
||||||
|
test $y {}
|
||||||
|
-- OUTPUT --
|
||||||
|
Vertex: test[hey]
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
-- main.mcl --
|
||||||
|
# set scope ordering test
|
||||||
|
$y = $x
|
||||||
|
$x = "hey"
|
||||||
|
test $y {}
|
||||||
|
-- OUTPUT --
|
||||||
|
Vertex: test[hey]
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
-- main.mcl --
|
||||||
|
# reverse order
|
||||||
|
test $i0.f0 {}
|
||||||
|
include c0 as i0
|
||||||
|
class c0() {
|
||||||
|
$f0 = "hello"
|
||||||
|
}
|
||||||
|
-- OUTPUT --
|
||||||
|
Vertex: test[hello]
|
||||||
@@ -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]
|
||||||
@@ -85,8 +85,14 @@
|
|||||||
return MINUS
|
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
|
yylex.pos(lval) // our pos
|
||||||
lval.str = yylex.Text()
|
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
|
return MULTIPLY
|
||||||
}
|
}
|
||||||
/\// {
|
/\// {
|
||||||
|
|||||||
@@ -1670,12 +1670,12 @@ func TestLexParse0(t *testing.T) {
|
|||||||
exp: exp,
|
exp: exp,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
{
|
if ast.AllowBareImports {
|
||||||
exp := &ast.StmtProg{
|
exp := &ast.StmtProg{
|
||||||
Body: []interfaces.Stmt{
|
Body: []interfaces.Stmt{
|
||||||
&ast.StmtImport{
|
&ast.StmtImport{
|
||||||
Name: "foo1",
|
Name: "foo1",
|
||||||
Alias: "*",
|
Alias: interfaces.BareSymbol,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -295,6 +295,27 @@ stmt:
|
|||||||
Args: $4.exprs,
|
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 "name"`
|
||||||
| IMPORT_IDENTIFIER STRING
|
| IMPORT_IDENTIFIER STRING
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user