lang: Add a forkv loop statement for iterating over a map

This adds a forkv statement which is used to iterate over a map 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 forkv 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.

Note, I couldn't think of a better keyword that "forkv" but suggestions
are welcome if you think you have a better idea. Other ideas were formap
and foreach, but neither got me very excited.
This commit is contained in:
James Shubin
2025-03-05 13:18:35 -05:00
parent cf7e73bbf6
commit 2899bc234a
51 changed files with 1885 additions and 2 deletions

View File

@@ -109,6 +109,15 @@ expression
} }
``` ```
- **forkv**: loop over a map with a body of statements
```mcl
$map = {0 => "a", 1 => "b", 2 => "c",}
forkv $key, $val in $map {
# some statements go here
}
```
- **resource**: produces a resource - **resource**: produces a resource
```mcl ```mcl

21
examples/lang/forkv.mcl Normal file
View File

@@ -0,0 +1,21 @@
import "datetime"
import "fmt"
import "math"
$now = datetime.now()
$num = if math.mod($now, 2) == 0 {
13
} else {
$now
}
# NOTE: the graph doesn't get regenerated just because a map value changed!
$m = {"foo" => 42, "bar" => $num,}
forkv $k, $v in $m {
print ["${k}",] {
msg => fmt.printf("hello %s; count: %d", $k, $v),
Meta:autogroup => true,
}
}

View File

@@ -3505,6 +3505,509 @@ func (obj *StmtFor) Output(table map[interfaces.Func]types.Value) (*interfaces.O
}, nil }, nil
} }
// StmtForKV represents an iteration over a map. The body contains statements.
type StmtForKV struct {
Textarea
data *interfaces.Data
scope *interfaces.Scope // store for referencing this later
Key string // no $ prefix
Val string // no $ prefix
TypeKey *types.Type
TypeVal *types.Type
keyParam *ExprParam
valParam *ExprParam
Expr interfaces.Expr
exprPtr interfaces.Func // ptr for table lookup
Body interfaces.Stmt // optional, but usually present
iterBody map[types.Value]interfaces.Stmt
}
// String returns a short representation of this statement.
func (obj *StmtForKV) String() string {
// TODO: improve/change this if needed
s := fmt.Sprintf("forkv($%s, $%s)", obj.Key, obj.Val)
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 *StmtForKV) 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 *StmtForKV) Init(data *interfaces.Data) error {
obj.data = data
obj.Textarea.Setup(data)
obj.iterBody = make(map[types.Value]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 StmtForKV")
}
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 *StmtForKV) 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 &StmtForKV{
Textarea: obj.Textarea,
data: obj.data,
scope: obj.scope, // XXX: Should we copy/include this here?
Key: obj.Key,
Val: obj.Val,
TypeKey: obj.TypeKey,
TypeVal: obj.TypeVal,
keyParam: obj.keyParam, // XXX: Should we copy/include this here?
valParam: obj.valParam, // 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 *StmtForKV) 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 &StmtForKV{
Textarea: obj.Textarea,
data: obj.data,
scope: obj.scope, // XXX: Should we copy/include this here?
Key: obj.Key,
Val: obj.Val,
TypeKey: obj.TypeKey,
TypeVal: obj.TypeVal,
keyParam: obj.keyParam, // XXX: Should we copy/include this here?
valParam: obj.valParam, // 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 *StmtForKV) 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: "stmtforkvexpr1"}
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: "stmtforkvexpr2"}
graph.AddEdge(n, k, edge)
}
if obj.Body == nil { // return early
return graph, cons, nil
}
// additional constraints...
edge1 := &pgraph.SimpleEdge{Name: "stmtforkvbodyexpr"}
graph.AddEdge(obj.Expr, obj.Body, edge1) // prod -> cons
edge2 := &pgraph.SimpleEdge{Name: "stmtforkvbody1"}
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: "stmtforkvbody2"}
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 *StmtForKV) 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.
typExprKey := obj.TypeKey
if obj.TypeKey == nil {
typExprKey = &types.Type{
Kind: types.KindUnification,
Uni: types.NewElem(), // unification variable, eg: ?1
}
}
obj.keyParam = newExprParam(
obj.Key,
typExprKey,
)
typExprVal := obj.TypeVal
if obj.TypeVal == nil {
typExprVal = &types.Type{
Kind: types.KindUnification,
Uni: types.NewElem(), // unification variable, eg: ?1
}
}
obj.valParam = newExprParam(
obj.Val,
typExprVal,
)
newScope := scope.Copy()
newScope.Iterated = true // important!
newScope.Variables[obj.Key] = obj.keyParam
newScope.Variables[obj.Val] = obj.valParam
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 *StmtForKV) 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!)
typExprKey := obj.keyParam.typ
typExprVal := obj.valParam.typ
typExpr := &types.Type{
Kind: types.KindMap,
Key: typExprKey,
Val: typExprVal,
}
invar := &interfaces.UnificationInvariant{
Node: obj,
Expr: obj.Expr,
Expect: typExpr, // the map
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!
invarKey := &interfaces.UnificationInvariant{
Node: obj,
Expr: obj.keyParam,
Expect: typExprKey, // the map key type
Actual: typExprKey, // not necessarily an int!
}
invariants = append(invariants, invarKey)
invarVal := &interfaces.UnificationInvariant{
Node: obj,
Expr: obj.valParam,
Expect: typExprVal, // the map val type
Actual: typExprVal,
}
invariants = append(invariants, invarVal)
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 *StmtForKV) Graph(env *interfaces.Env) (*pgraph.Graph, error) {
graph, err := pgraph.NewGraph("forkv")
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 map changes.
setOnIterBody := func(innerTxn interfaces.Txn, ptr types.Value, key, val interfaces.Func) error {
// Extend the environment with the two loop variables.
extendedEnv := env.Copy()
// calling convention
extendedEnv.Variables[obj.keyParam.envKey] = &interfaces.FuncSingleton{
MakeFunc: func() (*pgraph.Graph, interfaces.Func, error) {
f := key
g, err := pgraph.NewGraph("g")
if err != nil {
return nil, nil, err
}
g.AddVertex(f)
return g, f, nil
},
}
// XXX: create the function in ForKVFunc instead?
extendedEnv.Variables[obj.valParam.envKey] = &interfaces.FuncSingleton{ // XXX: We could set this one statically
MakeFunc: func() (*pgraph.Graph, interfaces.Func, error) {
f := val
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[ptr] = body
// XXX: Do we fake our map by giving each key an index too?
// NOTE: We can't do append since the key might not be an int.
//obj.iterBody = append(obj.iterBody, body) // not possible
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 map passing itself.
edgeName := structs.ForKVFuncArgNameMap
forKVFunc := &structs.ForKVFunc{
KeyType: obj.keyParam.typ,
ValType: obj.valParam.typ,
EdgeName: edgeName,
SetOnIterBody: setOnIterBody,
ClearIterBody: func(length int) { // XXX: use length?
mutex.Lock()
obj.iterBody = map[types.Value]interfaces.Stmt{}
mutex.Unlock()
},
}
graph.AddVertex(forKVFunc)
graph.AddEdge(f, forKVFunc, &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 *StmtForKV) 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{}
m := expr.Map() // must not panic!
for key := range m {
// key and val are both an mcl types.Value
// XXX: Do we need a mutex around this iterBody access?
if _, exists := obj.iterBody[key]; !exists {
// programming error
return nil, fmt.Errorf("programming error on key: %s", key)
}
output, err := obj.iterBody[key].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 // 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 // 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 // the bind statement's are correctly applied in this scope, and irrespective of
@@ -3804,6 +4307,25 @@ func (obj *StmtProg) Ordering(produces map[string]interfaces.Node) (*pgraph.Grap
// } // }
// prod[uid2] = stmt // store // prod[uid2] = stmt // store
//} //}
//if stmt, ok := x.(*StmtForKV); ok {
// if stmt.Key == "" {
// return nil, nil, fmt.Errorf("missing index name")
// }
// uid1 := varOrderingPrefix + stmt.Key // 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.Val == "" {
// return nil, nil, fmt.Errorf("missing val name")
// }
// uid2 := varOrderingPrefix + stmt.Val // 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! newProduces := CopyNodeMapping(produces) // don't modify the input map!
@@ -5060,7 +5582,7 @@ func (obj *StmtProg) Output(table map[interfaces.Func]types.Value) (*interfaces.
func (obj *StmtProg) IsModuleUnsafe() error { // TODO: rename this function? func (obj *StmtProg) IsModuleUnsafe() error { // TODO: rename this function?
for _, x := range obj.Body { for _, x := range obj.Body {
// stmt's allowed: import, bind, func, class // stmt's allowed: import, bind, func, class
// stmt's not-allowed: for, if, include, res, edge // stmt's not-allowed: for, forkv, if, include, res, edge
switch x.(type) { switch x.(type) {
case *StmtImport: case *StmtImport:
case *StmtBind: case *StmtBind:

314
lang/funcs/structs/forkv.go Normal file
View File

@@ -0,0 +1,314 @@
// 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 (
// ForKVFuncName is the unique name identifier for this function.
ForKVFuncName = "forkv"
// ForKVFuncArgNameMap is the name for the edge which connects the input
// map to CallFunc.
ForKVFuncArgNameMap = "map"
)
// ForKVFunc receives a map from upstream. We iterate over the received map to
// build a subgraph that processes each key and val, and in doing so we get a
// larger function graph. This is rebuilt as necessary if the input map changes.
type ForKVFunc struct {
KeyType *types.Type
ValType *types.Type
EdgeName string // name of the edge used
SetOnIterBody func(innerTxn interfaces.Txn, ptr types.Value, key, val interfaces.Func) error
ClearIterBody func(length int)
init *interfaces.Init
lastForKVMap types.Value // remember the last value
lastInputMapLength int // remember the last input map length
}
// String returns a simple name for this function. This is needed so this struct
// can satisfy the pgraph.Vertex interface.
func (obj *ForKVFunc) String() string {
return ForKVFuncName
}
// Validate makes sure we've built our struct properly.
func (obj *ForKVFunc) Validate() error {
if obj.KeyType == nil {
return fmt.Errorf("must specify a type")
}
if obj.ValType == 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 *ForKVFunc) Info() *interfaces.Info {
var typ *types.Type
if obj.KeyType != nil && obj.ValType != nil { // don't panic if called speculatively
// XXX: Improve function engine so it can return no value?
//typ = types.NewType(fmt.Sprintf("func(%s map{%s: %s})", obj.EdgeName, obj.KeyType, obj.ValType)) // 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 map{%s: %s}) float", obj.EdgeName, obj.KeyType, obj.ValType))
}
return &interfaces.Info{
Pure: true,
Memo: false, // TODO: ???
Sig: typ,
Err: obj.Validate(),
}
}
// Init runs some startup code for this composite function.
func (obj *ForKVFunc) Init(init *interfaces.Init) error {
obj.init = init
obj.lastForKVMap = nil
obj.lastInputMapLength = -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 *ForKVFunc) Stream(ctx context.Context) error {
defer close(obj.init.Output) // the sender closes
// A Func to send input maps 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 map we received to
// the subgraph.
inputChan := make(chan types.Value)
subgraphInput := &ChannelBasedSourceFunc{
Name: "subgraphInput",
Source: obj,
Chan: inputChan,
Type: obj.mapType(),
}
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
//canReceiveMoreMapValues = false
// We don't ever shutdown here, since even if we
// don't get more maps, that last map 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
}
forKVMap, exists := input.Struct()[obj.EdgeName]
if !exists {
return fmt.Errorf("programming error, can't find edge")
}
// It's important to have this compare step to avoid
// redundant graph replacements which slow things down,
// but also cause the engine to lock, which can preempt
// the process scheduler, which can cause duplicate or
// unnecessary re-sending of values here, which causes
// the whole process to repeat ad-nauseum.
n := len(forKVMap.Map())
// If the keys are the same, that's enough! We don't
// need to rebuild the graph unless any of the keys
// change, since those are our unique identifiers into
// the whole loop. As a result, we don't compare between
// the entire two map, since while we could rebuild the
// graph on any change, it's easier to leave it as is
// and simply push new values down the already built
// graph if any value changes.
if obj.lastInputMapLength != n || obj.cmpMapKeys(forKVMap) != nil {
// TODO: Technically we only need to save keys!
obj.lastForKVMap = forKVMap
obj.lastInputMapLength = 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 map to the subgraph
select {
case inputChan <- forKVMap:
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 *ForKVFunc) 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.lastInputMapLength) // XXX: pass in size?
forKVMap := obj.lastForKVMap.Map()
// XXX: Should we loop in a deterministic order?
// XXX: Should our type support the new iterator pattern?
for k := range forKVMap {
ptr := k
argNameKey := "forkvInputMapKey"
argNameVal := "forkvInputMapVal"
// the key
inputElemFuncKey := SimpleFnToDirectFunc(
fmt.Sprintf("forkvInputElemKey[%v]", ptr),
&types.FuncValue{
V: func(_ context.Context, args []types.Value) (types.Value, error) {
if len(args) != 1 {
return nil, fmt.Errorf("inputElemFuncKey: expected a single argument")
}
//arg := args[0]
//m, ok := arg.(*types.MapValue)
//if !ok {
// return nil, fmt.Errorf("inputElemFuncKey: expected a MapValue argument")
//}
// XXX: If we had some sort of index fn?
//return m.Map().Index(?), nil
return k, nil
},
T: types.NewType(fmt.Sprintf("func(%s %s) %s", argNameKey, obj.mapType(), obj.KeyType)),
},
)
obj.init.Txn.AddVertex(inputElemFuncKey)
obj.init.Txn.AddEdge(subgraphInput, inputElemFuncKey, &interfaces.FuncEdge{
Args: []string{argNameKey},
})
// the val
inputElemFuncVal := SimpleFnToDirectFunc(
fmt.Sprintf("forkvInputElemVal[%v]", ptr),
&types.FuncValue{
V: func(_ context.Context, args []types.Value) (types.Value, error) {
if len(args) != 1 {
return nil, fmt.Errorf("inputElemFuncVal: expected a single argument")
}
//return v, nil // If we always rebuild the map.
arg := args[0]
m, ok := arg.(*types.MapValue)
if !ok {
return nil, fmt.Errorf("inputElemFuncVal: expected a MapValue argument")
}
return m.Map()[ptr], nil
},
T: types.NewType(fmt.Sprintf("func(%s %s) %s", argNameVal, obj.mapType(), obj.ValType)),
},
)
obj.init.Txn.AddVertex(inputElemFuncVal)
obj.init.Txn.AddEdge(subgraphInput, inputElemFuncVal, &interfaces.FuncEdge{
Args: []string{argNameVal},
})
if err := obj.SetOnIterBody(obj.init.Txn, ptr, inputElemFuncKey, inputElemFuncVal); err != nil {
return errwrap.Wrapf(err, "could not call SetOnIterBody()")
}
}
return obj.init.Txn.Commit()
}
func (obj *ForKVFunc) mapType() *types.Type {
return types.NewType(fmt.Sprintf("map{%s: %s}", obj.KeyType, obj.ValType))
}
// cmpMapKeys compares the input map with the cached private lastForKVMap field.
// If either are nil, or if the keys of the maps are not identical, then this
// errors.
func (obj *ForKVFunc) cmpMapKeys(m types.Value) error {
if obj.lastForKVMap == nil || m == nil {
return fmt.Errorf("got a nil map")
}
m1 := obj.lastForKVMap.Map()
m2 := m.(*types.MapValue) // must not panic!
if len(m1) != len(m.Map()) {
return fmt.Errorf("lengths differ")
}
for k := range m1 {
if _, exists := m2.Lookup(k); !exists {
return fmt.Errorf("key not found")
}
}
return nil
}

View File

@@ -0,0 +1,31 @@
-- main.mcl --
import "fmt"
$map0 = {0 => "a", 1 => "b", 2 => "c",}
$map1 = {0 => "d", 1 => "e", 2 => "f",}
$map2 = {0 => "g", 1 => "h", 2 => "i",}
$map3 = {0 => "j", 1 => "k", 2 => "l",}
$list = [$map0, $map1, $map2, $map3,]
for $index, $value in $list {
forkv $key, $val in $value {
$s = fmt.printf("%s is %d", $val, $key+$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,13 @@
-- main.mcl --
import "fmt"
$map = {0 => "a", 1 => "b", 2 => "c",}
forkv $key, $val in $map {
test [$val,] {}
}
-- OUTPUT --
Vertex: test[a]
Vertex: test[b]
Vertex: test[c]

View File

@@ -0,0 +1,15 @@
-- main.mcl --
import "fmt"
$map = {0 => "a", 1 => "b", 2 => "c",}
forkv $key, $val in $map {
$s = $val # 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"
$map = {0 => "a", 1 => "b", 2 => "c",}
forkv $key, $val in $map {
test [fmt.printf("%s is %d", $val, $key),] {}
}
-- 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"
$map = {0 => "a", 1 => "b", 2 => "c",}
forkv $key, $val in $map {
$s = fmt.printf("%s is %d", $val, $key)
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"
$map = {0 => "a", 1 => "b", 2 => "c",}
$s = "nope" # should be out of scope
forkv $key, $val in $map {
$s = fmt.printf("%s is %d", $val, $key)
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"
$map = {0 => "a", 1 => "b", 2 => "c",}
$val = "nope" # should be out of scope
forkv $key, $val in $map {
$s = fmt.printf("%s is %d", $val, $key)
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"
$map = {0 => "a", 1 => "b", 2 => "c",}
$val = 42 # should be out of scope (also not the same type)
forkv $key, $val in $map {
$s = fmt.printf("%s is %d", $val, $key)
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"
$map = {0 => "a", 1 => "b", 2 => "c",}
$key = 42 # should be out of scope
forkv $key, $val in $map {
$key = 42
$s = fmt.printf("%s is %d", $val, $key)
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"
$map = {0 => "a", 1 => "b", 2 => "c",}
$key = 42 # should be out of scope
forkv $key, $val in $map {
$key = if math.mod($key, 2) == 0 {
$key
} else {
42
}
$s = fmt.printf("%s is %d", $val, $key)
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"
$map = {0 => "a", 1 => "b", 2 => "c",}
$key = 42 # should be out of scope
forkv $key, $val in $map {
$newkey = if math.mod($key, 2) == 0 {
$key
} else {
42
}
$s = fmt.printf("%s is %d", $val, $newkey)
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"
$map = {0 => "a", 1 => "b", 2 => "c",}
$key = 42 # should be out of scope
forkv $key, $val in $map {
$fn = func($x) {
"hello " + $x
}
$s = fmt.printf("%s is %d", $fn($val), $key)
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"
$map = {0 => "a", 1 => "b", 2 => "c",}
$key = 42 # should be out of scope
forkv $key, $val in $map {
func fn($x) {
"hello " + $x
}
$s = fmt.printf("%s is %d", fn($val), $key)
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"
$map = {0 => "a", 1 => "b", 2 => "c",}
$key = 42 # should be out of scope
forkv $key, $val in $map {
$fn = func($x) {
"hello " + $val
}
$s = fmt.printf("%s is %d", $fn("whatever"), $key)
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"
$map = {0 => "a", 1 => "b", 2 => "c",}
$key = 42 # should be out of scope
forkv $key, $val in $map {
func fn($x) {
"hello " + $val
}
$s = fmt.printf("%s is %d", fn("whatever"), $key)
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"
$map = {0 => "a", 1 => "b", 2 => "c",}
$key = 42 # should be out of scope
forkv $key, $val in $map {
$fn = func($x) {
"hello" + $value
}
$value = " " + $val
$s = fmt.printf("%s is %d", $fn("whatever"), $key)
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"
$map = {0 => "a", 1 => "b", 2 => "c",}
$key = 42 # should be out of scope
forkv $key, $val in $map {
func fn($x) {
"hello" + $value
}
$value = " " + $val
$s = fmt.printf("%s is %d", fn("whatever"), $key)
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"
$map = {0 => "a", 1 => "b", 2 => "c",}
$key = 42 # should be out of scope
forkv $key, $val in $map {
class foo($x) {
$result = "hello " + $x
}
include foo($val) as included
$s = fmt.printf("%s is %d", $included.result, $key)
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"
$map = {0 => "a", 1 => "b", 2 => "c",}
$word = "hello"
forkv $key, $val in $map {
class foo($x) {
$result = $word + " " + $x
}
include foo($val) as included
$s = fmt.printf("%s is %d", $included.result, $key)
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"
$map = {0 => "a", 1 => "b", 2 => "c",}
$key = 42 # should be out of scope
forkv $key, $val in $map {
class foo($x) {
$result = "hello " + $val
}
include foo("whatever") as included
$s = fmt.printf("%s is %d", $included.result, $key)
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"
$map = {0 => "a", 1 => "b", 2 => "c",}
$word = "hello"
forkv $key, $val in $map {
class foo($x) {
$result = $word + " " + $val
}
include foo("whatever") as included
$s = fmt.printf("%s is %d", $included.result, $key)
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"
$map = {0 => "a", 1 => "b", 2 => "c",}
$key = 42 # should be out of scope
forkv $key, $val in $map {
class foo($x) {
$result = "hello" + $value
}
include foo("whatever") as included
$value = " " + $val
$s = fmt.printf("%s is %d", $included.result, $key)
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"
$map = {0 => "a", 1 => "b", 2 => "c",}
$word = "hello"
forkv $key, $val in $map {
class foo($x) {
$result = $word + $value
}
include foo("whatever") as included
$value = " " + $val
$s = fmt.printf("%s is %d", $included.result, $key)
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"
$map1 = {0 => "a", 1 => "b", 2 => "c",}
$map2 = {"x" => 42, "y" => 13, "z" => -4,}
forkv $key1, $val1 in $map1 {
forkv $key2, $val2 in $map2 {
$s = fmt.printf("%s is %d", $val1, $val2)
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"
$map0 = {0 => "a", 1 => "b", 2 => "c",}
$map1 = {0 => "d", 1 => "e", 2 => "f",}
$map2 = {0 => "g", 1 => "h", 2 => "i",}
$map3 = {0 => "j", 1 => "k", 2 => "l",}
$map = {0 => $map0, 1 => $map1, 2 => $map2, 3 => $map3,}
forkv $key, $val in $map {
forkv $i, $v in $val {
$s = fmt.printf("%s is %d", $v, $i+$key)
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"
$map = {0 => "a",}
forkv $key, $val in $map {
$fn = func() {
"hello " + $val
}
$s = $fn()
test [$s,] {}
}
-- OUTPUT --
Vertex: test[hello a]

View File

@@ -0,0 +1,17 @@
-- main.mcl --
import "fmt"
$map = {0 => "a",}
forkv $key, $val in $map {
func fn() {
"hello " + $val
}
$s = fn()
test [$s,] {}
}
-- OUTPUT --
Vertex: test[hello a]

View File

@@ -0,0 +1,41 @@
-- main.mcl --
import "fmt"
$map1 = {0 => "a", 1 => "b", 2 => "c",}
$map2 = {0 => "x", 1 => "y", 2 => "z",}
$word = "hello"
forkv $key1, $val1 in $map1 {
forkv $key2, $val2 in $map2 {
class foo($x, $y) {
$result = "hello " + $x + $y + $val1 + $val2
$result1 = $x + $val1
$result2 = $y + $val2
}
include foo($val1, $val2) as included
$s = fmt.printf("%s is {%d,%d}", $included.result, $key1, $key2)
$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"
$map1 = {0 => "a", 1 => "b", 2 => "c",}
$map2 = {0 => "x", 1 => "y", 2 => "z",}
$word = "hello"
forkv $key1, $val1 in $map1 {
class foo($x, $y) {
$result = "hello " + $x + $y + $val1
$result1 = $x + $val1
}
forkv $key2, $val2 in $map2 {
include foo($val1, $val2) as included
$s = fmt.printf("%s is {%d,%d}", $included.result, $key1, $key2)
$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"
$map = {0 => "a", 1 => "b", 2 => "c",}
forkv $i, $x in $map {
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"
$map = {0 => "a", 1 => "b", 2 => "c",}
$key = 42 # should be out of scope
forkv $key, $val in $map {
class foo() {
test [$val + fmt.printf("%d", $key),] {}
}
include foo() # as included
#$s = fmt.printf("%s is %d", $included.result, $key)
#test [$s,] {}
}
-- OUTPUT --
Vertex: test[a0]
Vertex: test[b1]
Vertex: test[c2]

View File

@@ -0,0 +1,23 @@
-- main.mcl --
import "fmt"
$map = {0 => "a", 1 => "b", 2 => "c",}
$key = 42 # should be out of scope
forkv $key, $val in $map {
class foo($x) {
$result = "hello " + $x
test [$result,] {}
}
include foo($val) # as included
#$s = fmt.printf("%s is %d", $included.result, $key)
#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"
$map = {0 => "a", 1 => "b", 2 => "c",}
$key = 42 # should be out of scope
forkv $key, $val in $map {
class foo($x) {
func result($s) {
$s + $x + $val
}
}
include foo($val) as included
$s = fmt.printf("%s is %d", included.result($val), $key)
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"
$map = {0 => "a", 1 => "b", 2 => "c",}
$key = 42 # should be out of scope
forkv $key, $val in $map {
class foo($x) {
$result = "hello" + $x + $val
}
include foo($val) 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, $key, $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"
$map = {0 => "a", 1 => "b", 2 => "c",}
$key = 42 # should be out of scope
forkv $key, $val in $map {
class foo($x) {
$result = func($s) {
$s + $x + $val
}
}
include foo($val) as included
$s = fmt.printf("%s is %d", $included.result($val), $key)
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"
$map = {0 => "a", 1 => "b", 2 => "c",}
$key = 42 # should be out of scope
forkv $key, $val in $map {
class foo() {
$result = "hello " + $val + fmt.printf("%d", $key)
test [$result,] {}
}
include foo() # as included
#$s = fmt.printf("%s is %d", $included.result, $key)
#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"
$map1 = {0 => "a", 1 => "b", 2 => "c",}
$map2 = {0 => "x", 1 => "y", 2 => "z",}
$word = "hello"
class foo($x, $y) {
$result = "hello " + $x + $y
}
forkv $key1, $val1 in $map1 {
forkv $key2, $val2 in $map2 {
include foo($val1, $val2) as included
$s = fmt.printf("%s is {%d,%d}", $included.result, $key1, $key2)
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"
$map = {0 => "a", 1 => "b", 2 => "c",}
#$key = 42 # should be out of scope
forkv $key, $val in $map {
class foo($x) {
#$result = "hello" + $x + $val # harder
#$result = $val # works
#$result = $x # works
$resultx = "hello" + $x # harder
#$result = "hello" + $val # harder
#$result = $x + $val # harder
}
include foo($val)# 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", $val, $key, $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"
$map = {0 => "a", 1 => "b", 2 => "c",}
$key = 42 # should be out of scope
forkv $key, $val in $map {
class foo($x) {
$result = func($y1) {
"hello" + $x + $val + $y1
}
}
include foo($val) 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("!"), $key, $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"
$map = {0 => "a", 1 => "b", 2 => "c",}
class foo($x) {
$result = "hello " + $x
}
forkv $key, $val in $map {
include foo($val) as included
$s = fmt.printf("%s is %d", $included.result, $key)
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"
$map = {0 => "a", 1 => "b", 2 => "c",}
class foo($x) {
$result = "hello " + fmt.printf("%d", $x)
}
forkv $key, $val in $map {
include foo($key) as included
$s = fmt.printf("%s is %d", $included.result, $key)
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"
$map = {0 => "a", 1 => "b", 2 => "c",}
forkv $key, $val in $map {
$foo = $key # does nothing
}
-- OUTPUT --

View File

@@ -0,0 +1,24 @@
-- main.mcl --
import "fmt"
$map = {0 => "a", 1 => "b", 2 => "c",}
import "thing.mcl" # works
forkv $key, $val in $map {
# 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", $val, $key, $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"
$map = {0 => "a", 1 => "b", 2 => "c",}
import "thing.mcl" # works
forkv $key, $val in $map {
# 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($key) as usefoo
$x = 42 + $usefoo.out
$s = fmt.printf("%s is %d = %d", $val, $key, $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"
$map = {0 => "a", 1 => "b", 2 => "c",}
#import "thing.mcl" # works
forkv $key, $val in $map {
# 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", $val, $key, $x)
test [$s,] {}
}
-- thing.mcl --
$inside = 13
-- OUTPUT --
# err: errInit: a StmtImport can't be contained inside a StmtForKV

View File

@@ -159,6 +159,11 @@
lval.str = yylex.Text() lval.str = yylex.Text()
return FOR return FOR
} }
/forkv/ {
yylex.pos(lval) // our pos
lval.str = yylex.Text()
return FORKV
}
/\->/ { /\->/ {
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 FOR %token IF ELSE FOR FORKV
%token BOOL STRING INTEGER FLOAT %token BOOL STRING INTEGER FLOAT
%token EQUALS DOLLAR %token EQUALS DOLLAR
%token COMMA COLON SEMICOLON %token COMMA COLON SEMICOLON
@@ -228,6 +228,18 @@ stmt:
} }
locate(yylex, $1, yyDollar[len(yyDollar)-1], $$.stmt) locate(yylex, $1, yyDollar[len(yyDollar)-1], $$.stmt)
} }
// iterate over maps
// `forkv $key, $val in $map { <body> }`
| FORKV var_identifier COMMA var_identifier IN expr OPEN_CURLY prog CLOSE_CURLY
{
$$.stmt = &ast.StmtForKV{
Key: $2.str, // no $ prefix
Val: $4.str, // no $ prefix
Expr: $6.expr, // XXX: name this Map ?
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> }`