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() { }`