lang: Add a for loop statement for iterating over a list

This adds a for statement which is used to iterate over a list with a
body of statements. This is an important data transformation tool which
should be used sparingly, but is important to have.

An import statement inside of a for loop is not currently supported. We
have a simple hack to detect the obvious cases, but more deeply nested
scenarios probably won't be caught, and you'll get an obscure error
message if you try to do this.

This was incredibly challenging to get right, and it's all thanks to Sam
for his brilliance.

Co-authored-by: Samuel Gélineau <gelisam@gmail.com>
This commit is contained in:
James Shubin
2025-03-04 21:55:29 -05:00
parent c456a5ab97
commit cf7e73bbf6
63 changed files with 2814 additions and 203 deletions

View File

@@ -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 - **resource**: produces a resource
```mcl ```mcl

39
examples/lang/for.mcl Normal file
View File

@@ -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]

View File

@@ -73,6 +73,11 @@ func (obj *StmtIf) ScopeGraph(g *pgraph.Graph) {
g.AddVertex(obj) 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. // ScopeGraph adds nodes and vertices to the supplied graph.
func (obj *StmtProg) ScopeGraph(g *pgraph.Graph) { func (obj *StmtProg) ScopeGraph(g *pgraph.Graph) {
g.AddVertex(obj) g.AddVertex(obj)
@@ -196,6 +201,17 @@ func (obj *ExprParam) ScopeGraph(g *pgraph.Graph) {
g.AddVertex(obj) 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. // ScopeGraph adds nodes and vertices to the supplied graph.
func (obj *ExprPoly) ScopeGraph(g *pgraph.Graph) { func (obj *ExprPoly) ScopeGraph(g *pgraph.Graph) {
g.AddVertex(obj) g.AddVertex(obj)

File diff suppressed because it is too large Load Diff

View File

@@ -339,11 +339,54 @@ func trueCallee(apparentCallee interfaces.Expr) interfaces.Expr {
return trueCallee(x.Definition) return trueCallee(x.Definition)
case *ExprSingleton: case *ExprSingleton:
return trueCallee(x.Definition) 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: default:
return apparentCallee 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 // 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 // that the user gets a hint about what's going on. This is useful for catching
// bugs in our programming or in user code! // bugs in our programming or in user code!

View File

@@ -1240,6 +1240,8 @@ func (obj *Engine) Run(ctx context.Context) (reterr error) {
} }
fn := func(nodeCtx context.Context) (reterr error) { fn := func(nodeCtx context.Context) (reterr error) {
// NOTE: Comment out this defer to make
// debugging a lot easier.
defer func() { defer func() {
// catch programming errors // catch programming errors
if r := recover(); r != nil { if r := recover(); r != nil {

254
lang/funcs/structs/for.go Normal file
View File

@@ -0,0 +1,254 @@
// Mgmt
// Copyright (C) James Shubin and the project contributors
// Written by James Shubin <james@shubin.ca> 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 <https://www.gnu.org/licenses/>.
//
// 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))
}

View File

@@ -206,8 +206,14 @@ func (obj *opAddEdge) Fn(opapi *opapi) error {
args[x] = struct{}{} args[x] = struct{}{}
} }
if len(args) != len(obj.FE.Args)+len(edge.Args) { if len(args) != len(obj.FE.Args)+len(edge.Args) {
// programming error // previously, a programming error
return fmt.Errorf("duplicate arg found") // 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{} newArgs := []string{}
for x := range args { for x := range args {

View File

@@ -85,8 +85,9 @@ type Stmt interface {
// child statements, and Infer/Check for child expressions. // child statements, and Infer/Check for child expressions.
TypeCheck() ([]*UnificationInvariant, error) TypeCheck() ([]*UnificationInvariant, error)
// Graph returns the reactive function graph expressed by this node. // Graph returns the reactive function graph expressed by this node. It
Graph() (*pgraph.Graph, error) // 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 // Output returns the output that this "program" produces. This output
// is what is used to build the output graph. It requires the input // 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 // Graph returns the reactive function graph expressed by this node. It
// takes in the environment of any functions in scope. It also returns // takes in the environment of any functions in scope. It also returns
// the function for this node. // 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 // SetValue stores the result of the last computation of this expression
// node. // node.
@@ -277,9 +278,21 @@ func (obj *Data) AbsFilename() string {
// An interesting note about these is that they exist in a distinct namespace // An interesting note about these is that they exist in a distinct namespace
// from the variables, which could actually contain lambda functions. // from the variables, which could actually contain lambda functions.
type Scope struct { type Scope struct {
// Variables maps the scope of name to Expr.
Variables map[string]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 Chain []Node // chain of previously seen node's
} }
@@ -291,6 +304,7 @@ func EmptyScope() *Scope {
Variables: make(map[string]Expr), Variables: make(map[string]Expr),
Functions: make(map[string]Expr), Functions: make(map[string]Expr),
Classes: make(map[string]Stmt), Classes: make(map[string]Stmt),
Iterated: false,
Chain: []Node{}, Chain: []Node{},
} }
} }
@@ -307,6 +321,7 @@ func (obj *Scope) Copy() *Scope {
variables := make(map[string]Expr) variables := make(map[string]Expr)
functions := make(map[string]Expr) functions := make(map[string]Expr)
classes := make(map[string]Stmt) classes := make(map[string]Stmt)
iterated := obj.Iterated
chain := []Node{} chain := []Node{}
for k, v := range obj.Variables { // copy for k, v := range obj.Variables { // copy
@@ -326,6 +341,7 @@ func (obj *Scope) Copy() *Scope {
Variables: variables, Variables: variables,
Functions: functions, Functions: functions,
Classes: classes, Classes: classes,
Iterated: iterated,
Chain: chain, Chain: chain,
} }
} }
@@ -377,6 +393,10 @@ func (obj *Scope) Merge(scope *Scope) error {
obj.Classes[name] = scope.Classes[name] obj.Classes[name] = scope.Classes[name]
} }
if scope.Iterated { // XXX: how should we merge this?
obj.Iterated = scope.Iterated
}
return err return err
} }
@@ -398,6 +418,94 @@ func (obj *Scope) IsEmpty() bool {
return true 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 // 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. // is sometimes accompanied by a type. This does not satisfy the Expr interface.
type Arg struct { type Arg struct {

View File

@@ -507,7 +507,7 @@ func TestAstFunc1(t *testing.T) {
} }
// build the function graph // build the function graph
fgraph, err := iast.Graph() fgraph, err := iast.Graph(interfaces.EmptyEnv()) // XXX: Ask Sam
if (!fail || !failGraph) && err != nil { if (!fail || !failGraph) && err != nil {
t.Errorf("test #%d: FAIL", index) t.Errorf("test #%d: FAIL", index)
t.Errorf("test #%d: functions failed with: %+v", index, err) 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 // in implementation and before unification, and static
// once we've unified the specific resource. // 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 // build the function graph
fgraph, err := iast.Graph() fgraph, err := iast.Graph(env) // XXX: Ask Sam
if (!fail || !failGraph) && err != nil { if (!fail || !failGraph) && err != nil {
t.Errorf("test #%d: FAIL", index) t.Errorf("test #%d: FAIL", index)
t.Errorf("test #%d: functions failed with: %+v", index, err) 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. // once we've unified the specific resource.
// build the function graph // build the function graph
fgraph, err := iast.Graph() fgraph, err := iast.Graph(interfaces.EmptyEnv()) // XXX: Ask Sam
if (!fail || !failGraph) && err != nil { if (!fail || !failGraph) && err != nil {
t.Errorf("test #%d: FAIL", index) t.Errorf("test #%d: FAIL", index)
t.Errorf("test #%d: functions failed with: %+v", index, err) t.Errorf("test #%d: functions failed with: %+v", index, err)

View File

@@ -17,4 +17,4 @@ $out2 = $add($val) # hellohello
test [fmt.printf("%s + %s is %s", $val, $val, $out2),] {} # simple concat test [fmt.printf("%s + %s is %s", $val, $val, $out2),] {} # simple concat
-- OUTPUT -- -- 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

View File

@@ -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]

View File

@@ -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]

View File

@@ -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]

View File

@@ -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]

View File

@@ -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]

View File

@@ -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]

View File

@@ -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]

View File

@@ -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]

View File

@@ -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

View File

@@ -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]

View File

@@ -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]

View File

@@ -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]

View File

@@ -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]

View File

@@ -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]

View File

@@ -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]

View File

@@ -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]

View File

@@ -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]

View File

@@ -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]

View File

@@ -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]

View File

@@ -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]

View File

@@ -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]

View File

@@ -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]

View File

@@ -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]

View File

@@ -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]

View File

@@ -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]

View File

@@ -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]

View File

@@ -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]

View File

@@ -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]

View File

@@ -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]

View File

@@ -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]

View File

@@ -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]

View File

@@ -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]

View File

@@ -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]

View File

@@ -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]

View File

@@ -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]

View File

@@ -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}]

View File

@@ -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]

View File

@@ -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!]

View File

@@ -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]

View File

@@ -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]

View File

@@ -0,0 +1,10 @@
-- main.mcl --
import "fmt"
$list = ["a", "b", "c",]
for $index, $value in $list {
$foo = $index # does nothing
}
-- OUTPUT --

View File

@@ -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]

View File

@@ -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]

View File

@@ -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

View File

@@ -10,4 +10,4 @@ test "test2" {
anotherstr => $id("hello"), anotherstr => $id("hello"),
} }
-- OUTPUT -- -- 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

View File

@@ -12,4 +12,4 @@ class use_polymorphically($id) {
} }
include use_polymorphically(func($x) {$x}) include use_polymorphically(func($x) {$x})
-- OUTPUT -- -- 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

View File

@@ -0,0 +1,11 @@
-- main.mcl --
$x = ["foo", "bar",]
$f = func() {
$x[0]
}
$y = $f()
$z = $f()
test ["${y}${z}",] {}
-- OUTPUT --
Vertex: test[foofoo]

View File

@@ -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]

View File

@@ -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]

View File

@@ -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 // 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 // iow, we don't support variable, variables or absurd things like that
obj.graph = &pgraph.Graph{Name: "functionGraph"} obj.graph = &pgraph.Graph{Name: "functionGraph"}
env := make(map[string]interfaces.Func) env := interfaces.EmptyEnv()
for k, v := range scope.Variables { // XXX: Do we need to do something like this?
g, builtinFunc, err := v.Graph(nil) //for k, v := range scope.Variables {
if err != nil { // g, builtinFunc, err := v.Graph(nil)
return errwrap.Wrapf(err, "calling Graph on builtins") // if err != nil {
} // return errwrap.Wrapf(err, "calling Graph on builtins")
obj.graph.AddGraph(g) // }
env[k] = builtinFunc // obj.graph.AddGraph(g)
} // env.Variables[k] = builtinFunc // XXX: Ask Sam (.Functions ???)
g, err := obj.ast.Graph() // build the graph of 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 { if err != nil {
return errwrap.Wrapf(err, "could not generate function graph") return errwrap.Wrapf(err, "could not generate function graph")
} }

View File

@@ -154,6 +154,11 @@
lval.str = yylex.Text() lval.str = yylex.Text()
return IN return IN
} }
/for/ {
yylex.pos(lval) // our pos
lval.str = yylex.Text()
return FOR
}
/\->/ { /\->/ {
yylex.pos(lval) // our pos yylex.pos(lval) // our pos
lval.str = yylex.Text() lval.str = yylex.Text()

View File

@@ -92,7 +92,7 @@ func init() {
%token OPEN_CURLY CLOSE_CURLY %token OPEN_CURLY CLOSE_CURLY
%token OPEN_PAREN CLOSE_PAREN %token OPEN_PAREN CLOSE_PAREN
%token OPEN_BRACK CLOSE_BRACK %token OPEN_BRACK CLOSE_BRACK
%token IF ELSE %token IF ELSE FOR
%token BOOL STRING INTEGER FLOAT %token BOOL STRING INTEGER FLOAT
%token EQUALS DOLLAR %token EQUALS DOLLAR
%token COMMA COLON SEMICOLON %token COMMA COLON SEMICOLON
@@ -216,6 +216,18 @@ stmt:
} }
locate(yylex, $1, yyDollar[len(yyDollar)-1], $$.stmt) locate(yylex, $1, yyDollar[len(yyDollar)-1], $$.stmt)
} }
// iterate over lists
// `for $index, $value in $list { <body> }`
| 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) // this is the named version, iow, a user-defined function (statement)
// `func name() { <expr> }` // `func name() { <expr> }`
// `func name(<arg>) { <expr> }` // `func name(<arg>) { <expr> }`