From cf7e73bbf6b0771f0144d8ec08a9f4d9905d8f91 Mon Sep 17 00:00:00 2001 From: James Shubin Date: Tue, 4 Mar 2025 21:55:29 -0500 Subject: [PATCH] lang: Add a for loop statement for iterating over a list MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This adds a for statement which is used to iterate over a list with a body of statements. This is an important data transformation tool which should be used sparingly, but is important to have. An import statement inside of a for loop is not currently supported. We have a simple hack to detect the obvious cases, but more deeply nested scenarios probably won't be caught, and you'll get an obscure error message if you try to do this. This was incredibly challenging to get right, and it's all thanks to Sam for his brilliance. Co-authored-by: Samuel Gélineau --- docs/language-guide.md | 9 + examples/lang/for.mcl | 39 + lang/ast/scopegraph.go | 16 + lang/ast/structs.go | 1432 ++++++++++++++--- lang/ast/util.go | 43 + lang/funcs/dage/dage.go | 2 + lang/funcs/structs/for.go | 254 +++ lang/funcs/txn/txn.go | 10 +- lang/interfaces/ast.go | 118 +- lang/interpret_test.go | 27 +- .../TestAstFunc2/polymorphic-lambda.txtar | 2 +- .../TestAstFunc2/stmtfor00.txtar | 13 + .../TestAstFunc2/stmtfor01.txtar | 15 + .../TestAstFunc2/stmtfor02.txtar | 13 + .../TestAstFunc2/stmtfor03.txtar | 14 + .../TestAstFunc2/stmtfor04.txtar | 16 + .../TestAstFunc2/stmtfor05.txtar | 16 + .../TestAstFunc2/stmtfor06.txtar | 16 + .../TestAstFunc2/stmtfor07.txtar | 17 + .../TestAstFunc2/stmtfor08.txtar | 20 + .../TestAstFunc2/stmtfor09.txtar | 22 + .../TestAstFunc2/stmtfor10.txtar | 21 + .../TestAstFunc2/stmtfor11.txtar | 21 + .../TestAstFunc2/stmtfor12.txtar | 21 + .../TestAstFunc2/stmtfor13.txtar | 21 + .../TestAstFunc2/stmtfor14.txtar | 23 + .../TestAstFunc2/stmtfor15.txtar | 23 + .../TestAstFunc2/stmtfor16.txtar | 22 + .../TestAstFunc2/stmtfor17.txtar | 22 + .../TestAstFunc2/stmtfor18.txtar | 22 + .../TestAstFunc2/stmtfor19.txtar | 22 + .../TestAstFunc2/stmtfor20.txtar | 24 + .../TestAstFunc2/stmtfor21.txtar | 24 + .../TestAstFunc2/stmtfor22.txtar | 24 + .../TestAstFunc2/stmtfor23.txtar | 31 + .../TestAstFunc2/stmtfor24.txtar | 17 + .../TestAstFunc2/stmtfor25.txtar | 17 + .../TestAstFunc2/stmtfor26.txtar | 41 + .../TestAstFunc2/stmtfor27.txtar | 38 + .../TestAstFunc2/stmtfor28.txtar | 19 + .../TestAstFunc2/stmtfor29.txtar | 22 + .../TestAstFunc2/stmtfor30.txtar | 23 + .../TestAstFunc2/stmtfor31.txtar | 24 + .../TestAstFunc2/stmtfor32.txtar | 24 + .../TestAstFunc2/stmtfor33.txtar | 24 + .../TestAstFunc2/stmtfor34.txtar | 23 + .../TestAstFunc2/stmtfor35.txtar | 32 + .../TestAstFunc2/stmtfor36.txtar | 29 + .../TestAstFunc2/stmtfor37.txtar | 28 + .../TestAstFunc2/stmtfor38.txtar | 21 + .../TestAstFunc2/stmtfor39.txtar | 21 + .../TestAstFunc2/stmtfor40.txtar | 10 + .../TestAstFunc2/stmtfor41.txtar | 24 + .../TestAstFunc2/stmtfor42.txtar | 27 + .../TestAstFunc2/stmtfor43.txtar | 22 + .../TestAstFunc2/test-monomorphic-bind.txtar | 2 +- .../test-monomorphic-class-arg.txtar | 2 +- lang/interpret_test/TestAstFunc2/wat.txtar | 11 + .../interpret_test/TestAstFunc2/watsam1.txtar | 14 + .../interpret_test/TestAstFunc2/watsam2.txtar | 21 + lang/lang.go | 27 +- lang/parser/lexer.nex | 5 + lang/parser/parser.y | 14 +- 63 files changed, 2814 insertions(+), 203 deletions(-) create mode 100644 examples/lang/for.mcl create mode 100644 lang/funcs/structs/for.go create mode 100644 lang/interpret_test/TestAstFunc2/stmtfor00.txtar create mode 100644 lang/interpret_test/TestAstFunc2/stmtfor01.txtar create mode 100644 lang/interpret_test/TestAstFunc2/stmtfor02.txtar create mode 100644 lang/interpret_test/TestAstFunc2/stmtfor03.txtar create mode 100644 lang/interpret_test/TestAstFunc2/stmtfor04.txtar create mode 100644 lang/interpret_test/TestAstFunc2/stmtfor05.txtar create mode 100644 lang/interpret_test/TestAstFunc2/stmtfor06.txtar create mode 100644 lang/interpret_test/TestAstFunc2/stmtfor07.txtar create mode 100644 lang/interpret_test/TestAstFunc2/stmtfor08.txtar create mode 100644 lang/interpret_test/TestAstFunc2/stmtfor09.txtar create mode 100644 lang/interpret_test/TestAstFunc2/stmtfor10.txtar create mode 100644 lang/interpret_test/TestAstFunc2/stmtfor11.txtar create mode 100644 lang/interpret_test/TestAstFunc2/stmtfor12.txtar create mode 100644 lang/interpret_test/TestAstFunc2/stmtfor13.txtar create mode 100644 lang/interpret_test/TestAstFunc2/stmtfor14.txtar create mode 100644 lang/interpret_test/TestAstFunc2/stmtfor15.txtar create mode 100644 lang/interpret_test/TestAstFunc2/stmtfor16.txtar create mode 100644 lang/interpret_test/TestAstFunc2/stmtfor17.txtar create mode 100644 lang/interpret_test/TestAstFunc2/stmtfor18.txtar create mode 100644 lang/interpret_test/TestAstFunc2/stmtfor19.txtar create mode 100644 lang/interpret_test/TestAstFunc2/stmtfor20.txtar create mode 100644 lang/interpret_test/TestAstFunc2/stmtfor21.txtar create mode 100644 lang/interpret_test/TestAstFunc2/stmtfor22.txtar create mode 100644 lang/interpret_test/TestAstFunc2/stmtfor23.txtar create mode 100644 lang/interpret_test/TestAstFunc2/stmtfor24.txtar create mode 100644 lang/interpret_test/TestAstFunc2/stmtfor25.txtar create mode 100644 lang/interpret_test/TestAstFunc2/stmtfor26.txtar create mode 100644 lang/interpret_test/TestAstFunc2/stmtfor27.txtar create mode 100644 lang/interpret_test/TestAstFunc2/stmtfor28.txtar create mode 100644 lang/interpret_test/TestAstFunc2/stmtfor29.txtar create mode 100644 lang/interpret_test/TestAstFunc2/stmtfor30.txtar create mode 100644 lang/interpret_test/TestAstFunc2/stmtfor31.txtar create mode 100644 lang/interpret_test/TestAstFunc2/stmtfor32.txtar create mode 100644 lang/interpret_test/TestAstFunc2/stmtfor33.txtar create mode 100644 lang/interpret_test/TestAstFunc2/stmtfor34.txtar create mode 100644 lang/interpret_test/TestAstFunc2/stmtfor35.txtar create mode 100644 lang/interpret_test/TestAstFunc2/stmtfor36.txtar create mode 100644 lang/interpret_test/TestAstFunc2/stmtfor37.txtar create mode 100644 lang/interpret_test/TestAstFunc2/stmtfor38.txtar create mode 100644 lang/interpret_test/TestAstFunc2/stmtfor39.txtar create mode 100644 lang/interpret_test/TestAstFunc2/stmtfor40.txtar create mode 100644 lang/interpret_test/TestAstFunc2/stmtfor41.txtar create mode 100644 lang/interpret_test/TestAstFunc2/stmtfor42.txtar create mode 100644 lang/interpret_test/TestAstFunc2/stmtfor43.txtar create mode 100644 lang/interpret_test/TestAstFunc2/wat.txtar create mode 100644 lang/interpret_test/TestAstFunc2/watsam1.txtar create mode 100644 lang/interpret_test/TestAstFunc2/watsam2.txtar diff --git a/docs/language-guide.md b/docs/language-guide.md index 4c6937de..9728c608 100644 --- a/docs/language-guide.md +++ b/docs/language-guide.md @@ -100,6 +100,15 @@ expression } ``` +- **for**: loop over a list with a body of statements + + ```mcl + $list = ["a", "b", "c",] + for $index, $value in $list { + # some statements go here + } + ``` + - **resource**: produces a resource ```mcl diff --git a/examples/lang/for.mcl b/examples/lang/for.mcl new file mode 100644 index 00000000..e2c79b44 --- /dev/null +++ b/examples/lang/for.mcl @@ -0,0 +1,39 @@ +import "datetime" +import "fmt" +import "math" + +$now = datetime.now() +$alpha = if math.mod($now, 2) == 0 { + "m" +} else { + "j" +} + +$list0 = ["a", "b", "c",] +$list1 = ["d", "e", "f",] +$list2 = ["g", "h", "i",] +$list3 = ["${alpha}", "k", "l",] + +$list = [$list0, $list1, $list2, $list3,] + +for $index, $value in $list { + for $i, $v in $value { + $s = fmt.printf("%s is %d", $v, $i+$index) + print [$s,] { + Meta:autogroup => false, + } + } +} + +#Vertex: test[a is 0] +#Vertex: test[b is 1] +#Vertex: test[c is 2] +#Vertex: test[d is 1] +#Vertex: test[e is 2] +#Vertex: test[f is 3] +#Vertex: test[g is 2] +#Vertex: test[h is 3] +#Vertex: test[i is 4] +#Vertex: test[j is 3] +#Vertex: test[k is 4] +#Vertex: test[l is 5] diff --git a/lang/ast/scopegraph.go b/lang/ast/scopegraph.go index 9eb3d3eb..538eb4ed 100644 --- a/lang/ast/scopegraph.go +++ b/lang/ast/scopegraph.go @@ -73,6 +73,11 @@ func (obj *StmtIf) ScopeGraph(g *pgraph.Graph) { g.AddVertex(obj) } +// ScopeGraph adds nodes and vertices to the supplied graph. +func (obj *StmtFor) ScopeGraph(g *pgraph.Graph) { + g.AddVertex(obj) +} + // ScopeGraph adds nodes and vertices to the supplied graph. func (obj *StmtProg) ScopeGraph(g *pgraph.Graph) { g.AddVertex(obj) @@ -196,6 +201,17 @@ func (obj *ExprParam) ScopeGraph(g *pgraph.Graph) { g.AddVertex(obj) } +// ScopeGraph adds nodes and vertices to the supplied graph. +func (obj *ExprIterated) ScopeGraph(g *pgraph.Graph) { + g.AddVertex(obj) + definition, ok := obj.Definition.(interfaces.ScopeGrapher) + if !ok { + panic("can't graph scope") // programming error + } + definition.ScopeGraph(g) + g.AddEdge(obj, obj.Definition, &pgraph.SimpleEdge{Name: "def"}) +} + // ScopeGraph adds nodes and vertices to the supplied graph. func (obj *ExprPoly) ScopeGraph(g *pgraph.Graph) { g.AddVertex(obj) diff --git a/lang/ast/structs.go b/lang/ast/structs.go index 563f52ad..36ffd376 100644 --- a/lang/ast/structs.go +++ b/lang/ast/structs.go @@ -347,12 +347,17 @@ func (obj *StmtBind) TypeCheck() ([]*interfaces.UnificationInvariant, error) { // children might. This particular bind statement adds its linked expression to // the graph. It is not logically done in the ExprVar since that could exist // multiple times for the single binding operation done here. -func (obj *StmtBind) Graph() (*pgraph.Graph, error) { - emptyContext := map[string]interfaces.Func{} - g, _, err := obj.Value.Graph(emptyContext) +func (obj *StmtBind) Graph(env *interfaces.Env) (*pgraph.Graph, error) { + g, _, err := obj.privateGraph(env) return g, err } +// privateGraph is a more general version of Graph which also returns a Func. +func (obj *StmtBind) privateGraph(env *interfaces.Env) (*pgraph.Graph, interfaces.Func, error) { + g, f, err := obj.Value.Graph(env) + return g, f, err +} + // Output for the bind statement produces no output. Any values of interest come // from the use of the var which this binds the expression to. func (obj *StmtBind) Output(map[interfaces.Func]types.Value) (*interfaces.Output, error) { @@ -669,7 +674,7 @@ func (obj *StmtRes) TypeCheck() ([]*interfaces.UnificationInvariant, error) { // Since I don't think it's worth extending the Stmt API for this, we can do the // checks here at the beginning, and error out if something was invalid. In this // particular case, the issue is one of catching duplicate meta fields. -func (obj *StmtRes) Graph() (*pgraph.Graph, error) { +func (obj *StmtRes) Graph(env *interfaces.Env) (*pgraph.Graph, error) { metaNames := make(map[string]struct{}) for _, x := range obj.Contents { line, ok := x.(*StmtResMeta) @@ -709,15 +714,16 @@ func (obj *StmtRes) Graph() (*pgraph.Graph, error) { return nil, err } - g, f, err := obj.Name.Graph(map[string]interfaces.Func{}) + g, f, err := obj.Name.Graph(env) if err != nil { return nil, err } + graph.AddGraph(g) obj.namePtr = f for _, x := range obj.Contents { - g, err := x.Graph() + g, err := x.Graph(env) if err != nil { return nil, err } @@ -1212,7 +1218,7 @@ type StmtResContents interface { Ordering(map[string]interfaces.Node) (*pgraph.Graph, map[interfaces.Node]string, error) SetScope(*interfaces.Scope) error TypeCheck(kind string) ([]*interfaces.UnificationInvariant, error) - Graph() (*pgraph.Graph, error) + Graph(env *interfaces.Env) (*pgraph.Graph, error) } // StmtResField represents a single field in the parsed resource representation. @@ -1475,13 +1481,13 @@ func (obj *StmtResField) TypeCheck(kind string) ([]*interfaces.UnificationInvari // to the resources created, but rather, once all the values (expressions) with // no outgoing edges have produced at least a single value, then the resources // know they're able to be built. -func (obj *StmtResField) Graph() (*pgraph.Graph, error) { +func (obj *StmtResField) Graph(env *interfaces.Env) (*pgraph.Graph, error) { graph, err := pgraph.NewGraph("resfield") if err != nil { return nil, err } - g, f, err := obj.Value.Graph(map[string]interfaces.Func{}) + g, f, err := obj.Value.Graph(env) if err != nil { return nil, err } @@ -1489,7 +1495,7 @@ func (obj *StmtResField) Graph() (*pgraph.Graph, error) { obj.valuePtr = f if obj.Condition != nil { - g, f, err := obj.Condition.Graph(map[string]interfaces.Func{}) + g, f, err := obj.Condition.Graph(env) if err != nil { return nil, err } @@ -1722,20 +1728,20 @@ func (obj *StmtResEdge) TypeCheck(kind string) ([]*interfaces.UnificationInvaria // to the resources created, but rather, once all the values (expressions) with // no outgoing edges have produced at least a single value, then the resources // know they're able to be built. -func (obj *StmtResEdge) Graph() (*pgraph.Graph, error) { +func (obj *StmtResEdge) Graph(env *interfaces.Env) (*pgraph.Graph, error) { graph, err := pgraph.NewGraph("resedge") if err != nil { return nil, err } - g, err := obj.EdgeHalf.Graph() + g, err := obj.EdgeHalf.Graph(env) if err != nil { return nil, err } graph.AddGraph(g) if obj.Condition != nil { - g, f, err := obj.Condition.Graph(map[string]interfaces.Func{}) + g, f, err := obj.Condition.Graph(env) if err != nil { return nil, err } @@ -2072,13 +2078,13 @@ func (obj *StmtResMeta) TypeCheck(kind string) ([]*interfaces.UnificationInvaria // to the resources created, but rather, once all the values (expressions) with // no outgoing edges have produced at least a single value, then the resources // know they're able to be built. -func (obj *StmtResMeta) Graph() (*pgraph.Graph, error) { +func (obj *StmtResMeta) Graph(env *interfaces.Env) (*pgraph.Graph, error) { graph, err := pgraph.NewGraph("resmeta") if err != nil { return nil, err } - g, f, err := obj.MetaExpr.Graph(map[string]interfaces.Func{}) + g, f, err := obj.MetaExpr.Graph(env) if err != nil { return nil, err } @@ -2086,7 +2092,7 @@ func (obj *StmtResMeta) Graph() (*pgraph.Graph, error) { obj.metaExprPtr = f if obj.Condition != nil { - g, f, err := obj.Condition.Graph(map[string]interfaces.Func{}) + g, f, err := obj.Condition.Graph(env) if err != nil { return nil, err } @@ -2344,14 +2350,14 @@ func (obj *StmtEdge) TypeCheck() ([]*interfaces.UnificationInvariant, error) { // to the edges created, but rather, once all the values (expressions) with no // outgoing function graph edges have produced at least a single value, then the // edges know they're able to be built. -func (obj *StmtEdge) Graph() (*pgraph.Graph, error) { +func (obj *StmtEdge) Graph(env *interfaces.Env) (*pgraph.Graph, error) { graph, err := pgraph.NewGraph("edge") if err != nil { return nil, err } for _, x := range obj.EdgeHalfList { - g, err := x.Graph() + g, err := x.Graph(env) if err != nil { return nil, err } @@ -2611,8 +2617,8 @@ func (obj *StmtEdgeHalf) TypeCheck() ([]*interfaces.UnificationInvariant, error) // to the resources created, but rather, once all the values (expressions) with // no outgoing edges have produced at least a single value, then the resources // know they're able to be built. -func (obj *StmtEdgeHalf) Graph() (*pgraph.Graph, error) { - g, f, err := obj.Name.Graph(map[string]interfaces.Func{}) +func (obj *StmtEdgeHalf) Graph(env *interfaces.Env) (*pgraph.Graph, error) { + g, f, err := obj.Name.Graph(env) if err != nil { return nil, err } @@ -2923,13 +2929,13 @@ func (obj *StmtIf) TypeCheck() ([]*interfaces.UnificationInvariant, error) { // shouldn't have any ill effects. // XXX: is this completely true if we're running technically impure, but safe // built-in functions on both branches? Can we turn off half of this? -func (obj *StmtIf) Graph() (*pgraph.Graph, error) { +func (obj *StmtIf) Graph(env *interfaces.Env) (*pgraph.Graph, error) { graph, err := pgraph.NewGraph("if") if err != nil { return nil, err } - g, f, err := obj.Condition.Graph(map[string]interfaces.Func{}) + g, f, err := obj.Condition.Graph(env) if err != nil { return nil, err } @@ -2940,7 +2946,7 @@ func (obj *StmtIf) Graph() (*pgraph.Graph, error) { if x == nil { continue } - g, err := x.Graph() + g, err := x.Graph(env) if err != nil { return nil, err } @@ -2991,6 +2997,514 @@ func (obj *StmtIf) Output(table map[interfaces.Func]types.Value) (*interfaces.Ou }, nil } +// StmtFor represents an iteration over a list. The body contains statements. +type StmtFor struct { + Textarea + data *interfaces.Data + scope *interfaces.Scope // store for referencing this later + + Index string // no $ prefix + Value string // no $ prefix + + TypeIndex *types.Type + TypeValue *types.Type + + indexParam *ExprParam + valueParam *ExprParam + + Expr interfaces.Expr + exprPtr interfaces.Func // ptr for table lookup + Body interfaces.Stmt // optional, but usually present + + iterBody []interfaces.Stmt +} + +// String returns a short representation of this statement. +func (obj *StmtFor) String() string { + // TODO: improve/change this if needed + s := fmt.Sprintf("for($%s, $%s)", obj.Index, obj.Value) + s += fmt.Sprintf(" in %s", obj.Expr.String()) + if obj.Body != nil { + s += fmt.Sprintf(" { %s }", obj.Body.String()) + } + return s +} + +// Apply is a general purpose iterator method that operates on any AST node. It +// is not used as the primary AST traversal function because it is less readable +// and easy to reason about than manually implementing traversal for each node. +// Nevertheless, it is a useful facility for operations that might only apply to +// a select number of node types, since they won't need extra noop iterators... +func (obj *StmtFor) Apply(fn func(interfaces.Node) error) error { + if err := obj.Expr.Apply(fn); err != nil { + return err + } + if obj.Body != nil { + if err := obj.Body.Apply(fn); err != nil { + return err + } + } + return fn(obj) +} + +// Init initializes this branch of the AST, and returns an error if it fails to +// validate. +func (obj *StmtFor) Init(data *interfaces.Data) error { + obj.data = data + obj.Textarea.Setup(data) + + obj.iterBody = []interfaces.Stmt{} + + if err := obj.Expr.Init(data); err != nil { + return err + } + if obj.Body != nil { + if err := obj.Body.Init(data); err != nil { + return err + } + } + // XXX: remove this check if we can! + for _, stmt := range obj.Body.(*StmtProg).Body { + if _, ok := stmt.(*StmtImport); !ok { + continue + } + return fmt.Errorf("a StmtImport can't be contained inside a StmtFor") + } + return nil +} + +// Interpolate returns a new node (aka a copy) once it has been expanded. This +// generally increases the size of the AST when it is used. It calls Interpolate +// on any child elements and builds the new node with those new node contents. +func (obj *StmtFor) Interpolate() (interfaces.Stmt, error) { + expr, err := obj.Expr.Interpolate() + if err != nil { + return nil, errwrap.Wrapf(err, "could not interpolate Expr") + } + var body interfaces.Stmt + if obj.Body != nil { + body, err = obj.Body.Interpolate() + if err != nil { + return nil, errwrap.Wrapf(err, "could not interpolate Body") + } + } + return &StmtFor{ + Textarea: obj.Textarea, + data: obj.data, + scope: obj.scope, // XXX: Should we copy/include this here? + + Index: obj.Index, + Value: obj.Value, + + TypeIndex: obj.TypeIndex, + TypeValue: obj.TypeValue, + + indexParam: obj.indexParam, // XXX: Should we copy/include this here? + valueParam: obj.valueParam, // XXX: Should we copy/include this here? + + Expr: expr, + exprPtr: obj.exprPtr, // XXX: Should we copy/include this here? + Body: body, + + iterBody: obj.iterBody, // XXX: Should we copy/include this here? + }, nil +} + +// Copy returns a light copy of this struct. Anything static will not be copied. +func (obj *StmtFor) Copy() (interfaces.Stmt, error) { + copied := false + expr, err := obj.Expr.Copy() + if err != nil { + return nil, errwrap.Wrapf(err, "could not copy Expr") + } + if expr != obj.Expr { // must have been copied, or pointer would be same + copied = true + } + + var body interfaces.Stmt + if obj.Body != nil { + body, err = obj.Body.Copy() + if err != nil { + return nil, errwrap.Wrapf(err, "could not copy Body") + } + if body != obj.Body { + copied = true + } + } + + if !copied { // it's static + return obj, nil + } + return &StmtFor{ + Textarea: obj.Textarea, + data: obj.data, + scope: obj.scope, // XXX: Should we copy/include this here? + + Index: obj.Index, + Value: obj.Value, + + TypeIndex: obj.TypeIndex, + TypeValue: obj.TypeValue, + + indexParam: obj.indexParam, // XXX: Should we copy/include this here? + valueParam: obj.valueParam, // XXX: Should we copy/include this here? + + Expr: expr, + exprPtr: obj.exprPtr, // XXX: Should we copy/include this here? + Body: body, + + iterBody: obj.iterBody, // XXX: Should we copy/include this here? + }, nil +} + +// Ordering returns a graph of the scope ordering that represents the data flow. +// This can be used in SetScope so that it knows the correct order to run it in. +func (obj *StmtFor) Ordering(produces map[string]interfaces.Node) (*pgraph.Graph, map[interfaces.Node]string, error) { + graph, err := pgraph.NewGraph("ordering") + if err != nil { + return nil, nil, err + } + graph.AddVertex(obj) + + // Additional constraints: We know the condition has to be satisfied + // before this for statement itself can be used, since we depend on that + // value. + edge := &pgraph.SimpleEdge{Name: "stmtforexpr1"} + graph.AddEdge(obj.Expr, obj, edge) // prod -> cons + + cons := make(map[interfaces.Node]string) + + g, c, err := obj.Expr.Ordering(produces) + if err != nil { + return nil, nil, err + } + graph.AddGraph(g) // add in the child graph + + for k, v := range c { // c is consumes + x, exists := cons[k] + if exists && v != x { + return nil, nil, fmt.Errorf("consumed value is different, got `%+v`, expected `%+v`", x, v) + } + cons[k] = v // add to map + + n, exists := produces[v] + if !exists { + continue + } + edge := &pgraph.SimpleEdge{Name: "stmtforexpr2"} + graph.AddEdge(n, k, edge) + } + + if obj.Body == nil { // return early + return graph, cons, nil + } + + // additional constraints... + edge1 := &pgraph.SimpleEdge{Name: "stmtforbodyexpr"} + graph.AddEdge(obj.Expr, obj.Body, edge1) // prod -> cons + edge2 := &pgraph.SimpleEdge{Name: "stmtforbody1"} + graph.AddEdge(obj.Body, obj, edge2) // prod -> cons + + nodes := []interfaces.Stmt{obj.Body} // XXX: are there more to add? + + for _, node := range nodes { // "dry" + g, c, err := node.Ordering(produces) + if err != nil { + return nil, nil, err + } + graph.AddGraph(g) // add in the child graph + + for k, v := range c { // c is consumes + x, exists := cons[k] + if exists && v != x { + return nil, nil, fmt.Errorf("consumed value is different, got `%+v`, expected `%+v`", x, v) + } + cons[k] = v // add to map + + n, exists := produces[v] + if !exists { + continue + } + edge := &pgraph.SimpleEdge{Name: "stmtforbody2"} + graph.AddEdge(n, k, edge) + } + } + + return graph, cons, nil +} + +// SetScope stores the scope for later use in this resource and its children, +// which it propagates this downwards to. +func (obj *StmtFor) SetScope(scope *interfaces.Scope) error { + if scope == nil { + scope = interfaces.EmptyScope() + } + obj.scope = scope // store for later + + if err := obj.Expr.SetScope(scope, map[string]interfaces.Expr{}); err != nil { // XXX: empty sctx? + return err + } + + if obj.Body == nil { // no loop body, we're done early + return nil + } + + // We need to build the two ExprParam's here, and those will contain the + // type unification variables, so we might as well populate those parts + // now, rather than waiting for the subsequent TypeCheck step. + + typExprIndex := obj.TypeIndex + if obj.TypeIndex == nil { + typExprIndex = &types.Type{ + Kind: types.KindUnification, + Uni: types.NewElem(), // unification variable, eg: ?1 + } + } + // We know this one is types.TypeInt, but we only officially determine + // that in the subsequent TypeCheck step since we need to relate things + // to the input param so it can be easily solved if it's a variable... + obj.indexParam = newExprParam( + obj.Index, + typExprIndex, + ) + + typExprValue := obj.TypeValue + if obj.TypeValue == nil { + typExprValue = &types.Type{ + Kind: types.KindUnification, + Uni: types.NewElem(), // unification variable, eg: ?1 + } + } + obj.valueParam = newExprParam( + obj.Value, + typExprValue, + ) + + newScope := scope.Copy() + newScope.Iterated = true // important! + newScope.Variables[obj.Index] = obj.indexParam + newScope.Variables[obj.Value] = obj.valueParam + + return obj.Body.SetScope(newScope) +} + +// TypeCheck returns the list of invariants that this node produces. It does so +// recursively on any children elements that exist in the AST, and returns the +// collection to the caller. It calls TypeCheck for child statements, and +// Infer/Check for child expressions. +func (obj *StmtFor) TypeCheck() ([]*interfaces.UnificationInvariant, error) { + // Don't call obj.Expr.Check here! + typ, invariants, err := obj.Expr.Infer() + if err != nil { + return nil, err + } + + // The type unification variables get created in SetScope! (If needed!) + typExprIndex := obj.indexParam.typ + typExprValue := obj.valueParam.typ + + typExpr := &types.Type{ + Kind: types.KindList, + Val: typExprValue, + } + + invar := &interfaces.UnificationInvariant{ + Node: obj, + Expr: obj.Expr, + Expect: typExpr, // the list + Actual: typ, + } + invariants = append(invariants, invar) + + // The following two invariants are needed to ensure the ExprParam's are + // added to the unification solver so that we actually benefit from that + // relationship and solution! + invarIndex := &interfaces.UnificationInvariant{ + Node: obj, + Expr: obj.indexParam, + Expect: typExprIndex, // the list index type + Actual: types.TypeInt, // here we finally also say it's an int! + } + invariants = append(invariants, invarIndex) + + invarValue := &interfaces.UnificationInvariant{ + Node: obj, + Expr: obj.valueParam, + Expect: typExprValue, // the list element type + Actual: typExprValue, + } + invariants = append(invariants, invarValue) + + if obj.Body != nil { + invars, err := obj.Body.TypeCheck() + if err != nil { + return nil, err + } + invariants = append(invariants, invars...) + } + + return invariants, nil +} + +// Graph returns the reactive function graph which is expressed by this node. It +// includes any vertices produced by this node, and the appropriate edges to any +// vertices that are produced by its children. Nodes which fulfill the Expr +// interface directly produce vertices (and possible children) where as nodes +// that fulfill the Stmt interface do not produces vertices, where as their +// children might. This particular for statement has lots of complex magic to +// make it all work. +func (obj *StmtFor) Graph(env *interfaces.Env) (*pgraph.Graph, error) { + graph, err := pgraph.NewGraph("for") + if err != nil { + return nil, err + } + + g, f, err := obj.Expr.Graph(env) + if err != nil { + return nil, err + } + graph.AddGraph(g) + obj.exprPtr = f + + if obj.Body == nil { // no loop body, we're done early + return graph, nil + } + + mutex := &sync.Mutex{} + + // This gets called once per iteration, each time the list changes. + appendToIterBody := func(innerTxn interfaces.Txn, index int, value interfaces.Func) error { + // Extend the environment with the two loop variables. + extendedEnv := env.Copy() + + // calling convention + extendedEnv.Variables[obj.indexParam.envKey] = &interfaces.FuncSingleton{ + MakeFunc: func() (*pgraph.Graph, interfaces.Func, error) { + f := &structs.ConstFunc{ + Value: &types.IntValue{ + V: int64(index), + }, + NameHint: obj.Index, // XXX: is this right? + } + g, err := pgraph.NewGraph("g") + if err != nil { + return nil, nil, err + } + g.AddVertex(f) + return g, f, nil + }, + } + + // XXX: create the function in ForFunc instead? + //extendedEnv.Variables[obj.Index] = index + //extendedEnv.Variables[obj.Value] = value + //extendedEnv.Variables[obj.valueParam.envKey] = value + extendedEnv.Variables[obj.valueParam.envKey] = &interfaces.FuncSingleton{ // XXX: We could set this one statically + MakeFunc: func() (*pgraph.Graph, interfaces.Func, error) { + f := value + g, err := pgraph.NewGraph("g") + if err != nil { + return nil, nil, err + } + g.AddVertex(f) + return g, f, nil + }, + } + + // NOTE: We previously considered doing a "copy singletons" here + // instead, but decided we didn't need it after all. + body, err := obj.Body.Copy() + if err != nil { + return err + } + + mutex.Lock() + obj.iterBody = append(obj.iterBody, body) + // TODO: Can we avoid using append and do it this way instead? + //obj.iterBody[index] = body + mutex.Unlock() + + // Create a subgraph from the lambda's body, instantiating the + // lambda's parameters with the args and the other variables + // with the nodes in the captured environment. + subgraph, err := body.Graph(extendedEnv) + if err != nil { + return errwrap.Wrapf(err, "could not create the lambda body's subgraph") + } + + innerTxn.AddGraph(subgraph) + + // We don't need an output func because body.Graph is a + // statement and it doesn't return an interfaces.Func, + // only the expression versions return those! + return nil + } + + // Add a vertex for the list passing itself. + edgeName := structs.ForFuncArgNameList + forFunc := &structs.ForFunc{ + IndexType: obj.indexParam.typ, + ValueType: obj.valueParam.typ, + + EdgeName: edgeName, + + AppendToIterBody: appendToIterBody, + ClearIterBody: func(length int) { // XXX: use length? + mutex.Lock() + obj.iterBody = []interfaces.Stmt{} + mutex.Unlock() + }, + } + graph.AddVertex(forFunc) + graph.AddEdge(f, forFunc, &interfaces.FuncEdge{ + Args: []string{edgeName}, + }) + + return graph, nil +} + +// Output returns the output that this "program" produces. This output is what +// is used to build the output graph. This only exists for statements. The +// analogous function for expressions is Value. Those Value functions might get +// called by this Output function if they are needed to produce the output. +func (obj *StmtFor) Output(table map[interfaces.Func]types.Value) (*interfaces.Output, error) { + if obj.exprPtr == nil { + return nil, ErrFuncPointerNil + } + expr, exists := table[obj.exprPtr] + if !exists { + return nil, ErrTableNoValue + } + + if obj.Body == nil { // logically body is optional + return &interfaces.Output{}, nil // XXX: test this doesn't panic anything + } + + resources := []engine.Res{} + edges := []*interfaces.Edge{} + + list := expr.List() // must not panic! + + for index := range list { + // index is a golang int, value is an mcl types.Value + // XXX: Do we need a mutex around this iterBody access? + output, err := obj.iterBody[index].Output(table) + if err != nil { + return nil, err + } + + if output != nil { + resources = append(resources, output.Resources...) + edges = append(edges, output.Edges...) + } + } + + return &interfaces.Output{ + Resources: resources, + Edges: edges, + }, nil +} + // StmtProg represents a list of stmt's. This usually occurs at the top-level of // any program, and often within an if stmt. It also contains the logic so that // the bind statement's are correctly applied in this scope, and irrespective of @@ -3004,6 +3518,8 @@ type StmtProg struct { importProgs []*StmtProg // list of child programs after running SetScope importFiles []string // list of files seen during the SetScope import + nodeOrder []interfaces.Stmt // used for .Graph + Body []interfaces.Stmt } @@ -3039,6 +3555,7 @@ func (obj *StmtProg) Init(data *interfaces.Data) error { obj.data = data obj.importProgs = []*StmtProg{} obj.importFiles = []string{} + obj.nodeOrder = []interfaces.Stmt{} for _, x := range obj.Body { if err := x.Init(data); err != nil { return err @@ -3125,6 +3642,7 @@ func (obj *StmtProg) Interpolate() (interfaces.Stmt, error) { scope: obj.scope, importProgs: obj.importProgs, // TODO: do we even need this here? importFiles: obj.importFiles, + nodeOrder: obj.nodeOrder, Body: body, }, nil } @@ -3133,6 +3651,9 @@ func (obj *StmtProg) Interpolate() (interfaces.Stmt, error) { func (obj *StmtProg) Copy() (interfaces.Stmt, error) { copied := false body := []interfaces.Stmt{} + + m := make(map[interfaces.Stmt]interfaces.Stmt) // mapping + for _, x := range obj.Body { cp, err := x.Copy() if err != nil { @@ -3142,6 +3663,12 @@ func (obj *StmtProg) Copy() (interfaces.Stmt, error) { copied = true } body = append(body, cp) + m[x] = cp // store mapping + } + + newNodeOrder := []interfaces.Stmt{} + for _, n := range obj.nodeOrder { + newNodeOrder = append(newNodeOrder, m[n]) } if !copied { // it's static @@ -3153,6 +3680,7 @@ func (obj *StmtProg) Copy() (interfaces.Stmt, error) { scope: obj.scope, importProgs: obj.importProgs, // TODO: do we even need this here? importFiles: obj.importFiles, + nodeOrder: newNodeOrder, // we need to update this Body: body, }, nil } @@ -3254,6 +3782,28 @@ func (obj *StmtProg) Ordering(produces map[string]interfaces.Node) (*pgraph.Grap } prod[uid] = stmt // store } + + // XXX: I have no idea if this is needed or is done correctly. + // XXX: If I add it, it turns this into a dag. + //if stmt, ok := x.(*StmtFor); ok { + // if stmt.Index == "" { + // return nil, nil, fmt.Errorf("missing index name") + // } + // uid1 := varOrderingPrefix + stmt.Index // ordering id + // if n, exists := prod[uid1]; exists { + // return nil, nil, fmt.Errorf("duplicate assignment to `%s`, have: %s", uid1, n) + // } + // prod[uid1] = stmt // store + // + // if stmt.Value == "" { + // return nil, nil, fmt.Errorf("missing value name") + // } + // uid2 := varOrderingPrefix + stmt.Value // ordering id + // if n, exists := prod[uid2]; exists { + // return nil, nil, fmt.Errorf("duplicate assignment to `%s`, have: %s", uid2, n) + // } + // prod[uid2] = stmt // store + //} } newProduces := CopyNodeMapping(produces) // don't modify the input map! @@ -3535,7 +4085,7 @@ func (obj *StmtProg) importSystemScope(name string) (*interfaces.Scope, error) { scope := &interfaces.Scope{ // TODO: we could use the core API for variables somehow... Variables: variables, - Functions: functions, // map[string]interfaces.Expr + Functions: functions, // map[string]Expr // TODO: we could add a core API for classes too! //Classes: make(map[string]interfaces.Stmt), } @@ -3998,6 +4548,7 @@ func (obj *StmtProg) SetScope(scope *interfaces.Scope) error { if obj.data.Debug { obj.data.Logf("prog: set scope: ordering: %+v", stmts) } + obj.nodeOrder = stmts // save for .Graph() // Track all the bind statements, functions, and classes. This is used // for duplicate checking. These might appear out-of-order as code, but @@ -4046,15 +4597,22 @@ func (obj *StmtProg) SetScope(scope *interfaces.Scope) error { } 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 loopScope.Iterated { + exprIterated := newExprIterated(bind.Ident, bind.Value) + loopScope.Variables[bind.Ident] = exprIterated + } else { + // 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) } @@ -4093,12 +4651,39 @@ func (obj *StmtProg) SetScope(scope *interfaces.Scope) error { 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, - }, + + if loopScope.Iterated { + // XXX: ExprPoly or ExprTopLevel might + // end up breaking something here... + //loopScope.Functions[fn.Name] = &ExprIterated{ + // Name: fn.Name, + // Definition: &ExprPoly{ + // Definition: &ExprTopLevel{ + // Definition: f, // store the *ExprFunc + // CapturedScope: capturedScope, + // }, + // }, + //} + // We reordered the nesting here to try and fix some bug. + loopScope.Functions[fn.Name] = &ExprPoly{ + Definition: newExprIterated( + fn.Name, + &ExprTopLevel{ + Definition: f, // store the *ExprFunc + CapturedScope: capturedScope, + }, + ), + } + + } else { + loopScope.Functions[fn.Name] = &ExprPoly{ // XXX: is this ExprPoly approach optimal? + Definition: &ExprTopLevel{ + Definition: f, // store the *ExprFunc + CapturedScope: capturedScope, + }, + } } + continue } @@ -4298,30 +4883,114 @@ func (obj *StmtProg) TypeCheck() ([]*interfaces.UnificationInvariant, error) { // interface directly produce vertices (and possible children) where as nodes // that fulfill the Stmt interface do not produces vertices, where as their // children might. -func (obj *StmtProg) Graph() (*pgraph.Graph, error) { - graph, err := pgraph.NewGraph("prog") +func (obj *StmtProg) Graph(env *interfaces.Env) (*pgraph.Graph, error) { + g, _, err := obj.updateEnv(env) if err != nil { return nil, err } + return g, nil +} + +// updateEnv is a more general version of Graph. +func (obj *StmtProg) updateEnv(env *interfaces.Env) (*pgraph.Graph, *interfaces.Env, error) { + graph, err := pgraph.NewGraph("prog") + if err != nil { + return nil, nil, err + } + + loopEnv := env.Copy() + + // In this loop, we want to skip over StmtClass, StmtFunc, and StmtBind, + // but only in their "normal" boring definition modes. + for _, x := range obj.nodeOrder { + stmt, ok := x.(interfaces.Stmt) + if !ok { + continue + } + + //if _, ok := x.(*StmtImport); ok { // TODO: should we skip this? + // continue + //} - // collect all graphs that need to be included - for _, x := range obj.Body { // skip over *StmtClass here if _, ok := x.(*StmtClass); ok { continue } - // skip over StmtFunc, even though it doesn't produce anything! - if _, ok := x.(*StmtFunc); ok { - continue - } - // skip over StmtBind, even though it doesn't produce anything! - if _, ok := x.(*StmtBind); ok { + + if include, ok := x.(*StmtInclude); ok && obj.scope.Iterated { + // The include can bring a bunch of variables into scope + // so we need an entry for them in the loop Env. Let's + // fill it up. + //g, extendedEnv, err := include.class.Body.(*StmtProg).updateEnv(loopEnv) + g, extendedEnv, err := include.updateEnv(loopEnv) + //g, err := include.Graph(loopEnv) + if err != nil { + return nil, nil, err + } + loopEnv = extendedEnv // XXX: Sam says we need to change this for trickier cases! + + graph.AddGraph(g) // We DO want to add this to graph. continue } - g, err := x.Graph() + if bind, ok := x.(*StmtBind); ok { + if !obj.scope.Iterated { + continue // We do NOTHING, not even add to Graph + } + + // always squash, this is shadowing... + expr, exists := obj.scope.Variables[bind.Ident] + if !exists { + // TODO: is this a programming error? + return nil, nil, fmt.Errorf("programming error") + } + exprIterated, ok := expr.(*ExprIterated) + if !ok { + // TODO: is this a programming error? + return nil, nil, fmt.Errorf("programming error") + } + //loopEnv.Variables[exprIterated.envKey] = f + loopEnv.Variables[exprIterated.envKey] = &interfaces.FuncSingleton{ + MakeFunc: func() (*pgraph.Graph, interfaces.Func, error) { + return bind.privateGraph(loopEnv) + }, + } + + //graph.AddGraph(g) // We DO want to add this to graph. + continue + } + + if stmtFunc, ok := x.(*StmtFunc); ok { + if !obj.scope.Iterated { + continue // We do NOTHING, not even add to Graph + } + + expr, exists := obj.scope.Functions[stmtFunc.Name] // map[string]Expr + if !exists { + // programming error + return nil, nil, fmt.Errorf("programming error 1") + } + exprPoly, ok := expr.(*ExprPoly) + if !ok { + // programming error + return nil, nil, fmt.Errorf("programming error 2") + } + if _, ok := exprPoly.Definition.(*ExprIterated); !ok { + // programming error + return nil, nil, fmt.Errorf("programming error 3") + } + + // always squash, this is shadowing... + // XXX: We probably don't need to copy here says Sam. + loopEnv.Functions[expr] = loopEnv.Copy() // captured env + + // We NEVER want to add anything to the graph here. + continue + } + + g, err := stmt.Graph(loopEnv) if err != nil { - return nil, err + return nil, nil, err } graph.AddGraph(g) } @@ -4335,7 +5004,7 @@ func (obj *StmtProg) Graph() (*pgraph.Graph, error) { // graph.AddGraph(g) //} - return graph, nil + return graph, loopEnv, nil } // Output returns the output that this "program" produces. This output is what @@ -4391,7 +5060,7 @@ func (obj *StmtProg) Output(table map[interfaces.Func]types.Value) (*interfaces. func (obj *StmtProg) IsModuleUnsafe() error { // TODO: rename this function? for _, x := range obj.Body { // stmt's allowed: import, bind, func, class - // stmt's not-allowed: if, include, res, edge + // stmt's not-allowed: for, if, include, res, edge switch x.(type) { case *StmtImport: case *StmtBind: @@ -4609,7 +5278,7 @@ func (obj *StmtFunc) TypeCheck() ([]*interfaces.UnificationInvariant, error) { // that fulfill the Stmt interface do not produces vertices, where as their // children might. This particular func statement adds its linked expression to // the graph. -func (obj *StmtFunc) Graph() (*pgraph.Graph, error) { +func (obj *StmtFunc) Graph(*interfaces.Env) (*pgraph.Graph, error) { //return obj.Func.Graph(nil) // nope! return pgraph.NewGraph("stmtfunc") // do this in ExprCall instead } @@ -4630,8 +5299,8 @@ type StmtClass struct { scope *interfaces.Scope // store for referencing this later Name string - Args []*interfaces.Arg - Body interfaces.Stmt // probably a *StmtProg + Args []*interfaces.Arg // XXX: sam thinks we should name this Params and interfaces.Param + Body interfaces.Stmt // probably a *StmtProg } // String returns a short representation of this statement. @@ -4819,8 +5488,8 @@ func (obj *StmtClass) TypeCheck() ([]*interfaces.UnificationInvariant, error) { // that fulfill the Stmt interface do not produces vertices, where as their // children might. This particular func statement adds its linked expression to // the graph. -func (obj *StmtClass) Graph() (*pgraph.Graph, error) { - return obj.Body.Graph() +func (obj *StmtClass) Graph(env *interfaces.Env) (*pgraph.Graph, error) { + return obj.Body.Graph(env) } // Output for the class statement produces no output. Any values of interest @@ -4836,14 +5505,16 @@ func (obj *StmtClass) Output(table map[interfaces.Func]types.Value) (*interfaces // the interesting logic for classes happens here or in StmtProg. type StmtInclude struct { Textarea - data *interfaces.Data + data *interfaces.Data + scope *interfaces.Scope class *StmtClass // copy of class that we're using orig *StmtInclude // original pointer to this - Name string - Args []interfaces.Expr - Alias string + Name string + Args []interfaces.Expr + argsEnvKeys []*ExprIterated + Alias string } // String returns a short representation of this statement. @@ -4910,13 +5581,15 @@ func (obj *StmtInclude) Interpolate() (interfaces.Stmt, error) { orig = obj.orig } return &StmtInclude{ - Textarea: obj.Textarea, - data: obj.data, - //class: obj.class, // TODO: is this necessary? - orig: orig, - Name: obj.Name, - Args: args, - Alias: obj.Alias, + Textarea: obj.Textarea, + data: obj.data, + scope: obj.scope, + class: obj.class, // XXX: Should we copy this? + orig: orig, + Name: obj.Name, + Args: args, + argsEnvKeys: obj.argsEnvKeys, // update this if we interpolate after SetScope + Alias: obj.Alias, }, nil } @@ -4942,17 +5615,37 @@ func (obj *StmtInclude) Copy() (interfaces.Stmt, error) { copied = true // TODO: is this what we want? } + // Sometimes when we run copy it's legal for obj.class to be nil. + var newClass *StmtClass + if obj.class != nil { + stmt, err := obj.class.Copy() + if err != nil { + return nil, err + } + class, ok := stmt.(*StmtClass) + if !ok { + // programming error + return nil, fmt.Errorf("unexpected copy failure") + } + if class != obj.class { + copied = true // TODO: is this what we want? + } + newClass = class + } + if !copied { // it's static return obj, nil } return &StmtInclude{ - Textarea: obj.Textarea, - data: obj.data, - //class: obj.class, // TODO: is this necessary? - orig: orig, - Name: obj.Name, - Args: args, - Alias: obj.Alias, + Textarea: obj.Textarea, + data: obj.data, + scope: obj.scope, + class: newClass, // This seems necessary! + orig: orig, + Name: obj.Name, + Args: args, + argsEnvKeys: obj.argsEnvKeys, + Alias: obj.Alias, }, nil } @@ -5034,6 +5727,7 @@ func (obj *StmtInclude) SetScope(scope *interfaces.Scope) error { if scope == nil { scope = interfaces.EmptyScope() } + obj.scope = scope stmt, exists := scope.Classes[obj.Name] if !exists { @@ -5097,21 +5791,37 @@ func (obj *StmtInclude) SetScope(scope *interfaces.Scope) error { // We start with the scope that the class had, and we augment it with // our parameterized arg variables, which will be needed in that scope. newScope := obj.class.scope.Copy() - // Add our args `include foo(42, "bar", true)` into the class scope. - for i, arg := range obj.class.Args { // copy - newScope.Variables[arg.Name] = &ExprTopLevel{ - Definition: &ExprSingleton{ - Definition: obj.Args[i], - mutex: &sync.Mutex{}, // TODO: call Init instead - }, - CapturedScope: newScope, + if obj.scope.Iterated { // Sam says NOT obj.class.scope + obj.argsEnvKeys = make([]*ExprIterated, len(obj.class.Args)) // or just append() in loop below... + + // Add our args `include foo(42, "bar", true)` into the class scope. + for i, param := range obj.class.Args { // copy + // NOTE: similar to StmtProg.SetScope (StmtBind case) + obj.argsEnvKeys[i] = newExprIterated( + param.Name, + obj.Args[i], + ) + newScope.Variables[param.Name] = obj.argsEnvKeys[i] + } + } else { + // Add our args `include foo(42, "bar", true)` into the class scope. + for i, param := range obj.class.Args { // copy + newScope.Variables[param.Name] = &ExprTopLevel{ + Definition: &ExprSingleton{ + Definition: obj.Args[i], + + mutex: &sync.Mutex{}, // TODO: call Init instead + }, + CapturedScope: newScope, + } } } // recursion detection newScope.Chain = append(newScope.Chain, obj.orig) // add stmt to list newScope.Classes[obj.Name] = copied // overwrite with new pointer + newScope.Iterated = scope.Iterated // very important! // NOTE: This would overwrite the scope that was previously set here, // which would break the scoping rules. Scopes are propagated into @@ -5119,6 +5829,11 @@ func (obj *StmtInclude) SetScope(scope *interfaces.Scope) error { // need to use the original scope of the class as it was set as the // basis for this scope, so that we overwrite it only with the arg // changes. + // + // Whether this body is iterated or not, does not depend on whether the + // class definition site is inside of a for loop but on whether the + // StmtInclude is inside of a for loop. So we set that Iterated var + // above. if err := obj.class.Body.SetScope(newScope); err != nil { return err } @@ -5184,19 +5899,87 @@ func (obj *StmtInclude) TypeCheck() ([]*interfaces.UnificationInvariant, error) // that fulfill the Stmt interface do not produces vertices, where as their // children might. This particular func statement adds its linked expression to // the graph. -func (obj *StmtInclude) Graph() (*pgraph.Graph, error) { - graph, err := pgraph.NewGraph("include") +func (obj *StmtInclude) Graph(env *interfaces.Env) (*pgraph.Graph, error) { + g, _, err := obj.updateEnv(env) if err != nil { return nil, err } + return g, nil +} - g, err := obj.class.Graph() +// updateEnv is a more general version of Graph. +// +// Normally, an ExprIterated.Name is the same as the name in the corresponding +// StmtBind. When StmtInclude AS is used, the name in ExprIterated is the short +// name (like `result`), and the StmtBind.Ident is the short name (like +// `result`), but the ExprVar and the ExprCall use $iterated.result. Instead of +// the short name (in ExprIterated.Name and the environment) we should either +// use $iterated.result or the ExprIterated pointer. We are currently trying the +// latter (the pointer). +// +// More importantly: StmtFor.Graph copies the body of the ? for N times. If +// there are any StmtBind's their ExprSingleton's are cleared, so that each +// iteration gets its own Func. If there is a StmtInclude, it contains a copy of +// the class body, and this body is also copied once per iteration. However, the +// variables in that body do not contain the thing to which they refer, that is +// called the "referend" (since we just have a string name) and therefore if it +// refers to an ExprSingleton, that ExprSingleton does not get cleared, and +// every iteration of the loop gets the same value for that variable. This is +// fine under normal circumstances because the thing to which the ExprVar refers +// might be defined outside of the for loop, in which case, we don't want to +// copy it once per iteration. The problem is that in this case, the variable is +// pointing to something inside the for loop which is somehow not being copied. +func (obj *StmtInclude) updateEnv(env *interfaces.Env) (*pgraph.Graph, *interfaces.Env, error) { + graph, err := pgraph.NewGraph("include") if err != nil { - return nil, err + return nil, nil, err + } + + if obj.class == nil { + // programming error + return nil, nil, fmt.Errorf("can't include class %s, contents are nil", obj.Name) + } + + if obj.scope.Iterated { + loopEnv := env.Copy() + + // This args stuff is here since it's not technically needed in + // the non-iterated case, and it would only make the function + // graph bigger if that arg isn't used. The arg gets pulled in + // to the function graph via ExprSingleton otherwise. + for i, arg := range obj.Args { + //g, f, err := arg.Graph(env) + //if err != nil { + // return nil, nil, err + //} + //graph.AddGraph(g) + //paramName := obj.class.Args[i].Name + //loopEnv.Variables[paramName] = f + //loopEnv.Variables[obj.argsEnvKeys[i]] = f + loopEnv.Variables[obj.argsEnvKeys[i]] = &interfaces.FuncSingleton{ + MakeFunc: func() (*pgraph.Graph, interfaces.Func, error) { + return arg.Graph(env) + }, + } + } + + g, extendedEnv, err := obj.class.Body.(*StmtProg).updateEnv(loopEnv) + if err != nil { + return nil, nil, err + } + graph.AddGraph(g) + loopEnv = extendedEnv + + return graph, loopEnv, nil + } + + g, err := obj.class.Graph(env) + if err != nil { + return nil, nil, err } graph.AddGraph(g) - return graph, nil + return graph, env, nil } // Output returns the output that this include produces. This output is what is @@ -5304,7 +6087,7 @@ func (obj *StmtImport) TypeCheck() ([]*interfaces.UnificationInvariant, error) { // interface directly produce vertices (and possible children) where as nodes // that fulfill the Stmt interface do not produces vertices, where as their // children might. This particular statement just returns an empty graph. -func (obj *StmtImport) Graph() (*pgraph.Graph, error) { +func (obj *StmtImport) Graph(*interfaces.Env) (*pgraph.Graph, error) { return pgraph.NewGraph("import") // empty graph } @@ -5395,7 +6178,7 @@ func (obj *StmtComment) TypeCheck() ([]*interfaces.UnificationInvariant, error) // interface directly produce vertices (and possible children) where as nodes // that fulfill the Stmt interface do not produces vertices, where as their // children might. This particular graph does nothing clever. -func (obj *StmtComment) Graph() (*pgraph.Graph, error) { +func (obj *StmtComment) Graph(*interfaces.Env) (*pgraph.Graph, error) { return pgraph.NewGraph("comment") } @@ -5522,7 +6305,7 @@ func (obj *ExprBool) Func() (interfaces.Func, error) { // interface directly produce vertices (and possible children) where as nodes // that fulfill the Stmt interface do not produces vertices, where as their // children might. This returns a graph with a single vertex (itself) in it. -func (obj *ExprBool) Graph(map[string]interfaces.Func) (*pgraph.Graph, interfaces.Func, error) { +func (obj *ExprBool) Graph(*interfaces.Env) (*pgraph.Graph, interfaces.Func, error) { graph, err := pgraph.NewGraph("bool") if err != nil { return nil, nil, err @@ -5725,7 +6508,7 @@ func (obj *ExprStr) Func() (interfaces.Func, error) { // interface directly produce vertices (and possible children) where as nodes // that fulfill the Stmt interface do not produces vertices, where as their // children might. This returns a graph with a single vertex (itself) in it. -func (obj *ExprStr) Graph(map[string]interfaces.Func) (*pgraph.Graph, interfaces.Func, error) { +func (obj *ExprStr) Graph(*interfaces.Env) (*pgraph.Graph, interfaces.Func, error) { graph, err := pgraph.NewGraph("str") if err != nil { return nil, nil, err @@ -5877,7 +6660,7 @@ func (obj *ExprInt) Func() (interfaces.Func, error) { // interface directly produce vertices (and possible children) where as nodes // that fulfill the Stmt interface do not produces vertices, where as their // children might. This returns a graph with a single vertex (itself) in it. -func (obj *ExprInt) Graph(map[string]interfaces.Func) (*pgraph.Graph, interfaces.Func, error) { +func (obj *ExprInt) Graph(*interfaces.Env) (*pgraph.Graph, interfaces.Func, error) { graph, err := pgraph.NewGraph("int") if err != nil { return nil, nil, err @@ -6031,7 +6814,7 @@ func (obj *ExprFloat) Func() (interfaces.Func, error) { // interface directly produce vertices (and possible children) where as nodes // that fulfill the Stmt interface do not produces vertices, where as their // children might. This returns a graph with a single vertex (itself) in it. -func (obj *ExprFloat) Graph(map[string]interfaces.Func) (*pgraph.Graph, interfaces.Func, error) { +func (obj *ExprFloat) Graph(*interfaces.Env) (*pgraph.Graph, interfaces.Func, error) { graph, err := pgraph.NewGraph("float") if err != nil { return nil, nil, err @@ -6261,7 +7044,7 @@ func (obj *ExprList) Type() (*types.Type, error) { if err != nil { return nil, errwrap.Wrapf(interfaces.ErrTypeCurrentlyUnknown, err.Error()) } - return nil, interfaces.ErrTypeCurrentlyUnknown + return nil, errwrap.Wrapf(interfaces.ErrTypeCurrentlyUnknown, obj.String()) } return obj.typ, nil } @@ -6343,7 +7126,7 @@ func (obj *ExprList) Func() (interfaces.Func, error) { // that fulfill the Stmt interface do not produces vertices, where as their // children might. This returns a graph with a single vertex (itself) in it, and // the edges from all of the child graphs to this. -func (obj *ExprList) Graph(env map[string]interfaces.Func) (*pgraph.Graph, interfaces.Func, error) { +func (obj *ExprList) Graph(env *interfaces.Env) (*pgraph.Graph, interfaces.Func, error) { graph, err := pgraph.NewGraph("list") if err != nil { return nil, nil, err @@ -6701,7 +7484,7 @@ func (obj *ExprMap) Type() (*types.Type, error) { if err != nil { return nil, errwrap.Wrapf(interfaces.ErrTypeCurrentlyUnknown, err.Error()) } - return nil, interfaces.ErrTypeCurrentlyUnknown + return nil, errwrap.Wrapf(interfaces.ErrTypeCurrentlyUnknown, obj.String()) } return obj.typ, nil } @@ -6794,7 +7577,7 @@ func (obj *ExprMap) Func() (interfaces.Func, error) { // that fulfill the Stmt interface do not produces vertices, where as their // children might. This returns a graph with a single vertex (itself) in it, and // the edges from all of the child graphs to this. -func (obj *ExprMap) Graph(env map[string]interfaces.Func) (*pgraph.Graph, interfaces.Func, error) { +func (obj *ExprMap) Graph(env *interfaces.Env) (*pgraph.Graph, interfaces.Func, error) { graph, err := pgraph.NewGraph("map") if err != nil { return nil, nil, err @@ -7140,7 +7923,7 @@ func (obj *ExprStruct) Type() (*types.Type, error) { if err != nil { return nil, errwrap.Wrapf(interfaces.ErrTypeCurrentlyUnknown, err.Error()) } - return nil, interfaces.ErrTypeCurrentlyUnknown + return nil, errwrap.Wrapf(interfaces.ErrTypeCurrentlyUnknown, obj.String()) } return obj.typ, nil } @@ -7229,7 +8012,7 @@ func (obj *ExprStruct) Func() (interfaces.Func, error) { // that fulfill the Stmt interface do not produces vertices, where as their // children might. This returns a graph with a single vertex (itself) in it, and // the edges from all of the child graphs to this. -func (obj *ExprStruct) Graph(env map[string]interfaces.Func) (*pgraph.Graph, interfaces.Func, error) { +func (obj *ExprStruct) Graph(env *interfaces.Env) (*pgraph.Graph, interfaces.Func, error) { graph, err := pgraph.NewGraph("struct") if err != nil { return nil, nil, err @@ -7510,6 +8293,7 @@ func (obj *ExprFunc) Interpolate() (interfaces.Expr, error) { typ: obj.typ, Title: obj.Title, Args: args, + params: obj.params, Return: obj.Return, Body: body, Function: obj.Function, @@ -7606,6 +8390,7 @@ func (obj *ExprFunc) Copy() (interfaces.Expr, error) { typ: obj.typ, Title: obj.Title, Args: obj.Args, + params: obj.params, // don't copy says sam! Return: obj.Return, Body: body, // definitely copy Function: obj.Function, @@ -7691,10 +8476,10 @@ func (obj *ExprFunc) SetScope(scope *interfaces.Scope, sctx map[string]interface // make a list as long as obj.Args obj.params = make([]*ExprParam, len(obj.Args)) for i, arg := range obj.Args { - param := &ExprParam{ - typ: arg.Type, - Name: arg.Name, - } + param := newExprParam( + arg.Name, + arg.Type, + ) obj.params[i] = param sctxBody[arg.Name] = param } @@ -7776,14 +8561,14 @@ func (obj *ExprFunc) Type() (*types.Type, error) { } if obj.typ == nil { - return nil, interfaces.ErrTypeCurrentlyUnknown + return nil, errwrap.Wrapf(interfaces.ErrTypeCurrentlyUnknown, obj.String()) } return obj.typ, nil } else if len(obj.Values) > 0 { // there's nothing we can do to speculate at this time if obj.typ == nil { - return nil, interfaces.ErrTypeCurrentlyUnknown + return nil, errwrap.Wrapf(interfaces.ErrTypeCurrentlyUnknown, obj.String()) } return obj.typ, nil } @@ -7792,7 +8577,7 @@ func (obj *ExprFunc) Type() (*types.Type, error) { if obj.function == nil { // TODO: should we return ErrTypeCurrentlyUnknown instead? panic("unexpected empty function") - //return nil, interfaces.ErrTypeCurrentlyUnknown + //return nil, errwrap.Wrapf(interfaces.ErrTypeCurrentlyUnknown, obj.String()) } sig := obj.function.Info().Sig if sig != nil && !sig.HasVariant() && obj.typ == nil { // type is now known statically @@ -7800,7 +8585,7 @@ func (obj *ExprFunc) Type() (*types.Type, error) { } if obj.typ == nil { - return nil, interfaces.ErrTypeCurrentlyUnknown + return nil, errwrap.Wrapf(interfaces.ErrTypeCurrentlyUnknown, obj.String()) } return obj.typ, nil } @@ -7848,7 +8633,7 @@ func (obj *ExprFunc) Type() (*types.Type, error) { if err != nil { return nil, errwrap.Wrapf(interfaces.ErrTypeCurrentlyUnknown, err.Error()) } - return nil, interfaces.ErrTypeCurrentlyUnknown + return nil, errwrap.Wrapf(interfaces.ErrTypeCurrentlyUnknown, obj.String()) } return obj.typ, nil } @@ -7969,7 +8754,7 @@ func (obj *ExprFunc) Check(typ *types.Type) ([]*interfaces.UnificationInvariant, // interface directly produce vertices (and possible children) where as nodes // that fulfill the Stmt interface do not produces vertices, where as their // children might. This returns a graph with a single vertex (itself) in it. -func (obj *ExprFunc) Graph(env map[string]interfaces.Func) (*pgraph.Graph, interfaces.Func, error) { +func (obj *ExprFunc) Graph(env *interfaces.Env) (*pgraph.Graph, interfaces.Func, error) { // This implementation produces a graph with a single node of in-degree // zero which outputs a single FuncValue. The FuncValue is a closure, in // that it holds both a lambda body and a captured environment. This @@ -7997,12 +8782,26 @@ func (obj *ExprFunc) Graph(env map[string]interfaces.Func) (*pgraph.Graph, inter funcValueFunc = structs.FuncValueToConstFunc(&full.FuncValue{ V: func(innerTxn interfaces.Txn, args []interfaces.Func) (interfaces.Func, error) { // Extend the environment with the arguments. - extendedEnv := make(map[string]interfaces.Func) - for k, v := range env { - extendedEnv[k] = v - } - for i, arg := range obj.Args { - extendedEnv[arg.Name] = args[i] + extendedEnv := env.Copy() // TODO: Should we copy? + for i := range obj.Args { + if args[i] == nil { + return nil, fmt.Errorf("programming error") + } + param := obj.params[i] + //extendedEnv.Variables[arg.Name] = args[i] + //extendedEnv.Variables[param.envKey] = args[i] + extendedEnv.Variables[param.envKey] = &interfaces.FuncSingleton{ + MakeFunc: func() (*pgraph.Graph, interfaces.Func, error) { + f := args[i] + g, err := pgraph.NewGraph("g") + if err != nil { + return nil, nil, err + } + g.AddVertex(f) + return g, f, nil + }, + } + } // Create a subgraph from the lambda's body, instantiating the @@ -8463,7 +9262,8 @@ func (obj *ExprCall) SetScope(scope *interfaces.Scope, sctx map[string]interface target = f } - if polymorphicTarget, isPolymorphic := target.(*ExprPoly); isPolymorphic { + // NOTE: We previously used a findExprPoly helper function here. + if polymorphicTarget, isExprPoly := target.(*ExprPoly); isExprPoly { // This function call refers to a polymorphic function // expression. Those expressions can be instantiated at // different types in different parts of the program, so that @@ -8532,7 +9332,7 @@ func (obj *ExprCall) Type() (*types.Type, error) { exprFunc, isFn := obj.expr.(*ExprFunc) if !isFn { if obj.typ == nil { - return nil, interfaces.ErrTypeCurrentlyUnknown + return nil, errwrap.Wrapf(interfaces.ErrTypeCurrentlyUnknown, obj.String()) } return obj.typ, nil } @@ -8585,7 +9385,7 @@ func (obj *ExprCall) Type() (*types.Type, error) { } if obj.typ == nil { - return nil, interfaces.ErrTypeCurrentlyUnknown + return nil, errwrap.Wrapf(interfaces.ErrTypeCurrentlyUnknown, obj.String()) } return obj.typ, nil } @@ -8849,7 +9649,7 @@ func (obj *ExprCall) Check(typ *types.Type) ([]*interfaces.UnificationInvariant, // that fulfill the Stmt interface do not produces vertices, where as their // children might. This returns a graph with a single vertex (itself) in it, and // the edges from all of the child graphs to this. -func (obj *ExprCall) Graph(env map[string]interfaces.Func) (*pgraph.Graph, interfaces.Func, error) { +func (obj *ExprCall) Graph(env *interfaces.Env) (*pgraph.Graph, interfaces.Func, error) { if obj.expr == nil { // possible programming error return nil, nil, fmt.Errorf("call doesn't contain an expr pointer yet") @@ -8866,28 +9666,11 @@ func (obj *ExprCall) Graph(env map[string]interfaces.Func) (*pgraph.Graph, inter } // Find the vertex which produces the FuncValue. - var funcValueFunc interfaces.Func - if _, isParam := obj.expr.(*ExprParam); isParam { - // The function being called is a parameter from the surrounding function. - // We should be able to find this parameter in the environment. - paramFunc, exists := env[obj.Name] - if !exists { - return nil, nil, fmt.Errorf("param `%s` is not in the environment", obj.Name) - } - graph.AddVertex(paramFunc) - funcValueFunc = paramFunc - } else { - // The function being called is a top-level definition. The parameters which - // are visible at this use site must not be visible at the definition site, - // so we pass an empty environment. - emptyEnv := map[string]interfaces.Func{} - exprGraph, topLevelFunc, err := obj.expr.Graph(emptyEnv) - if err != nil { - return nil, nil, errwrap.Wrapf(err, "could not get the graph for the expr pointer") - } - graph.AddGraph(exprGraph) - funcValueFunc = topLevelFunc + g, funcValueFunc, err := obj.funcValueFunc(env) + if err != nil { + return nil, nil, err } + graph.AddGraph(g) // Loop over the arguments, add them to the graph, but do _not_ connect them // to the function vertex. Instead, each time the call vertex (which we @@ -8919,6 +9702,92 @@ func (obj *ExprCall) Graph(env map[string]interfaces.Func) (*pgraph.Graph, inter return graph, callFunc, nil } +// funcValueFunc is a helper function to make the code more readable. This was +// some very hard logic to get right for each case, and it eventually simplifies +// down to two cases after refactoring. +// TODO: Maybe future refactoring can improve this even more! +func (obj *ExprCall) funcValueFunc(env *interfaces.Env) (*pgraph.Graph, interfaces.Func, error) { + _, isParam := obj.expr.(*ExprParam) + exprIterated, isIterated := obj.expr.(*ExprIterated) + + if !isIterated || obj.Var { + // XXX: isIterated and !obj.Var -- seems to never happen + //if (isParam || isIterated) && !obj.Var // better replacement? + if isParam && !obj.Var { + return nil, nil, fmt.Errorf("programming error") + } + // XXX: AFAICT we can *always* use the real env here. Ask Sam! + useEnv := interfaces.EmptyEnv() + if (isParam || isIterated) && obj.Var { + useEnv = env + } + // If isParam, the function being called is a parameter from the + // surrounding function. We should be able to find this + // parameter in the environment. + + // If obj.Var, then the function being called is a top-level + // definition. The parameters which are visible at this use site + // must not be visible at the definition site, so we pass an + // empty environment. Sam was confused about this but apparently + // it works. He thinks that the reason it works must be in + // ExprFunc where we must be capturing the Env somehow. See: + // watsam1.mcl and watsam2.mcl for more examples. + + // If else, the function being called is a top-level definition. + // (Which is NOT inside of a for loop.) The parameters which are + // visible at this use site must not be visible at the + // definition site, so we pass the captured environment. Sam is + // VERY confused about this case. + // + //capturedEnv, exists := env.Functions[obj.Name] + //if !exists { + // return nil, nil, fmt.Errorf("programming error with `%s`", obj.Name) + //} + //useEnv = capturedEnv + // But then we decided to not use this env there after all... + + return obj.expr.Graph(useEnv) + } + + // This is: isIterated && !obj.Var + + // The ExprPoly has been unwrapped to produce a fresh ExprIterated which + // was stored in obj.expr therefore we don't want to look up obj.expr in + // env.Functions because we would not find this fresh copy of the + // ExprIterated. Instead we recover the ExprPoly and look up that + // ExprPoly in env.Functions. + expr, exists := obj.scope.Functions[obj.Name] + if !exists { + // XXX: Improve this error message. + return nil, nil, fmt.Errorf("unspecified error with: %s", obj.Name) + } + + exprPoly, ok := expr.(*ExprPoly) + if !ok { + // XXX: Improve this error message. + return nil, nil, fmt.Errorf("unspecified error with: %s", obj.Name) + } + + // The function being called is a top-level definition inside a for + // loop. The parameters which are visible at this use site must not be + // visible at the definition site, so we pass the captured environment. + // Sam is not confused ANYMORE about this case. + capturedEnv, exists := env.Functions[exprPoly] + if !exists { + // XXX: Improve this error message. + return nil, nil, fmt.Errorf("unspecified error with: %s", obj.Name) + } + + g, f, err := exprIterated.Definition.Graph(capturedEnv) + if err != nil { + return nil, nil, errwrap.Wrapf(err, "could not get the graph for the expr pointer") + } + + return g, f, nil + + // NOTE: If `isIterated && obj.Var` is now handled in the above "else"! +} + // SetValue here is used to store the result of the last computation of this // expression node after it has received all the required input values. This // value is cached and can be retrieved by calling Value. @@ -9048,7 +9917,7 @@ func (obj *ExprVar) Ordering(produces map[string]interfaces.Node) (*pgraph.Graph func (obj *ExprVar) SetScope(scope *interfaces.Scope, sctx map[string]interfaces.Expr) error { obj.scope = interfaces.EmptyScope() if scope != nil { - obj.scope = scope.Copy() + obj.scope = scope.Copy() // XXX: Sam says we probably don't need to copy this. } if monomorphicTarget, exists := sctx[obj.Name]; exists { @@ -9103,7 +9972,7 @@ func (obj *ExprVar) Type() (*types.Type, error) { } if obj.typ == nil { - return nil, interfaces.ErrTypeCurrentlyUnknown + return nil, errwrap.Wrapf(interfaces.ErrTypeCurrentlyUnknown, obj.String()) } return obj.typ, nil } @@ -9163,23 +10032,28 @@ func (obj *ExprVar) Check(typ *types.Type) ([]*interfaces.UnificationInvariant, // important for filling the logical requirements of the graph type checker, and // to avoid duplicating production of the incoming input value from the bound // expression. -func (obj *ExprVar) Graph(env map[string]interfaces.Func) (*pgraph.Graph, interfaces.Func, error) { - // Delegate to the targetExpr. - targetExpr := obj.scope.Variables[obj.Name] - if _, isParam := targetExpr.(*ExprParam); isParam { - // The variable points to a function parameter. We should be able to find - // this parameter in the environment. - targetFunc, exists := env[obj.Name] - if !exists { - return nil, nil, fmt.Errorf("param `%s` is not in the environment", obj.Name) - } +func (obj *ExprVar) Graph(env *interfaces.Env) (*pgraph.Graph, interfaces.Func, error) { + // New "env" based methodology. One day we will use this for everything, + // and not use the scope in the same way. Sam hacking! + //targetFunc, exists := env.Variables[obj.Name] + //if exists { + // if targetFunc == nil { + // panic("BUG") + // } + // graph, err := pgraph.NewGraph("ExprVar") + // if err != nil { + // return nil, nil, err + // } + // graph.AddVertex(targetFunc) + // return graph, targetFunc, nil + //} - graph, err := pgraph.NewGraph("ExprParam") - if err != nil { - return nil, nil, err - } - graph.AddVertex(targetFunc) - return graph, targetFunc, nil + // Leave this remainder here for now... + + // Delegate to the targetExpr. + targetExpr, exists := obj.scope.Variables[obj.Name] + if !exists { + return nil, nil, fmt.Errorf("scope is missing %s", obj.Name) } // The variable points to a top-level expression. @@ -9218,6 +10092,8 @@ type ExprParam struct { typ *types.Type Name string // name of the parameter + + envKey interfaces.Expr } // String returns a short representation of this expression. @@ -9242,10 +10118,12 @@ func (obj *ExprParam) Init(*interfaces.Data) error { // generally increases the size of the AST when it is used. It calls Interpolate // on any child elements and builds the new node with those new node contents. func (obj *ExprParam) Interpolate() (interfaces.Expr, error) { - return &ExprParam{ + expr := &ExprParam{ typ: obj.typ, Name: obj.Name, - }, nil + } + expr.envKey = expr + return expr, nil } // Copy returns a light copy of this struct. Anything static will not be copied. @@ -9255,8 +10133,9 @@ func (obj *ExprParam) Interpolate() (interfaces.Expr, error) { // and they won't be able to have different values. func (obj *ExprParam) Copy() (interfaces.Expr, error) { return &ExprParam{ - typ: obj.typ, - Name: obj.Name, + typ: obj.typ, + Name: obj.Name, + envKey: obj.envKey, // don't copy }, nil } @@ -9288,8 +10167,9 @@ func (obj *ExprParam) Ordering(produces map[string]interfaces.Node) (*pgraph.Gra // SetScope stores the scope for use in this resource. func (obj *ExprParam) SetScope(scope *interfaces.Scope, sctx map[string]interfaces.Expr) error { - // ExprParam doesn't have a scope, because it is the node to which a VarExpr - // can point to, it doesn't point to anything itself. + obj.envKey = obj // XXX: not being used, we use newExprParam for now + // ExprParam doesn't have a scope, because it is the node to which a + // ExprVar can point to, so it doesn't point to anything itself. return nil } @@ -9327,7 +10207,7 @@ func (obj *ExprParam) Type() (*types.Type, error) { // Return the type if it is already known statically... It is useful for // type unification to have some extra info early. if obj.typ == nil { - return nil, interfaces.ErrTypeCurrentlyUnknown + return nil, errwrap.Wrapf(interfaces.ErrTypeCurrentlyUnknown, obj.String()) } return obj.typ, nil } @@ -9385,8 +10265,13 @@ func (obj *ExprParam) Check(typ *types.Type) ([]*interfaces.UnificationInvariant // interface directly produce vertices (and possible children) where as nodes // that fulfill the Stmt interface do not produces vertices, where as their // children might. -func (obj *ExprParam) Graph(env map[string]interfaces.Func) (*pgraph.Graph, interfaces.Func, error) { - panic("ExprParam.Graph(): should not happen, ExprVar.Graph() should handle the case where the ExprVar points to an ExprParam") +func (obj *ExprParam) Graph(env *interfaces.Env) (*pgraph.Graph, interfaces.Func, error) { + fSingleton, exists := env.Variables[obj.envKey] + if !exists { + return nil, nil, fmt.Errorf("could not find `%s` in env for ExprParam", obj.Name) + } + + return fSingleton.GraphFunc() } // SetValue here is a no-op, because algorithmically when this is called from @@ -9405,6 +10290,195 @@ func (obj *ExprParam) Value() (types.Value, error) { return nil, fmt.Errorf("no value for ExprParam") } +// ExprIterated tags an Expr to indicate that we want to use the env instead of +// the scope in Graph() because this variable was defined inside a for loop. We +// create a new ExprIterated which wraps an Expr and indicates that we want to +// use an iteration-specific value instead of the wrapped Expr. It delegates to +// the Expr for SetScope() and Infer(), and looks up in the env for Graph(), the +// same as ExprParam.Graph(), and panics if any later phase is called. +type ExprIterated struct { + // Name is the name (Ident) of the StmtBind. + Name string + + // Definition is the wrapped expression. + Definition interfaces.Expr + + envKey interfaces.Expr +} + +// String returns a short representation of this expression. +func (obj *ExprIterated) String() string { + return fmt.Sprintf("iterated(%v %s)", obj.Definition, obj.Name) +} + +// Apply is a general purpose iterator method that operates on any AST node. It +// is not used as the primary AST traversal function because it is less readable +// and easy to reason about than manually implementing traversal for each node. +// Nevertheless, it is a useful facility for operations that might only apply to +// a select number of node types, since they won't need extra noop iterators... +func (obj *ExprIterated) Apply(fn func(interfaces.Node) error) error { + if obj.Definition != nil { + if err := obj.Definition.Apply(fn); err != nil { + return err + } + } + return fn(obj) +} + +// Init initializes this branch of the AST, and returns an error if it fails to +// validate. +func (obj *ExprIterated) Init(*interfaces.Data) error { + if obj.Name == "" { + return fmt.Errorf("empty name for ExprIterated") + } + if obj.Definition == nil { + return fmt.Errorf("empty Definition for ExprIterated") + } + return nil + //return langUtil.ValidateVarName(obj.Name) XXX: Should we add this? +} + +// Interpolate returns a new node (aka a copy) once it has been expanded. This +// generally increases the size of the AST when it is used. It calls Interpolate +// on any child elements and builds the new node with those new node contents. +func (obj *ExprIterated) Interpolate() (interfaces.Expr, error) { + expr := &ExprIterated{ + Name: obj.Name, + Definition: obj.Definition, + // TODO: Should we copy envKey ? + } + expr.envKey = expr + return expr, nil +} + +// Copy returns a light copy of this struct. Anything static will not be copied. +// This intentionally returns a copy, because if a function (usually a lambda) +// that is used more than once, contains this variable, we will want each +// instantiation of it to be unique, otherwise they will be the same pointer, +// and they won't be able to have different values. +func (obj *ExprIterated) Copy() (interfaces.Expr, error) { + return &ExprIterated{ + Name: obj.Name, + Definition: obj.Definition, // XXX: Should we copy this? + envKey: obj.envKey, // don't copy + }, nil +} + +// Ordering returns a graph of the scope ordering that represents the data flow. +// This can be used in SetScope so that it knows the correct order to run it in. +func (obj *ExprIterated) Ordering(produces map[string]interfaces.Node) (*pgraph.Graph, map[interfaces.Node]string, error) { + return obj.Definition.Ordering(produces) // Also do this. + // XXX: Do we need to add our own graph too? Sam says yes, maybe. + // + //graph, err := pgraph.NewGraph("ordering") + //if err != nil { + // return nil, nil, err + //} + //graph.AddVertex(obj) + // + //if obj.Name == "" { + // return nil, nil, fmt.Errorf("missing param name") + //} + //uid := paramOrderingPrefix + obj.Name // ordering id + // + //cons := make(map[interfaces.Node]string) + //cons[obj] = uid + // + //node, exists := produces[uid] + //if exists { + // edge := &pgraph.SimpleEdge{Name: "ExprIterated"} + // graph.AddEdge(node, obj, edge) // prod -> cons + //} + // + //return graph, cons, nil +} + +// SetScope stores the scope for use in this resource. +func (obj *ExprIterated) SetScope(scope *interfaces.Scope, sctx map[string]interfaces.Expr) error { + // When we copy, the pointer of the obj changes, so we save it here now, + // so that we can use it later in the env lookup! + obj.envKey = obj // XXX: not being used we use newExprIterated for now + return obj.Definition.SetScope(scope, sctx) +} + +// SetType is used to set the type of this expression once it is known. This +// usually happens during type unification, but it can also happen during +// parsing if a type is specified explicitly. Since types are static and don't +// change on expressions, if you attempt to set a different type than what has +// previously been set (when not initially known) this will error. +func (obj *ExprIterated) SetType(typ *types.Type) error { + return obj.Definition.SetType(typ) +} + +// Type returns the type of this expression. +func (obj *ExprIterated) Type() (*types.Type, error) { + return obj.Definition.Type() +} + +// Infer returns the type of itself and a collection of invariants. The returned +// type may contain unification variables. It collects the invariants by calling +// Check on its children expressions. In making those calls, it passes in the +// known type for that child to get it to "Check" it. When the type is not +// known, it should create a new unification variable to pass in to the child +// Check calls. Infer usually only calls Check on things inside of it, and often +// does not call another Infer. This Infer returns a quasi-equivalent to my +// ExprAny invariant idea. +func (obj *ExprIterated) Infer() (*types.Type, []*interfaces.UnificationInvariant, error) { + typ, invariants, err := obj.Definition.Infer() + if err != nil { + return nil, nil, err + } + + // This adds the obj ptr, so it's seen as an expr that we need to solve. + invar := &interfaces.UnificationInvariant{ + Expr: obj, + Expect: typ, + Actual: typ, + } + invariants = append(invariants, invar) + + return typ, invariants, nil +} + +// Check is checking that the input type is equal to the object that Check is +// running on. In doing so, it adds any invariants that are necessary. Check +// must always call Infer to produce the invariant. The implementation can be +// generic for all expressions. +func (obj *ExprIterated) Check(typ *types.Type) ([]*interfaces.UnificationInvariant, error) { + return interfaces.GenericCheck(obj, typ) +} + +// Graph returns the reactive function graph which is expressed by this node. It +// includes any vertices produced by this node, and the appropriate edges to any +// vertices that are produced by its children. Nodes which fulfill the Expr +// interface directly produce vertices (and possible children) where as nodes +// that fulfill the Stmt interface do not produces vertices, where as their +// children might. +func (obj *ExprIterated) Graph(env *interfaces.Env) (*pgraph.Graph, interfaces.Func, error) { + fSingleton, exists := env.Variables[obj.envKey] + if !exists { + return nil, nil, fmt.Errorf("could not find `%s` in env for ExprIterated", obj.Name) + } + + return fSingleton.GraphFunc() +} + +// SetValue here is a no-op, because algorithmically when this is called from +// the func engine, the child fields (the dest lookup expr) will have had this +// done to them first, and as such when we try and retrieve the set value from +// this expression by calling `Value`, it will build it from scratch! +func (obj *ExprIterated) SetValue(value types.Value) error { + // ignored, as we don't support ExprIterated.Value() + return nil +} + +// Value returns the value of this expression in our type system. This will +// usually only be valid once the engine has run and values have been produced. +// This might get called speculatively (early) during unification to learn more. +func (obj *ExprIterated) Value() (types.Value, error) { + return nil, fmt.Errorf("no value for ExprIterated") +} + // ExprPoly is a polymorphic expression that is a definition that can be used in // multiple places with different types. We must copy the definition at each // call site in order for the type checker to find a different type at each call @@ -9481,7 +10555,7 @@ func (obj *ExprPoly) SetType(typ *types.Type) error { // Type returns the type of this expression. func (obj *ExprPoly) Type() (*types.Type, error) { - return nil, interfaces.ErrTypeCurrentlyUnknown + return nil, errwrap.Wrapf(interfaces.ErrTypeCurrentlyUnknown, obj.String()) } // Infer returns the type of itself and a collection of invariants. The returned @@ -9509,7 +10583,7 @@ func (obj *ExprPoly) Check(typ *types.Type) ([]*interfaces.UnificationInvariant, // interface directly produce vertices (and possible children) where as nodes // that fulfill the Stmt interface do not produces vertices, where as their // children might. -func (obj *ExprPoly) Graph(env map[string]interfaces.Func) (*pgraph.Graph, interfaces.Func, error) { +func (obj *ExprPoly) Graph(env *interfaces.Env) (*pgraph.Graph, interfaces.Func, error) { panic("ExprPoly.Graph(): should not happen, all ExprPoly expressions should be gone by the time type-checking starts") } @@ -9692,7 +10766,7 @@ func (obj *ExprTopLevel) Check(typ *types.Type) ([]*interfaces.UnificationInvari // interface directly produce vertices (and possible children) where as nodes // that fulfill the Stmt interface do not produces vertices, where as their // children might. -func (obj *ExprTopLevel) Graph(env map[string]interfaces.Func) (*pgraph.Graph, interfaces.Func, error) { +func (obj *ExprTopLevel) Graph(env *interfaces.Env) (*pgraph.Graph, interfaces.Func, error) { return obj.Definition.Graph(env) } @@ -9719,8 +10793,8 @@ type ExprSingleton struct { singletonType *types.Type singletonGraph *pgraph.Graph - singletonExpr interfaces.Func - mutex *sync.Mutex // protects singletonGraph and singletonExpr + singletonFunc interfaces.Func + mutex *sync.Mutex // protects singletonGraph and singletonFunc } // String returns a short representation of this expression. @@ -9760,7 +10834,7 @@ func (obj *ExprSingleton) Interpolate() (interfaces.Expr, error) { Definition: definition, singletonType: nil, // each copy should have its own Type singletonGraph: nil, // each copy should have its own Graph - singletonExpr: nil, // each copy should have its own Func + singletonFunc: nil, // each copy should have its own Func mutex: &sync.Mutex{}, }, nil } @@ -9776,7 +10850,7 @@ func (obj *ExprSingleton) Copy() (interfaces.Expr, error) { Definition: definition, singletonType: nil, // each copy should have its own Type singletonGraph: nil, // each copy should have its own Graph - singletonExpr: nil, // each copy should have its own Func + singletonFunc: nil, // each copy should have its own Func mutex: &sync.Mutex{}, }, nil } @@ -9891,21 +10965,21 @@ func (obj *ExprSingleton) Check(typ *types.Type) ([]*interfaces.UnificationInvar // interface directly produce vertices (and possible children) where as nodes // that fulfill the Stmt interface do not produces vertices, where as their // children might. -func (obj *ExprSingleton) Graph(env map[string]interfaces.Func) (*pgraph.Graph, interfaces.Func, error) { +func (obj *ExprSingleton) Graph(env *interfaces.Env) (*pgraph.Graph, interfaces.Func, error) { obj.mutex.Lock() defer obj.mutex.Unlock() - if obj.singletonExpr == nil { + if obj.singletonFunc == nil { g, f, err := obj.Definition.Graph(env) if err != nil { return nil, nil, err } obj.singletonGraph = g - obj.singletonExpr = f + obj.singletonFunc = f return g, f, nil } - return obj.singletonGraph, obj.singletonExpr, nil + return obj.singletonGraph, obj.singletonFunc, nil } // SetValue here is a no-op, because algorithmically when this is called from @@ -10193,7 +11267,7 @@ func (obj *ExprIf) Type() (*types.Type, error) { if typ != nil { return typ, nil } - return nil, interfaces.ErrTypeCurrentlyUnknown + return nil, errwrap.Wrapf(interfaces.ErrTypeCurrentlyUnknown, obj.String()) } // Infer returns the type of itself and a collection of invariants. The returned @@ -10281,7 +11355,7 @@ func (obj *ExprIf) Func() (interfaces.Func, error) { // shouldn't have any ill effects. // XXX: is this completely true if we're running technically impure, but safe // built-in functions on both branches? Can we turn off half of this? -func (obj *ExprIf) Graph(env map[string]interfaces.Func) (*pgraph.Graph, interfaces.Func, error) { +func (obj *ExprIf) Graph(env *interfaces.Env) (*pgraph.Graph, interfaces.Func, error) { graph, err := pgraph.NewGraph("if") if err != nil { return nil, nil, err diff --git a/lang/ast/util.go b/lang/ast/util.go index 2f9f3341..59fe658d 100644 --- a/lang/ast/util.go +++ b/lang/ast/util.go @@ -339,11 +339,54 @@ func trueCallee(apparentCallee interfaces.Expr) interfaces.Expr { return trueCallee(x.Definition) case *ExprSingleton: return trueCallee(x.Definition) + case *ExprIterated: + return trueCallee(x.Definition) + case *ExprPoly: // XXX: Did we want this one added too? + return trueCallee(x.Definition) + default: return apparentCallee } } +// findExprPoly is a helper used in SetScope. +func findExprPoly(apparentCallee interfaces.Expr) *ExprPoly { + switch x := apparentCallee.(type) { + case *ExprTopLevel: + return findExprPoly(x.Definition) + case *ExprSingleton: + return findExprPoly(x.Definition) + case *ExprIterated: + return findExprPoly(x.Definition) + case *ExprPoly: + return x // found it! + default: + return nil // not found! + } +} + +// newExprParam is a helper function to create an ExprParam with the internal +// key set to the pointer of the thing we're creating. +func newExprParam(name string, typ *types.Type) *ExprParam { + expr := &ExprParam{ + Name: name, + typ: typ, + } + expr.envKey = expr + return expr +} + +// newExprIterated is a helper function to create an ExprIterated with the +// internal key set to the pointer of the thing we're creating. +func newExprIterated(name string, definition interfaces.Expr) *ExprIterated { + expr := &ExprIterated{ + Name: name, + Definition: definition, + } + expr.envKey = expr + return expr +} + // variableScopeFeedback logs some messages about what is actually in scope so // that the user gets a hint about what's going on. This is useful for catching // bugs in our programming or in user code! diff --git a/lang/funcs/dage/dage.go b/lang/funcs/dage/dage.go index c7391fca..262bc1c8 100644 --- a/lang/funcs/dage/dage.go +++ b/lang/funcs/dage/dage.go @@ -1240,6 +1240,8 @@ func (obj *Engine) Run(ctx context.Context) (reterr error) { } fn := func(nodeCtx context.Context) (reterr error) { + // NOTE: Comment out this defer to make + // debugging a lot easier. defer func() { // catch programming errors if r := recover(); r != nil { diff --git a/lang/funcs/structs/for.go b/lang/funcs/structs/for.go new file mode 100644 index 00000000..c3d973a7 --- /dev/null +++ b/lang/funcs/structs/for.go @@ -0,0 +1,254 @@ +// Mgmt +// Copyright (C) James Shubin and the project contributors +// Written by James Shubin and the project contributors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// Additional permission under GNU GPL version 3 section 7 +// +// If you modify this program, or any covered work, by linking or combining it +// with embedded mcl code and modules (and that the embedded mcl code and +// modules which link with this program, contain a copy of their source code in +// the authoritative form) containing parts covered by the terms of any other +// license, the licensors of this program grant you additional permission to +// convey the resulting work. Furthermore, the licensors of this program grant +// the original author, James Shubin, additional permission to update this +// additional permission if he deems it necessary to achieve the goals of this +// additional permission. + +package structs + +import ( + "context" + "fmt" + + "github.com/purpleidea/mgmt/lang/interfaces" + "github.com/purpleidea/mgmt/lang/types" + "github.com/purpleidea/mgmt/util/errwrap" +) + +const ( + // ForFuncName is the unique name identifier for this function. + ForFuncName = "for" + + // ForFuncArgNameList is the name for the edge which connects the input + // list to CallFunc. + ForFuncArgNameList = "list" +) + +// ForFunc receives a list from upstream. We iterate over the received list to +// build a subgraph that processes each element, and in doing so we get a larger +// function graph. This is rebuilt as necessary if the input list changes. +type ForFunc struct { + IndexType *types.Type + ValueType *types.Type + + EdgeName string // name of the edge used + + AppendToIterBody func(innerTxn interfaces.Txn, index int, value interfaces.Func) error + ClearIterBody func(length int) + + init *interfaces.Init + + lastInputListLength int // remember the last input list length +} + +// String returns a simple name for this function. This is needed so this struct +// can satisfy the pgraph.Vertex interface. +func (obj *ForFunc) String() string { + return ForFuncName +} + +// Validate makes sure we've built our struct properly. +func (obj *ForFunc) Validate() error { + if obj.IndexType == nil { + return fmt.Errorf("must specify a type") + } + if obj.ValueType == nil { + return fmt.Errorf("must specify a type") + } + + // TODO: maybe we can remove this if we use this for core functions... + if obj.EdgeName == "" { + return fmt.Errorf("must specify an edge name") + } + + return nil +} + +// Info returns some static info about itself. +func (obj *ForFunc) Info() *interfaces.Info { + var typ *types.Type + + if obj.IndexType != nil && obj.ValueType != nil { // don't panic if called speculatively + // XXX: Improve function engine so it can return no value? + //typ = types.NewType(fmt.Sprintf("func(%s []%s)", obj.EdgeName, obj.ValueType)) // returns nothing + // XXX: Temporary float type to prove we're dropping the output since we don't use it. + typ = types.NewType(fmt.Sprintf("func(%s []%s) float", obj.EdgeName, obj.ValueType)) + } + + return &interfaces.Info{ + Pure: true, + Memo: false, // TODO: ??? + Sig: typ, + Err: obj.Validate(), + } +} + +// Init runs some startup code for this composite function. +func (obj *ForFunc) Init(init *interfaces.Init) error { + obj.init = init + obj.lastInputListLength = -1 + return nil +} + +// Stream takes an input struct in the format as described in the Func and Graph +// methods of the Expr, and returns the actual expected value as a stream based +// on the changing inputs to that value. +func (obj *ForFunc) Stream(ctx context.Context) error { + defer close(obj.init.Output) // the sender closes + + // A Func to send input lists to the subgraph. The Txn.Erase() call + // ensures that this Func is not removed when the subgraph is recreated, + // so that the function graph can propagate the last list we received to + // the subgraph. + inputChan := make(chan types.Value) + subgraphInput := &ChannelBasedSourceFunc{ + Name: "subgraphInput", + Source: obj, + Chan: inputChan, + Type: obj.listType(), + } + obj.init.Txn.AddVertex(subgraphInput) + if err := obj.init.Txn.Commit(); err != nil { + return errwrap.Wrapf(err, "commit error in Stream") + } + obj.init.Txn.Erase() // prevent the next Reverse() from removing subgraphInput + defer func() { + close(inputChan) + obj.init.Txn.Reverse() + obj.init.Txn.DeleteVertex(subgraphInput) + obj.init.Txn.Commit() + }() + + for { + select { + case input, ok := <-obj.init.Input: + if !ok { + obj.init.Input = nil // block looping back here + //canReceiveMoreListValues = false + // We don't ever shutdown here, since even if we + // don't get more lists, that last list value is + // still propagating inside of the subgraph and + // so we don't want to shutdown since that would + // reverse the txn which we only do at the very + // end on graph shutdown. + continue + } + + forList, exists := input.Struct()[obj.EdgeName] + if !exists { + return fmt.Errorf("programming error, can't find edge") + } + + // If the length of the input list has changed, then we + // need to replace the subgraph with a new one that has + // that many "tentacles". Basically the shape of the + // graph depends on the length of the list. If we get a + // brand new list where each value is different, but + // the length is the same, then we can just flow new + // values into the list and we don't need to change the + // graph shape! Changing the graph shape is more + // expensive, so we don't do it when not necessary. + n := len(forList.List()) + + //if forList.Cmp(obj.lastForList) != nil // don't! + if n != obj.lastInputListLength { + //obj.lastForList = forList + obj.lastInputListLength = n + // replaceSubGraph uses the above two values + if err := obj.replaceSubGraph(subgraphInput); err != nil { + return errwrap.Wrapf(err, "could not replace subgraph") + } + } + + // send the new input list to the subgraph + select { + case inputChan <- forList: + case <-ctx.Done(): + return nil + } + + case <-ctx.Done(): + return nil + } + + select { + case obj.init.Output <- &types.FloatValue{ + V: 42.0, // XXX: temporary + }: + case <-ctx.Done(): + return nil + } + } +} + +func (obj *ForFunc) replaceSubGraph(subgraphInput interfaces.Func) error { + // delete the old subgraph + if err := obj.init.Txn.Reverse(); err != nil { + return errwrap.Wrapf(err, "could not Reverse") + } + + obj.ClearIterBody(obj.lastInputListLength) // XXX: pass in size? + + for i := 0; i < obj.lastInputListLength; i++ { + i := i + argName := "forInputList" + + inputElemFunc := SimpleFnToDirectFunc( + fmt.Sprintf("forInputElem[%d]", i), + &types.FuncValue{ + V: func(_ context.Context, args []types.Value) (types.Value, error) { + if len(args) != 1 { + return nil, fmt.Errorf("inputElemFunc: expected a single argument") + } + arg := args[0] + + list, ok := arg.(*types.ListValue) + if !ok { + return nil, fmt.Errorf("inputElemFunc: expected a ListValue argument") + } + + return list.List()[i], nil + }, + T: types.NewType(fmt.Sprintf("func(%s %s) %s", argName, obj.listType(), obj.ValueType)), + }, + ) + obj.init.Txn.AddVertex(inputElemFunc) + + obj.init.Txn.AddEdge(subgraphInput, inputElemFunc, &interfaces.FuncEdge{ + Args: []string{argName}, + }) + + if err := obj.AppendToIterBody(obj.init.Txn, i, inputElemFunc); err != nil { + return errwrap.Wrapf(err, "could not call AppendToIterBody()") + } + } + + return obj.init.Txn.Commit() +} + +func (obj *ForFunc) listType() *types.Type { + return types.NewType(fmt.Sprintf("[]%s", obj.ValueType)) +} diff --git a/lang/funcs/txn/txn.go b/lang/funcs/txn/txn.go index 7f6708b0..3cd0f01e 100644 --- a/lang/funcs/txn/txn.go +++ b/lang/funcs/txn/txn.go @@ -206,8 +206,14 @@ func (obj *opAddEdge) Fn(opapi *opapi) error { args[x] = struct{}{} } if len(args) != len(obj.FE.Args)+len(edge.Args) { - // programming error - return fmt.Errorf("duplicate arg found") + // previously, a programming error + // On 24/nov/2024, Sam and I agreed this should be on. + // On 11/jan/2025, Sam and I decided to disable this + // check, since it was cause duplicates when a nested + // StmtFor was having it's list contents add more than + // once. It may be helpful to turn this on when + // debugging graph transactions not involved StmtFor. + //return fmt.Errorf("duplicate arg found: %v -> %v (%v)", obj.F1, obj.F2, obj.FE.Args) } newArgs := []string{} for x := range args { diff --git a/lang/interfaces/ast.go b/lang/interfaces/ast.go index 636fcb43..99bfb3ab 100644 --- a/lang/interfaces/ast.go +++ b/lang/interfaces/ast.go @@ -85,8 +85,9 @@ type Stmt interface { // child statements, and Infer/Check for child expressions. TypeCheck() ([]*UnificationInvariant, error) - // Graph returns the reactive function graph expressed by this node. - Graph() (*pgraph.Graph, error) + // Graph returns the reactive function graph expressed by this node. It + // takes in the environment of any functions in scope. + Graph(env *Env) (*pgraph.Graph, error) // Output returns the output that this "program" produces. This output // is what is used to build the output graph. It requires the input @@ -148,7 +149,7 @@ type Expr interface { // Graph returns the reactive function graph expressed by this node. It // takes in the environment of any functions in scope. It also returns // the function for this node. - Graph(env map[string]Func) (*pgraph.Graph, Func, error) + Graph(env *Env) (*pgraph.Graph, Func, error) // SetValue stores the result of the last computation of this expression // node. @@ -277,9 +278,21 @@ func (obj *Data) AbsFilename() string { // An interesting note about these is that they exist in a distinct namespace // from the variables, which could actually contain lambda functions. type Scope struct { + // Variables maps the scope of name to Expr. Variables map[string]Expr - 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 + + // Functions is the scope of functions. + // + // The Expr will usually be an *ExprFunc. (Actually it's usually or + // always an *ExprSingleton, which wraps an *ExprFunc now.) + Functions map[string]Expr + + // Classes map the name of the class to the class. + Classes map[string]Stmt + + // Iterated is a flag that is true if this scope is inside of a for + // loop. + Iterated bool Chain []Node // chain of previously seen node's } @@ -291,6 +304,7 @@ func EmptyScope() *Scope { Variables: make(map[string]Expr), Functions: make(map[string]Expr), Classes: make(map[string]Stmt), + Iterated: false, Chain: []Node{}, } } @@ -307,6 +321,7 @@ func (obj *Scope) Copy() *Scope { variables := make(map[string]Expr) functions := make(map[string]Expr) classes := make(map[string]Stmt) + iterated := obj.Iterated chain := []Node{} for k, v := range obj.Variables { // copy @@ -326,6 +341,7 @@ func (obj *Scope) Copy() *Scope { Variables: variables, Functions: functions, Classes: classes, + Iterated: iterated, Chain: chain, } } @@ -377,6 +393,10 @@ func (obj *Scope) Merge(scope *Scope) error { obj.Classes[name] = scope.Classes[name] } + if scope.Iterated { // XXX: how should we merge this? + obj.Iterated = scope.Iterated + } + return err } @@ -398,6 +418,94 @@ func (obj *Scope) IsEmpty() bool { return true } +// Env is an environment which contains the relevant mappings. This is used at +// the Graph(...) stage of the compiler. It does not contain classes. +type Env struct { + // Variables map and Expr to a *FuncSingleton which deduplicates the + // use of a function. + Variables map[Expr]*FuncSingleton + + // Functions contains the captured environment, because when we're + // recursing into a StmtFunc which is defined inside a for loop, we can + // use that to get the right Env.Variables map. As for the function + // itself, it's the same in each loop iteration, therefore, we find it + // in obj.expr of ExprCall. (Functions map[string]*Env) But actually, + // our new version is now this: + Functions map[Expr]*Env +} + +// EmptyEnv returns the zero, empty value for the scope, with all the internal +// lists initialized appropriately. +func EmptyEnv() *Env { + return &Env{ + Variables: make(map[Expr]*FuncSingleton), + Functions: make(map[Expr]*Env), + } +} + +// Copy makes a copy of the Env struct. This ensures that if the internal maps +// are changed, it doesn't affect other copies of the Env. It does *not* copy or +// change the pointers contained within, since these are references, and we need +// those to be consistently pointing to the same things after copying. +func (obj *Env) Copy() *Env { + if obj == nil { // allow copying nil envs + return EmptyEnv() + } + + variables := make(map[Expr]*FuncSingleton) + functions := make(map[Expr]*Env) + + for k, v := range obj.Variables { // copy + variables[k] = v // we don't copy the func's! + } + for k, v := range obj.Functions { // copy + functions[k] = v // we don't copy the generator func's + } + + return &Env{ + Variables: variables, + Functions: functions, + } +} + +// FuncSingleton is a singleton system for storing a singleton func and its +// corresponding graph. You must pass in a `MakeFunc` builder method to generate +// these. The graph which is returned from this must contain that Func as a +// node. +type FuncSingleton struct { + // MakeFunc builds and returns a Func and a graph that it must be + // contained within. + // XXX: Add Txn as an input arg? + MakeFunc func() (*pgraph.Graph, Func, error) + + g *pgraph.Graph + f Func +} + +// GraphFunc returns the previously saved graph and func if they exist. If they +// do not, then it calls the MakeFunc method to get them, and saves a copy for +// next time. +// XXX: Add Txn as an input arg? +func (obj *FuncSingleton) GraphFunc() (*pgraph.Graph, Func, error) { + // If obj.f already exists, just use that. + if obj.f != nil { // && obj.g != nil + return obj.g, obj.f, nil + } + + var err error + obj.g, obj.f, err = obj.MakeFunc() // XXX: Add Txn as an input arg? + if err != nil { + return nil, nil, err + } + if obj.g == nil { + return nil, nil, fmt.Errorf("unexpected nil graph") + } + if obj.f == nil { + return nil, nil, fmt.Errorf("unexpected nil function") + } + return obj.g, obj.f, nil +} + // Arg represents a name identifier for a func or class argument declaration and // is sometimes accompanied by a type. This does not satisfy the Expr interface. type Arg struct { diff --git a/lang/interpret_test.go b/lang/interpret_test.go index 965e8478..2379d66f 100644 --- a/lang/interpret_test.go +++ b/lang/interpret_test.go @@ -507,7 +507,7 @@ func TestAstFunc1(t *testing.T) { } // build the function graph - fgraph, err := iast.Graph() + fgraph, err := iast.Graph(interfaces.EmptyEnv()) // XXX: Ask Sam if (!fail || !failGraph) && err != nil { t.Errorf("test #%d: FAIL", index) t.Errorf("test #%d: functions failed with: %+v", index, err) @@ -1101,8 +1101,29 @@ func TestAstFunc2(t *testing.T) { // in implementation and before unification, and static // once we've unified the specific resource. + // build the env + //fgraph := &pgraph.Graph{Name: "functionGraph"} + env := interfaces.EmptyEnv() + // XXX: Do we need to do something like this? + //for k, v := range scope.Variables { + // g, builtinFunc, err := v.Graph(nil) + // if err != nil { + // t.Errorf("test #%d: FAIL", index) + // t.Errorf("test #%d: calling Graph on builtins errored: %+v", index, err) + // return + // } + // fgraph.AddGraph(g) + // env.Variables[k] = builtinFunc // XXX: Ask Sam (.Functions ???) + //} + //for k, closure := range scope.Functions { + // env.Functions[k] = &interfaces.Closure{ + // Env: interfaces.EmptyEnv(), + // Expr: closure.Expr, // XXX: Ask Sam + // } + //} + // build the function graph - fgraph, err := iast.Graph() + fgraph, err := iast.Graph(env) // XXX: Ask Sam if (!fail || !failGraph) && err != nil { t.Errorf("test #%d: FAIL", index) t.Errorf("test #%d: functions failed with: %+v", index, err) @@ -1931,7 +1952,7 @@ func TestAstFunc3(t *testing.T) { // once we've unified the specific resource. // build the function graph - fgraph, err := iast.Graph() + fgraph, err := iast.Graph(interfaces.EmptyEnv()) // XXX: Ask Sam if (!fail || !failGraph) && err != nil { t.Errorf("test #%d: FAIL", index) t.Errorf("test #%d: functions failed with: %+v", index, err) diff --git a/lang/interpret_test/TestAstFunc2/polymorphic-lambda.txtar b/lang/interpret_test/TestAstFunc2/polymorphic-lambda.txtar index 0ba5cb0a..29cefe89 100644 --- a/lang/interpret_test/TestAstFunc2/polymorphic-lambda.txtar +++ b/lang/interpret_test/TestAstFunc2/polymorphic-lambda.txtar @@ -17,4 +17,4 @@ $out2 = $add($val) # hellohello test [fmt.printf("%s + %s is %s", $val, $val, $out2),] {} # simple concat -- OUTPUT -- -# err: errUnify: unify error with: topLevel(singleton(func(x) { call:_operator(str("+"), var(x), var(x)) })): type error: int != str +# err: errUnify: unify error with: topLevel(singleton(func(x) { call:_operator(str("+"), var(x), var(x)) })): type error: str != int diff --git a/lang/interpret_test/TestAstFunc2/stmtfor00.txtar b/lang/interpret_test/TestAstFunc2/stmtfor00.txtar new file mode 100644 index 00000000..2e6dfd9e --- /dev/null +++ b/lang/interpret_test/TestAstFunc2/stmtfor00.txtar @@ -0,0 +1,13 @@ +-- main.mcl -- +import "fmt" + +$list = ["a", "b", "c",] + +for $index, $value in $list { + test [$value,] {} +} + +-- OUTPUT -- +Vertex: test[a] +Vertex: test[b] +Vertex: test[c] diff --git a/lang/interpret_test/TestAstFunc2/stmtfor01.txtar b/lang/interpret_test/TestAstFunc2/stmtfor01.txtar new file mode 100644 index 00000000..e24de80e --- /dev/null +++ b/lang/interpret_test/TestAstFunc2/stmtfor01.txtar @@ -0,0 +1,15 @@ +-- main.mcl -- +import "fmt" + +$list = ["a", "b", "c",] + +for $index, $value in $list { + $s = $value # our first major bug was triggered by this! + test [$s,] {} +} + +# The buggy version would return "test[a]" three times! +-- OUTPUT -- +Vertex: test[a] +Vertex: test[b] +Vertex: test[c] diff --git a/lang/interpret_test/TestAstFunc2/stmtfor02.txtar b/lang/interpret_test/TestAstFunc2/stmtfor02.txtar new file mode 100644 index 00000000..b7a926bb --- /dev/null +++ b/lang/interpret_test/TestAstFunc2/stmtfor02.txtar @@ -0,0 +1,13 @@ +-- main.mcl -- +import "fmt" + +$list = ["a", "b", "c",] + +for $index, $value in $list { + test [fmt.printf("%s is %d", $value, $index),] {} +} + +-- OUTPUT -- +Vertex: test[a is 0] +Vertex: test[b is 1] +Vertex: test[c is 2] diff --git a/lang/interpret_test/TestAstFunc2/stmtfor03.txtar b/lang/interpret_test/TestAstFunc2/stmtfor03.txtar new file mode 100644 index 00000000..2442f81a --- /dev/null +++ b/lang/interpret_test/TestAstFunc2/stmtfor03.txtar @@ -0,0 +1,14 @@ +-- main.mcl -- +import "fmt" + +$list = ["a", "b", "c",] + +for $index, $value in $list { + $s = fmt.printf("%s is %d", $value, $index) + test [$s,] {} +} + +-- OUTPUT -- +Vertex: test[a is 0] +Vertex: test[b is 1] +Vertex: test[c is 2] diff --git a/lang/interpret_test/TestAstFunc2/stmtfor04.txtar b/lang/interpret_test/TestAstFunc2/stmtfor04.txtar new file mode 100644 index 00000000..03ddeb60 --- /dev/null +++ b/lang/interpret_test/TestAstFunc2/stmtfor04.txtar @@ -0,0 +1,16 @@ +-- main.mcl -- +import "fmt" + +$list = ["a", "b", "c",] + +$s = "nope" # should be out of scope + +for $index, $value in $list { + $s = fmt.printf("%s is %d", $value, $index) + test [$s,] {} +} + +-- OUTPUT -- +Vertex: test[a is 0] +Vertex: test[b is 1] +Vertex: test[c is 2] diff --git a/lang/interpret_test/TestAstFunc2/stmtfor05.txtar b/lang/interpret_test/TestAstFunc2/stmtfor05.txtar new file mode 100644 index 00000000..ba77c279 --- /dev/null +++ b/lang/interpret_test/TestAstFunc2/stmtfor05.txtar @@ -0,0 +1,16 @@ +-- main.mcl -- +import "fmt" + +$list = ["a", "b", "c",] + +$value = "nope" # should be out of scope + +for $index, $value in $list { + $s = fmt.printf("%s is %d", $value, $index) + test [$s,] {} +} + +-- OUTPUT -- +Vertex: test[a is 0] +Vertex: test[b is 1] +Vertex: test[c is 2] diff --git a/lang/interpret_test/TestAstFunc2/stmtfor06.txtar b/lang/interpret_test/TestAstFunc2/stmtfor06.txtar new file mode 100644 index 00000000..b41418df --- /dev/null +++ b/lang/interpret_test/TestAstFunc2/stmtfor06.txtar @@ -0,0 +1,16 @@ +-- main.mcl -- +import "fmt" + +$list = ["a", "b", "c",] + +$value = 42 # should be out of scope (also not the same type) + +for $index, $value in $list { + $s = fmt.printf("%s is %d", $value, $index) + test [$s,] {} +} + +-- OUTPUT -- +Vertex: test[a is 0] +Vertex: test[b is 1] +Vertex: test[c is 2] diff --git a/lang/interpret_test/TestAstFunc2/stmtfor07.txtar b/lang/interpret_test/TestAstFunc2/stmtfor07.txtar new file mode 100644 index 00000000..ac26c8d9 --- /dev/null +++ b/lang/interpret_test/TestAstFunc2/stmtfor07.txtar @@ -0,0 +1,17 @@ +-- main.mcl -- +import "fmt" + +$list = ["a", "b", "c",] + +$index = 42 # should be out of scope + +for $index, $value in $list { + $index = 42 + $s = fmt.printf("%s is %d", $value, $index) + test [$s,] {} +} + +-- OUTPUT -- +Vertex: test[a is 42] +Vertex: test[b is 42] +Vertex: test[c is 42] diff --git a/lang/interpret_test/TestAstFunc2/stmtfor08.txtar b/lang/interpret_test/TestAstFunc2/stmtfor08.txtar new file mode 100644 index 00000000..9b638bd0 --- /dev/null +++ b/lang/interpret_test/TestAstFunc2/stmtfor08.txtar @@ -0,0 +1,20 @@ +-- main.mcl -- +import "fmt" +import "math" + +$list = ["a", "b", "c",] + +$index = 42 # should be out of scope + +for $index, $value in $list { + $index = if math.mod($index, 2) == 0 { + $index + } else { + 42 + } + $s = fmt.printf("%s is %d", $value, $index) + test [$s,] {} +} + +-- OUTPUT -- +# err: errSetScope: recursive reference while setting scope: not a dag diff --git a/lang/interpret_test/TestAstFunc2/stmtfor09.txtar b/lang/interpret_test/TestAstFunc2/stmtfor09.txtar new file mode 100644 index 00000000..08817d29 --- /dev/null +++ b/lang/interpret_test/TestAstFunc2/stmtfor09.txtar @@ -0,0 +1,22 @@ +-- main.mcl -- +import "fmt" +import "math" + +$list = ["a", "b", "c",] + +$index = 42 # should be out of scope + +for $index, $value in $list { + $newindex = if math.mod($index, 2) == 0 { + $index + } else { + 42 + } + $s = fmt.printf("%s is %d", $value, $newindex) + test [$s,] {} +} + +-- OUTPUT -- +Vertex: test[a is 0] +Vertex: test[b is 42] +Vertex: test[c is 2] diff --git a/lang/interpret_test/TestAstFunc2/stmtfor10.txtar b/lang/interpret_test/TestAstFunc2/stmtfor10.txtar new file mode 100644 index 00000000..f251694f --- /dev/null +++ b/lang/interpret_test/TestAstFunc2/stmtfor10.txtar @@ -0,0 +1,21 @@ +-- main.mcl -- +import "fmt" + +$list = ["a", "b", "c",] + +$index = 42 # should be out of scope + +for $index, $value in $list { + + $fn = func($x) { + "hello " + $x + } + + $s = fmt.printf("%s is %d", $fn($value), $index) + test [$s,] {} +} + +-- OUTPUT -- +Vertex: test[hello a is 0] +Vertex: test[hello b is 1] +Vertex: test[hello c is 2] diff --git a/lang/interpret_test/TestAstFunc2/stmtfor11.txtar b/lang/interpret_test/TestAstFunc2/stmtfor11.txtar new file mode 100644 index 00000000..b49806dd --- /dev/null +++ b/lang/interpret_test/TestAstFunc2/stmtfor11.txtar @@ -0,0 +1,21 @@ +-- main.mcl -- +import "fmt" + +$list = ["a", "b", "c",] + +$index = 42 # should be out of scope + +for $index, $value in $list { + + func fn($x) { + "hello " + $x + } + + $s = fmt.printf("%s is %d", fn($value), $index) + test [$s,] {} +} + +-- OUTPUT -- +Vertex: test[hello a is 0] +Vertex: test[hello b is 1] +Vertex: test[hello c is 2] diff --git a/lang/interpret_test/TestAstFunc2/stmtfor12.txtar b/lang/interpret_test/TestAstFunc2/stmtfor12.txtar new file mode 100644 index 00000000..c8ebe7b7 --- /dev/null +++ b/lang/interpret_test/TestAstFunc2/stmtfor12.txtar @@ -0,0 +1,21 @@ +-- main.mcl -- +import "fmt" + +$list = ["a", "b", "c",] + +$index = 42 # should be out of scope + +for $index, $value in $list { + + $fn = func($x) { + "hello " + $value + } + + $s = fmt.printf("%s is %d", $fn("whatever"), $index) + test [$s,] {} +} + +-- OUTPUT -- +Vertex: test[hello a is 0] +Vertex: test[hello b is 1] +Vertex: test[hello c is 2] diff --git a/lang/interpret_test/TestAstFunc2/stmtfor13.txtar b/lang/interpret_test/TestAstFunc2/stmtfor13.txtar new file mode 100644 index 00000000..21fb3db6 --- /dev/null +++ b/lang/interpret_test/TestAstFunc2/stmtfor13.txtar @@ -0,0 +1,21 @@ +-- main.mcl -- +import "fmt" + +$list = ["a", "b", "c",] + +$index = 42 # should be out of scope + +for $index, $value in $list { + + func fn($x) { + "hello " + $value + } + + $s = fmt.printf("%s is %d", fn("whatever"), $index) + test [$s,] {} +} + +-- OUTPUT -- +Vertex: test[hello a is 0] +Vertex: test[hello b is 1] +Vertex: test[hello c is 2] diff --git a/lang/interpret_test/TestAstFunc2/stmtfor14.txtar b/lang/interpret_test/TestAstFunc2/stmtfor14.txtar new file mode 100644 index 00000000..5467fa29 --- /dev/null +++ b/lang/interpret_test/TestAstFunc2/stmtfor14.txtar @@ -0,0 +1,23 @@ +-- main.mcl -- +import "fmt" + +$list = ["a", "b", "c",] + +$index = 42 # should be out of scope + +for $index, $value in $list { + + $fn = func($x) { + "hello" + $val + } + + $val = " " + $value + + $s = fmt.printf("%s is %d", $fn("whatever"), $index) + test [$s,] {} +} + +-- OUTPUT -- +Vertex: test[hello a is 0] +Vertex: test[hello b is 1] +Vertex: test[hello c is 2] diff --git a/lang/interpret_test/TestAstFunc2/stmtfor15.txtar b/lang/interpret_test/TestAstFunc2/stmtfor15.txtar new file mode 100644 index 00000000..01992648 --- /dev/null +++ b/lang/interpret_test/TestAstFunc2/stmtfor15.txtar @@ -0,0 +1,23 @@ +-- main.mcl -- +import "fmt" + +$list = ["a", "b", "c",] + +$index = 42 # should be out of scope + +for $index, $value in $list { + + func fn($x) { + "hello" + $val + } + + $val = " " + $value + + $s = fmt.printf("%s is %d", fn("whatever"), $index) + test [$s,] {} +} + +-- OUTPUT -- +Vertex: test[hello a is 0] +Vertex: test[hello b is 1] +Vertex: test[hello c is 2] diff --git a/lang/interpret_test/TestAstFunc2/stmtfor16.txtar b/lang/interpret_test/TestAstFunc2/stmtfor16.txtar new file mode 100644 index 00000000..28e603f2 --- /dev/null +++ b/lang/interpret_test/TestAstFunc2/stmtfor16.txtar @@ -0,0 +1,22 @@ +-- main.mcl -- +import "fmt" + +$list = ["a", "b", "c",] + +$index = 42 # should be out of scope + +for $index, $value in $list { + + class foo($x) { + $result = "hello " + $x + } + include foo($value) as included + + $s = fmt.printf("%s is %d", $included.result, $index) + test [$s,] {} +} + +-- OUTPUT -- +Vertex: test[hello a is 0] +Vertex: test[hello b is 1] +Vertex: test[hello c is 2] diff --git a/lang/interpret_test/TestAstFunc2/stmtfor17.txtar b/lang/interpret_test/TestAstFunc2/stmtfor17.txtar new file mode 100644 index 00000000..e3b4ba6f --- /dev/null +++ b/lang/interpret_test/TestAstFunc2/stmtfor17.txtar @@ -0,0 +1,22 @@ +-- main.mcl -- +import "fmt" + +$list = ["a", "b", "c",] + +$word = "hello" + +for $index, $value in $list { + + class foo($x) { + $result = $word + " " + $x + } + include foo($value) as included + + $s = fmt.printf("%s is %d", $included.result, $index) + test [$s,] {} +} + +-- OUTPUT -- +Vertex: test[hello a is 0] +Vertex: test[hello b is 1] +Vertex: test[hello c is 2] diff --git a/lang/interpret_test/TestAstFunc2/stmtfor18.txtar b/lang/interpret_test/TestAstFunc2/stmtfor18.txtar new file mode 100644 index 00000000..127c898b --- /dev/null +++ b/lang/interpret_test/TestAstFunc2/stmtfor18.txtar @@ -0,0 +1,22 @@ +-- main.mcl -- +import "fmt" + +$list = ["a", "b", "c",] + +$index = 42 # should be out of scope + +for $index, $value in $list { + + class foo($x) { + $result = "hello " + $value + } + include foo("whatever") as included + + $s = fmt.printf("%s is %d", $included.result, $index) + test [$s,] {} +} + +-- OUTPUT -- +Vertex: test[hello a is 0] +Vertex: test[hello b is 1] +Vertex: test[hello c is 2] diff --git a/lang/interpret_test/TestAstFunc2/stmtfor19.txtar b/lang/interpret_test/TestAstFunc2/stmtfor19.txtar new file mode 100644 index 00000000..461bcf2e --- /dev/null +++ b/lang/interpret_test/TestAstFunc2/stmtfor19.txtar @@ -0,0 +1,22 @@ +-- main.mcl -- +import "fmt" + +$list = ["a", "b", "c",] + +$word = "hello" + +for $index, $value in $list { + + class foo($x) { + $result = $word + " " + $value + } + include foo("whatever") as included + + $s = fmt.printf("%s is %d", $included.result, $index) + test [$s,] {} +} + +-- OUTPUT -- +Vertex: test[hello a is 0] +Vertex: test[hello b is 1] +Vertex: test[hello c is 2] diff --git a/lang/interpret_test/TestAstFunc2/stmtfor20.txtar b/lang/interpret_test/TestAstFunc2/stmtfor20.txtar new file mode 100644 index 00000000..634f2fae --- /dev/null +++ b/lang/interpret_test/TestAstFunc2/stmtfor20.txtar @@ -0,0 +1,24 @@ +-- main.mcl -- +import "fmt" + +$list = ["a", "b", "c",] + +$index = 42 # should be out of scope + +for $index, $value in $list { + + class foo($x) { + $result = "hello" + $val + } + include foo("whatever") as included + + $val = " " + $value + + $s = fmt.printf("%s is %d", $included.result, $index) + test [$s,] {} +} + +-- OUTPUT -- +Vertex: test[hello a is 0] +Vertex: test[hello b is 1] +Vertex: test[hello c is 2] diff --git a/lang/interpret_test/TestAstFunc2/stmtfor21.txtar b/lang/interpret_test/TestAstFunc2/stmtfor21.txtar new file mode 100644 index 00000000..abc0c136 --- /dev/null +++ b/lang/interpret_test/TestAstFunc2/stmtfor21.txtar @@ -0,0 +1,24 @@ +-- main.mcl -- +import "fmt" + +$list = ["a", "b", "c",] + +$word = "hello" + +for $index, $value in $list { + + class foo($x) { + $result = $word + $val + } + include foo("whatever") as included + + $val = " " + $value + + $s = fmt.printf("%s is %d", $included.result, $index) + test [$s,] {} +} + +-- OUTPUT -- +Vertex: test[hello a is 0] +Vertex: test[hello b is 1] +Vertex: test[hello c is 2] diff --git a/lang/interpret_test/TestAstFunc2/stmtfor22.txtar b/lang/interpret_test/TestAstFunc2/stmtfor22.txtar new file mode 100644 index 00000000..3e222a0b --- /dev/null +++ b/lang/interpret_test/TestAstFunc2/stmtfor22.txtar @@ -0,0 +1,24 @@ +-- main.mcl -- +import "fmt" + +$list1 = ["a", "b", "c",] +$list2 = [42, 13, -4,] + +for $index1, $value1 in $list1 { + for $index2, $value2 in $list2 { + + $s = fmt.printf("%s is %d", $value1, $value2) + test [$s,] {} + } +} + +-- OUTPUT -- +Vertex: test[a is 42] +Vertex: test[b is 42] +Vertex: test[c is 42] +Vertex: test[a is 13] +Vertex: test[b is 13] +Vertex: test[c is 13] +Vertex: test[a is -4] +Vertex: test[b is -4] +Vertex: test[c is -4] diff --git a/lang/interpret_test/TestAstFunc2/stmtfor23.txtar b/lang/interpret_test/TestAstFunc2/stmtfor23.txtar new file mode 100644 index 00000000..10b286aa --- /dev/null +++ b/lang/interpret_test/TestAstFunc2/stmtfor23.txtar @@ -0,0 +1,31 @@ +-- main.mcl -- +import "fmt" + +$list0 = ["a", "b", "c",] +$list1 = ["d", "e", "f",] +$list2 = ["g", "h", "i",] +$list3 = ["j", "k", "l",] + +$list = [$list0, $list1, $list2, $list3,] + +for $index, $value in $list { + for $i, $v in $value { + + $s = fmt.printf("%s is %d", $v, $i+$index) + test [$s,] {} + } +} + +-- OUTPUT -- +Vertex: test[a is 0] +Vertex: test[b is 1] +Vertex: test[c is 2] +Vertex: test[d is 1] +Vertex: test[e is 2] +Vertex: test[f is 3] +Vertex: test[g is 2] +Vertex: test[h is 3] +Vertex: test[i is 4] +Vertex: test[j is 3] +Vertex: test[k is 4] +Vertex: test[l is 5] diff --git a/lang/interpret_test/TestAstFunc2/stmtfor24.txtar b/lang/interpret_test/TestAstFunc2/stmtfor24.txtar new file mode 100644 index 00000000..754d892c --- /dev/null +++ b/lang/interpret_test/TestAstFunc2/stmtfor24.txtar @@ -0,0 +1,17 @@ +-- main.mcl -- +import "fmt" + +$list = ["a",] + +for $index, $value in $list { + + $fn = func() { + "hello " + $value + } + + $s = $fn() + test [$s,] {} +} + +-- OUTPUT -- +Vertex: test[hello a] diff --git a/lang/interpret_test/TestAstFunc2/stmtfor25.txtar b/lang/interpret_test/TestAstFunc2/stmtfor25.txtar new file mode 100644 index 00000000..8f808d86 --- /dev/null +++ b/lang/interpret_test/TestAstFunc2/stmtfor25.txtar @@ -0,0 +1,17 @@ +-- main.mcl -- +import "fmt" + +$list = ["a",] + +for $index, $value in $list { + + func fn() { + "hello " + $value + } + + $s = fn() + test [$s,] {} +} + +-- OUTPUT -- +Vertex: test[hello a] diff --git a/lang/interpret_test/TestAstFunc2/stmtfor26.txtar b/lang/interpret_test/TestAstFunc2/stmtfor26.txtar new file mode 100644 index 00000000..07960436 --- /dev/null +++ b/lang/interpret_test/TestAstFunc2/stmtfor26.txtar @@ -0,0 +1,41 @@ +-- main.mcl -- +import "fmt" + +$list1 = ["a", "b", "c",] +$list2 = ["x", "y", "z",] + +$word = "hello" + +for $index1, $value1 in $list1 { + for $index2, $value2 in $list2 { + + class foo($x, $y) { + $result = "hello " + $x + $y + $value1 + $value2 + $result1 = $x + $value1 + $result2 = $y + $value2 + } + include foo($value1, $value2) as included + + $s = fmt.printf("%s is {%d,%d}", $included.result, $index1, $index2) + $s1 = fmt.printf("one: %s", $included.result1) + $s2 = fmt.printf("two: %s", $included.result2) + test [$s, $s1, $s2,] {} + } +} + +-- OUTPUT -- +Vertex: test[hello axax is {0,0}] +Vertex: test[hello ayay is {0,1}] +Vertex: test[hello azaz is {0,2}] +Vertex: test[hello bxbx is {1,0}] +Vertex: test[hello byby is {1,1}] +Vertex: test[hello bzbz is {1,2}] +Vertex: test[hello cxcx is {2,0}] +Vertex: test[hello cycy is {2,1}] +Vertex: test[hello czcz is {2,2}] +Vertex: test[one: aa] +Vertex: test[one: bb] +Vertex: test[one: cc] +Vertex: test[two: xx] +Vertex: test[two: yy] +Vertex: test[two: zz] diff --git a/lang/interpret_test/TestAstFunc2/stmtfor27.txtar b/lang/interpret_test/TestAstFunc2/stmtfor27.txtar new file mode 100644 index 00000000..6b545820 --- /dev/null +++ b/lang/interpret_test/TestAstFunc2/stmtfor27.txtar @@ -0,0 +1,38 @@ +-- main.mcl -- +import "fmt" + +$list1 = ["a", "b", "c",] +$list2 = ["x", "y", "z",] + +$word = "hello" + +for $index1, $value1 in $list1 { + + class foo($x, $y) { + $result = "hello " + $x + $y + $value1 + $result1 = $x + $value1 + } + + for $index2, $value2 in $list2 { + + include foo($value1, $value2) as included + + $s = fmt.printf("%s is {%d,%d}", $included.result, $index1, $index2) + $s1 = fmt.printf("one: %s", $included.result1) + test [$s, $s1,] {} + } +} + +-- OUTPUT -- +Vertex: test[hello axa is {0,0}] +Vertex: test[hello aya is {0,1}] +Vertex: test[hello aza is {0,2}] +Vertex: test[hello bxb is {1,0}] +Vertex: test[hello byb is {1,1}] +Vertex: test[hello bzb is {1,2}] +Vertex: test[hello cxc is {2,0}] +Vertex: test[hello cyc is {2,1}] +Vertex: test[hello czc is {2,2}] +Vertex: test[one: aa] +Vertex: test[one: bb] +Vertex: test[one: cc] diff --git a/lang/interpret_test/TestAstFunc2/stmtfor28.txtar b/lang/interpret_test/TestAstFunc2/stmtfor28.txtar new file mode 100644 index 00000000..9c17a603 --- /dev/null +++ b/lang/interpret_test/TestAstFunc2/stmtfor28.txtar @@ -0,0 +1,19 @@ +-- main.mcl -- +import "fmt" + +$list = ["a", "b", "c",] + +for $i, $x in $list { + + func foo($y) { + "hello" + $x + $y + } + + $s = foo($x) + + test [$s,] {} +} +-- OUTPUT -- +Vertex: test[helloaa] +Vertex: test[hellobb] +Vertex: test[hellocc] diff --git a/lang/interpret_test/TestAstFunc2/stmtfor29.txtar b/lang/interpret_test/TestAstFunc2/stmtfor29.txtar new file mode 100644 index 00000000..d884e46e --- /dev/null +++ b/lang/interpret_test/TestAstFunc2/stmtfor29.txtar @@ -0,0 +1,22 @@ +-- main.mcl -- +import "fmt" + +$list = ["a", "b", "c",] + +$index = 42 # should be out of scope + +for $index, $value in $list { + + class foo() { + test [$value + fmt.printf("%d", $index),] {} + } + include foo() # as included + + #$s = fmt.printf("%s is %d", $included.result, $index) + #test [$s,] {} +} + +-- OUTPUT -- +Vertex: test[a0] +Vertex: test[b1] +Vertex: test[c2] diff --git a/lang/interpret_test/TestAstFunc2/stmtfor30.txtar b/lang/interpret_test/TestAstFunc2/stmtfor30.txtar new file mode 100644 index 00000000..203e93fb --- /dev/null +++ b/lang/interpret_test/TestAstFunc2/stmtfor30.txtar @@ -0,0 +1,23 @@ +-- main.mcl -- +import "fmt" + +$list = ["a", "b", "c",] + +$index = 42 # should be out of scope + +for $index, $value in $list { + + class foo($x) { + $result = "hello " + $x + test [$result,] {} + } + include foo($value) # as included + + #$s = fmt.printf("%s is %d", $included.result, $index) + #test [$s,] {} +} + +-- OUTPUT -- +Vertex: test[hello a] +Vertex: test[hello b] +Vertex: test[hello c] diff --git a/lang/interpret_test/TestAstFunc2/stmtfor31.txtar b/lang/interpret_test/TestAstFunc2/stmtfor31.txtar new file mode 100644 index 00000000..9fd18423 --- /dev/null +++ b/lang/interpret_test/TestAstFunc2/stmtfor31.txtar @@ -0,0 +1,24 @@ +-- main.mcl -- +import "fmt" + +$list = ["a", "b", "c",] + +$index = 42 # should be out of scope + +for $index, $value in $list { + + class foo($x) { + func result($s) { + $s + $x + $value + } + } + include foo($value) as included + + $s = fmt.printf("%s is %d", included.result($value), $index) + test [$s,] {} +} + +-- OUTPUT -- +Vertex: test[aaa is 0] +Vertex: test[bbb is 1] +Vertex: test[ccc is 2] diff --git a/lang/interpret_test/TestAstFunc2/stmtfor32.txtar b/lang/interpret_test/TestAstFunc2/stmtfor32.txtar new file mode 100644 index 00000000..66043ee7 --- /dev/null +++ b/lang/interpret_test/TestAstFunc2/stmtfor32.txtar @@ -0,0 +1,24 @@ +-- main.mcl -- +import "fmt" + +$list = ["a", "b", "c",] + +$index = 42 # should be out of scope + +for $index, $value in $list { + + class foo($x) { + $result = "hello" + $x + $value + } + include foo($value) as thing + $result = "please" + + # XXX: add $thing.some_func and so on... add more tests says sam. + $s = fmt.printf("%s is %d is %s", $thing.result, $index, $result) + test [$s,] {} +} + +-- OUTPUT -- +Vertex: test[helloaa is 0 is please] +Vertex: test[hellobb is 1 is please] +Vertex: test[hellocc is 2 is please] diff --git a/lang/interpret_test/TestAstFunc2/stmtfor33.txtar b/lang/interpret_test/TestAstFunc2/stmtfor33.txtar new file mode 100644 index 00000000..6fcf0b60 --- /dev/null +++ b/lang/interpret_test/TestAstFunc2/stmtfor33.txtar @@ -0,0 +1,24 @@ +-- main.mcl -- +import "fmt" + +$list = ["a", "b", "c",] + +$index = 42 # should be out of scope + +for $index, $value in $list { + + class foo($x) { + $result = func($s) { + $s + $x + $value + } + } + include foo($value) as included + + $s = fmt.printf("%s is %d", $included.result($value), $index) + test [$s,] {} +} + +-- OUTPUT -- +Vertex: test[aaa is 0] +Vertex: test[bbb is 1] +Vertex: test[ccc is 2] diff --git a/lang/interpret_test/TestAstFunc2/stmtfor34.txtar b/lang/interpret_test/TestAstFunc2/stmtfor34.txtar new file mode 100644 index 00000000..a6b87183 --- /dev/null +++ b/lang/interpret_test/TestAstFunc2/stmtfor34.txtar @@ -0,0 +1,23 @@ +-- main.mcl -- +import "fmt" + +$list = ["a", "b", "c",] + +$index = 42 # should be out of scope + +for $index, $value in $list { + + class foo() { + $result = "hello " + $value + fmt.printf("%d", $index) + test [$result,] {} + } + include foo() # as included + + #$s = fmt.printf("%s is %d", $included.result, $index) + #test [$s,] {} +} + +-- OUTPUT -- +Vertex: test[hello a0] +Vertex: test[hello b1] +Vertex: test[hello c2] diff --git a/lang/interpret_test/TestAstFunc2/stmtfor35.txtar b/lang/interpret_test/TestAstFunc2/stmtfor35.txtar new file mode 100644 index 00000000..a174da90 --- /dev/null +++ b/lang/interpret_test/TestAstFunc2/stmtfor35.txtar @@ -0,0 +1,32 @@ +-- main.mcl -- +import "fmt" + +$list1 = ["a", "b", "c",] +$list2 = ["x", "y", "z",] + +$word = "hello" + +class foo($x, $y) { + $result = "hello " + $x + $y +} + +for $index1, $value1 in $list1 { + for $index2, $value2 in $list2 { + + include foo($value1, $value2) as included + + $s = fmt.printf("%s is {%d,%d}", $included.result, $index1, $index2) + test [$s,] {} + } +} + +-- OUTPUT -- +Vertex: test[hello ax is {0,0}] +Vertex: test[hello ay is {0,1}] +Vertex: test[hello az is {0,2}] +Vertex: test[hello bx is {1,0}] +Vertex: test[hello by is {1,1}] +Vertex: test[hello bz is {1,2}] +Vertex: test[hello cx is {2,0}] +Vertex: test[hello cy is {2,1}] +Vertex: test[hello cz is {2,2}] diff --git a/lang/interpret_test/TestAstFunc2/stmtfor36.txtar b/lang/interpret_test/TestAstFunc2/stmtfor36.txtar new file mode 100644 index 00000000..f8770960 --- /dev/null +++ b/lang/interpret_test/TestAstFunc2/stmtfor36.txtar @@ -0,0 +1,29 @@ +-- main.mcl -- +import "fmt" + +$list = ["a", "b", "c",] + +#$index = 42 # should be out of scope + +for $index, $value in $list { + + class foo($x) { + #$result = "hello" + $x + $value # harder + #$result = $value # works + #$result = $x # works + $resultx = "hello" + $x # harder + #$result = "hello" + $value # harder + #$result = $x + $value # harder + } + include foo($value)# as included + $result = "please" + + # XXX: add $included.some_func and so on... add more tests says sam. + $s = fmt.printf("%s is %d is %s", $value, $index, $result) + test [$s,] {} +} + +-- OUTPUT -- +Vertex: test[a is 0 is please] +Vertex: test[b is 1 is please] +Vertex: test[c is 2 is please] diff --git a/lang/interpret_test/TestAstFunc2/stmtfor37.txtar b/lang/interpret_test/TestAstFunc2/stmtfor37.txtar new file mode 100644 index 00000000..259fe440 --- /dev/null +++ b/lang/interpret_test/TestAstFunc2/stmtfor37.txtar @@ -0,0 +1,28 @@ +-- main.mcl -- +import "fmt" + +$list = ["a", "b", "c",] + +$index = 42 # should be out of scope + +for $index, $value in $list { + + class foo($x) { + $result = func($y1) { + "hello" + $x + $value + $y1 + } + } + include foo($value) as thing + $result = func($y2) { + "please" + $y2 + } + + # XXX: add $thing.some_func and so on... add more tests says sam. + $s = fmt.printf("%s is %d is %s", $thing.result("!"), $index, $result("!")) + test [$s,] {} +} + +-- OUTPUT -- +Vertex: test[helloaa! is 0 is please!] +Vertex: test[hellobb! is 1 is please!] +Vertex: test[hellocc! is 2 is please!] diff --git a/lang/interpret_test/TestAstFunc2/stmtfor38.txtar b/lang/interpret_test/TestAstFunc2/stmtfor38.txtar new file mode 100644 index 00000000..aab65d67 --- /dev/null +++ b/lang/interpret_test/TestAstFunc2/stmtfor38.txtar @@ -0,0 +1,21 @@ +-- main.mcl -- +import "fmt" + +$list1 = ["a", "b", "c",] + +class foo($x) { + $result = "hello " + $x +} + +for $index, $value in $list1 { + + include foo($value) as included + + $s = fmt.printf("%s is %d", $included.result, $index) + test [$s,] {} +} + +-- OUTPUT -- +Vertex: test[hello a is 0] +Vertex: test[hello b is 1] +Vertex: test[hello c is 2] diff --git a/lang/interpret_test/TestAstFunc2/stmtfor39.txtar b/lang/interpret_test/TestAstFunc2/stmtfor39.txtar new file mode 100644 index 00000000..bd7ecb18 --- /dev/null +++ b/lang/interpret_test/TestAstFunc2/stmtfor39.txtar @@ -0,0 +1,21 @@ +-- main.mcl -- +import "fmt" + +$list1 = ["a", "b", "c",] + +class foo($x) { + $result = "hello " + fmt.printf("%d", $x) +} + +for $index1, $value1 in $list1 { + + include foo($index1) as included + + $s = fmt.printf("%s is %d", $included.result, $index1) + test [$s,] {} +} + +-- OUTPUT -- +Vertex: test[hello 0 is 0] +Vertex: test[hello 1 is 1] +Vertex: test[hello 2 is 2] diff --git a/lang/interpret_test/TestAstFunc2/stmtfor40.txtar b/lang/interpret_test/TestAstFunc2/stmtfor40.txtar new file mode 100644 index 00000000..96e00014 --- /dev/null +++ b/lang/interpret_test/TestAstFunc2/stmtfor40.txtar @@ -0,0 +1,10 @@ +-- main.mcl -- +import "fmt" + +$list = ["a", "b", "c",] + +for $index, $value in $list { + $foo = $index # does nothing +} + +-- OUTPUT -- diff --git a/lang/interpret_test/TestAstFunc2/stmtfor41.txtar b/lang/interpret_test/TestAstFunc2/stmtfor41.txtar new file mode 100644 index 00000000..7d38ba82 --- /dev/null +++ b/lang/interpret_test/TestAstFunc2/stmtfor41.txtar @@ -0,0 +1,24 @@ +-- main.mcl -- +import "fmt" + +$list = ["a", "b", "c",] + +import "thing.mcl" # works + +for $index, $value in $list { + # The semantics are that only one copy of an import is needed... Not one per iteration. + # XXX: Error: could not find `inside` in env for ExprIterated + # XXX: I added a hack to catch this obvious case + #import "thing.mcl" # XXX: doesn't work :( + + $x = 42 + $thing.inside + + $s = fmt.printf("%s is %d = %d", $value, $index, $x) + test [$s,] {} +} +-- thing.mcl -- +$inside = 13 +-- OUTPUT -- +Vertex: test[a is 0 = 55] +Vertex: test[b is 1 = 55] +Vertex: test[c is 2 = 55] diff --git a/lang/interpret_test/TestAstFunc2/stmtfor42.txtar b/lang/interpret_test/TestAstFunc2/stmtfor42.txtar new file mode 100644 index 00000000..f763ccf7 --- /dev/null +++ b/lang/interpret_test/TestAstFunc2/stmtfor42.txtar @@ -0,0 +1,27 @@ +-- main.mcl -- +import "fmt" + +$list = ["a", "b", "c",] + +import "thing.mcl" # works + +for $index, $value in $list { + # The semantics are that only one copy of an import is needed... Not one per iteration. + # XXX: Error: could not find `inside` in env for ExprIterated + class foo($y) { + #import "thing.mcl" # XXX: doesn't work :( + $out = $y + 7 + $thing.inside + } + include foo($index) as usefoo + + $x = 42 + $usefoo.out + + $s = fmt.printf("%s is %d = %d", $value, $index, $x) + test [$s,] {} +} +-- thing.mcl -- +$inside = 13 +-- OUTPUT -- +Vertex: test[a is 0 = 62] +Vertex: test[b is 1 = 63] +Vertex: test[c is 2 = 64] diff --git a/lang/interpret_test/TestAstFunc2/stmtfor43.txtar b/lang/interpret_test/TestAstFunc2/stmtfor43.txtar new file mode 100644 index 00000000..5e043533 --- /dev/null +++ b/lang/interpret_test/TestAstFunc2/stmtfor43.txtar @@ -0,0 +1,22 @@ +-- main.mcl -- +import "fmt" + +$list = ["a", "b", "c",] + +#import "thing.mcl" # works + +for $index, $value in $list { + # The semantics are that only one copy of an import is needed... Not one per iteration. + # XXX: Error: could not find `inside` in env for ExprIterated + # XXX: We don't want this to be an error, but it is for now. + import "thing.mcl" # XXX: doesn't work :( + + $x = 42 + $thing.inside + + $s = fmt.printf("%s is %d = %d", $value, $index, $x) + test [$s,] {} +} +-- thing.mcl -- +$inside = 13 +-- OUTPUT -- +# err: errInit: a StmtImport can't be contained inside a StmtFor diff --git a/lang/interpret_test/TestAstFunc2/test-monomorphic-bind.txtar b/lang/interpret_test/TestAstFunc2/test-monomorphic-bind.txtar index 2127badf..1e52750a 100644 --- a/lang/interpret_test/TestAstFunc2/test-monomorphic-bind.txtar +++ b/lang/interpret_test/TestAstFunc2/test-monomorphic-bind.txtar @@ -10,4 +10,4 @@ test "test2" { anotherstr => $id("hello"), } -- OUTPUT -- -# err: errUnify: unify error with: topLevel(singleton(func(x) { var(x) })): type error: int != str +# err: errUnify: unify error with: topLevel(singleton(func(x) { var(x) })): type error: str != int diff --git a/lang/interpret_test/TestAstFunc2/test-monomorphic-class-arg.txtar b/lang/interpret_test/TestAstFunc2/test-monomorphic-class-arg.txtar index 392a1e7a..7504889b 100644 --- a/lang/interpret_test/TestAstFunc2/test-monomorphic-class-arg.txtar +++ b/lang/interpret_test/TestAstFunc2/test-monomorphic-class-arg.txtar @@ -12,4 +12,4 @@ class use_polymorphically($id) { } include use_polymorphically(func($x) {$x}) -- OUTPUT -- -# err: errUnify: unify error with: topLevel(singleton(func(x) { var(x) })): type error: int != str +# err: errUnify: unify error with: topLevel(singleton(func(x) { var(x) })): type error: str != int diff --git a/lang/interpret_test/TestAstFunc2/wat.txtar b/lang/interpret_test/TestAstFunc2/wat.txtar new file mode 100644 index 00000000..51e8d398 --- /dev/null +++ b/lang/interpret_test/TestAstFunc2/wat.txtar @@ -0,0 +1,11 @@ +-- main.mcl -- +$x = ["foo", "bar",] +$f = func() { + $x[0] +} +$y = $f() +$z = $f() + +test ["${y}${z}",] {} +-- OUTPUT -- +Vertex: test[foofoo] diff --git a/lang/interpret_test/TestAstFunc2/watsam1.txtar b/lang/interpret_test/TestAstFunc2/watsam1.txtar new file mode 100644 index 00000000..02073dd4 --- /dev/null +++ b/lang/interpret_test/TestAstFunc2/watsam1.txtar @@ -0,0 +1,14 @@ +-- main.mcl -- +$call = func($f, $arg) { + $f($arg) +} + +$lambda = func($x) { + $call(func($z) { "hello" + $x }, "nope") +} + +$s = $lambda("world") + +test [$s,] {} +-- OUTPUT -- +Vertex: test[helloworld] diff --git a/lang/interpret_test/TestAstFunc2/watsam2.txtar b/lang/interpret_test/TestAstFunc2/watsam2.txtar new file mode 100644 index 00000000..8a26e968 --- /dev/null +++ b/lang/interpret_test/TestAstFunc2/watsam2.txtar @@ -0,0 +1,21 @@ +-- main.mcl -- +$call = func($f, $arg) { + $f($arg) +} + +$lambda = func($x) { + $call( + if $x == "nope1" { + func($z) { "nope2" + $x } + } else { + func($z) { "hello" + $x } + }, + "bye" + ) +} + +$s = $lambda("world") + +test [$s,] {} +-- OUTPUT -- +Vertex: test[helloworld] diff --git a/lang/lang.go b/lang/lang.go index ec3c4d69..38c595de 100644 --- a/lang/lang.go +++ b/lang/lang.go @@ -290,16 +290,23 @@ func (obj *Lang) Init(ctx context.Context) error { // we assume that for some given code, the list of funcs doesn't change // iow, we don't support variable, variables or absurd things like that obj.graph = &pgraph.Graph{Name: "functionGraph"} - env := make(map[string]interfaces.Func) - for k, v := range scope.Variables { - g, builtinFunc, err := v.Graph(nil) - if err != nil { - return errwrap.Wrapf(err, "calling Graph on builtins") - } - obj.graph.AddGraph(g) - env[k] = builtinFunc - } - g, err := obj.ast.Graph() // build the graph of functions + env := interfaces.EmptyEnv() + // XXX: Do we need to do something like this? + //for k, v := range scope.Variables { + // g, builtinFunc, err := v.Graph(nil) + // if err != nil { + // return errwrap.Wrapf(err, "calling Graph on builtins") + // } + // obj.graph.AddGraph(g) + // env.Variables[k] = builtinFunc // XXX: Ask Sam (.Functions ???) + //} + //for k, v := range scope.Functions { + // env.Functions[k] = &interfaces.Closure{ + // Env: interfaces.EmptyEnv(), + // Expr: v, + // } + //} + g, err := obj.ast.Graph(env) // build the graph of functions if err != nil { return errwrap.Wrapf(err, "could not generate function graph") } diff --git a/lang/parser/lexer.nex b/lang/parser/lexer.nex index 7e3c8133..5078a94b 100644 --- a/lang/parser/lexer.nex +++ b/lang/parser/lexer.nex @@ -154,6 +154,11 @@ lval.str = yylex.Text() return IN } +/for/ { + yylex.pos(lval) // our pos + lval.str = yylex.Text() + return FOR + } /\->/ { yylex.pos(lval) // our pos lval.str = yylex.Text() diff --git a/lang/parser/parser.y b/lang/parser/parser.y index d568ecd5..d77b4ff8 100644 --- a/lang/parser/parser.y +++ b/lang/parser/parser.y @@ -92,7 +92,7 @@ func init() { %token OPEN_CURLY CLOSE_CURLY %token OPEN_PAREN CLOSE_PAREN %token OPEN_BRACK CLOSE_BRACK -%token IF ELSE +%token IF ELSE FOR %token BOOL STRING INTEGER FLOAT %token EQUALS DOLLAR %token COMMA COLON SEMICOLON @@ -216,6 +216,18 @@ stmt: } locate(yylex, $1, yyDollar[len(yyDollar)-1], $$.stmt) } + // iterate over lists + // `for $index, $value in $list { }` +| FOR var_identifier COMMA var_identifier IN expr OPEN_CURLY prog CLOSE_CURLY + { + $$.stmt = &ast.StmtFor{ + Index: $2.str, // no $ prefix + Value: $4.str, // no $ prefix + Expr: $6.expr, // XXX: name this List ? + Body: $8.stmt, + } + locate(yylex, $1, yyDollar[len(yyDollar)-1], $$.stmt) + } // this is the named version, iow, a user-defined function (statement) // `func name() { }` // `func name() { }`