diff --git a/lang/ast/structs.go b/lang/ast/structs.go index a02f8d74..f366ec23 100644 --- a/lang/ast/structs.go +++ b/lang/ast/structs.go @@ -33,6 +33,7 @@ package ast import ( "bytes" + "context" "fmt" "reflect" "sort" @@ -45,7 +46,9 @@ import ( "github.com/purpleidea/mgmt/lang/core" "github.com/purpleidea/mgmt/lang/embedded" "github.com/purpleidea/mgmt/lang/funcs" + "github.com/purpleidea/mgmt/lang/funcs/ref" "github.com/purpleidea/mgmt/lang/funcs/structs" + "github.com/purpleidea/mgmt/lang/funcs/txn" "github.com/purpleidea/mgmt/lang/inputs" "github.com/purpleidea/mgmt/lang/interfaces" "github.com/purpleidea/mgmt/lang/types" @@ -9474,7 +9477,6 @@ func (obj *ExprFunc) Copy() (interfaces.Expr, error) { // 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. -// XXX: do we need to add ordering around named args, eg: obj.Args Name strings? func (obj *ExprFunc) Ordering(produces map[string]interfaces.Node) (*pgraph.Graph, map[interfaces.Node]string, error) { graph, err := pgraph.NewGraph("ordering") if err != nil { @@ -9502,7 +9504,6 @@ func (obj *ExprFunc) Ordering(produces map[string]interfaces.Node) (*pgraph.Grap cons := make(map[interfaces.Node]string) - // XXX: do we need ordering for other aspects of ExprFunc ? if obj.Body != nil { g, c, err := obj.Body.Ordering(newProduces) if err != nil { @@ -9851,6 +9852,11 @@ func (obj *ExprFunc) Graph(env *interfaces.Env) (*pgraph.Graph, interfaces.Func, var funcValueFunc interfaces.Func if obj.Body != nil { + f := func(ctx context.Context, args []types.Value) (types.Value, error) { + // XXX: Find a way to exercise this function if possible. + //return nil, funcs.ErrCantSpeculate + return nil, fmt.Errorf("not implemented") + } funcValueFunc = structs.FuncValueToConstFunc(&full.FuncValue{ V: func(innerTxn interfaces.Txn, args []interfaces.Func) (interfaces.Func, error) { // Extend the environment with the arguments. @@ -9888,13 +9894,27 @@ func (obj *ExprFunc) Graph(env *interfaces.Env) (*pgraph.Graph, interfaces.Func, return bodyFunc, nil }, + F: f, T: obj.typ, }) } else if obj.Function != nil { + // Build this "callable" version in case it's available and we + // can use that directly. We don't need to copy it because we + // expect anything that is Callable to be stateless, and so it + // can use the same function call for every instantiation of it. + var fn interfaces.FuncSig + callableFunc, ok := obj.function.(interfaces.CallableFunc) + if ok { + // XXX: this might be dead code, how do we exercise it? + // If the function is callable then the surrounding + // ExprCall will produce a graph containing this func + // instead of calling ExprFunc.Graph(). + fn = callableFunc.Call + } + // obj.function is a node which transforms input values into // an output value, but we need to construct a node which takes no // inputs and produces a FuncValue, so we need to wrap it. - funcValueFunc = structs.FuncValueToConstFunc(&full.FuncValue{ V: func(txn interfaces.Txn, args []interfaces.Func) (interfaces.Func, error) { // Copy obj.function so that the underlying ExprFunc.function gets @@ -9919,6 +9939,7 @@ func (obj *ExprFunc) Graph(env *interfaces.Env) (*pgraph.Graph, interfaces.Func, } return valueTransformingFunc, nil }, + F: fn, T: obj.typ, }) } else /* len(obj.Values) > 0 */ { @@ -9967,13 +9988,122 @@ func (obj *ExprFunc) SetValue(value types.Value) error { // This particular value is always known since it is a constant. func (obj *ExprFunc) Value() (types.Value, error) { // Don't panic because we call Value speculatively for partial values! - // XXX: Not implemented - return nil, fmt.Errorf("error: ExprFunc does not store its latest value because resources don't yet have function fields") - //// TODO: implement speculative value lookup (if not already sufficient) - //return &full.FuncValue{ - // V: obj.V, - // T: obj.typ, - //}, nil + //return nil, fmt.Errorf("error: ExprFunc does not store its latest value because resources don't yet have function fields") + + if obj.Body != nil { + // We can only return a Value if we know the value of all the + // ExprParams. We don't have an environment, so this is only + // possible if there are no ExprParams at all. + // XXX: If we add in EnvValue as an arg, can we change this up? + if err := checkParamScope(obj, make(map[interfaces.Expr]struct{})); err != nil { + // return the sentinel value + return nil, funcs.ErrCantSpeculate + } + + f := func(ctx context.Context, args []types.Value) (types.Value, error) { + // TODO: make TestAstFunc1/shape8.txtar better... + //extendedValueEnv := interfaces.EmptyValueEnv() // TODO: add me? + //for _, x := range obj.Args { + // extendedValueEnv[???] = ??? + //} + + // XXX: Find a way to exercise this function if possible. + // chained-returned-funcs.txtar will error if we use: + //return nil, fmt.Errorf("not implemented") + return nil, funcs.ErrCantSpeculate + } + + return &full.FuncValue{ + V: func(innerTxn interfaces.Txn, args []interfaces.Func) (interfaces.Func, error) { + // There are no ExprParams, so we start with the empty environment. + // Extend that environment with the arguments. + extendedEnv := interfaces.EmptyEnv() + //extendedEnv := make(map[string]interfaces.Func) + for i := range obj.Args { + if args[i] == nil { + // XXX: speculation error? + return nil, fmt.Errorf("programming error?") + } + if len(obj.params) <= i { + // XXX: speculation error? + return nil, fmt.Errorf("programming error?") + } + param := obj.params[i] + if param == nil || param.envKey == nil { + // XXX: speculation error? + return nil, fmt.Errorf("programming error?") + } + + extendedEnv.Variables[param.envKey] = &interfaces.FuncSingleton{ + MakeFunc: func() (*pgraph.Graph, interfaces.Func, error) { + f := args[i] + g, err := pgraph.NewGraph("g") + if err != nil { + return nil, nil, err + } + g.AddVertex(f) + return g, f, nil + }, + } + } + + // Create a subgraph from the lambda's body, instantiating the + // lambda's parameters with the args and the other variables + // with the nodes in the captured environment. + subgraph, bodyFunc, err := obj.Body.Graph(extendedEnv) + if err != nil { + return nil, errwrap.Wrapf(err, "could not create the lambda body's subgraph") + } + + innerTxn.AddGraph(subgraph) + + return bodyFunc, nil + }, + F: f, + T: obj.typ, + }, nil + + } else if obj.Function != nil { + copyFunc := func() interfaces.Func { + copyableFunc, isCopyableFunc := obj.function.(interfaces.CopyableFunc) + if obj.function == nil || !isCopyableFunc { + return obj.Function() // force re-build a new pointer here! + } + + // is copyable! + return copyableFunc.Copy() + } + + // Instead of passing in the obj.function, we instead pass in a + // builder function so that this can use that inside of the + // *full.FuncValue implementation to make new functions when it + // gets called. We'll need more than one so they're not the same + // pointer! + return structs.FuncToFullFuncValue(copyFunc, obj.typ), nil + } + // else if /* len(obj.Values) > 0 */ + + // XXX: It's unclear if the below code in this function is correct or + // even tested. + + // polymorphic case: figure out which one has the correct type and wrap + // it in a full.FuncValue. + + index, err := langUtil.FnMatch(obj.typ, obj.Values) + if err != nil { + // programming error + // since type checking succeeded at this point, there should only be one match + return nil, errwrap.Wrapf(err, "multiple matches found") + } + + simpleFn := obj.Values[index] // *types.FuncValue + simpleFn.T = obj.typ // ensure the precise type is set/known + + return &full.FuncValue{ + V: nil, // XXX: do we need to implement this too? + F: simpleFn.V, // XXX: is this correct? + T: obj.typ, + }, nil } // ExprCall is a representation of a function call. This does not represent the @@ -10737,13 +10867,6 @@ func (obj *ExprCall) Graph(env *interfaces.Env) (*pgraph.Graph, interfaces.Func, return nil, nil, errwrap.Wrapf(err, "could not get the type of the function") } - // Find the vertex which produces the FuncValue. - g, funcValueFunc, err := obj.funcValueFunc(env) - if err != nil { - return nil, nil, err - } - graph.AddGraph(g) - // Loop over the arguments, add them to the graph, but do _not_ connect them // to the function vertex. Instead, each time the call vertex (which we // create below) receives a FuncValue from the function node, it creates the @@ -10758,6 +10881,50 @@ func (obj *ExprCall) Graph(env *interfaces.Env) (*pgraph.Graph, interfaces.Func, argFuncs = append(argFuncs, argFunc) } + // Speculate early, in an attempt to get a simpler graph shape. + //exprFunc, ok := obj.expr.(*ExprFunc) + // XXX: Does this need to be .Pure for it to be allowed? + //canSpeculate := !ok || exprFunc.function == nil || (exprFunc.function.Info().Fast && exprFunc.function.Info().Spec) + canSpeculate := true // XXX: use the magic Info fields? + exprValue, err := obj.expr.Value() + exprFuncValue, ok := exprValue.(*full.FuncValue) + if err == nil && ok && canSpeculate { + txn := (&txn.GraphTxn{ + GraphAPI: (&txn.Graph{ + Debug: obj.data.Debug, + Logf: func(format string, v ...interface{}) { + obj.data.Logf(format, v...) + }, + }).Init(), + Lock: func() {}, + Unlock: func() {}, + RefCount: (&ref.Count{}).Init(), + }).Init() + txn.AddGraph(graph) // add all of the graphs so far... + + outputFunc, err := exprFuncValue.CallWithFuncs(txn, argFuncs) + if err != nil { + return nil, nil, errwrap.Wrapf(err, "could not construct the static graph for a function call") + } + txn.AddVertex(outputFunc) + + if err := txn.Commit(); err != nil { // Must Commit after txn.AddGraph(...) + return nil, nil, err + } + + return txn.Graph(), outputFunc, nil + } else if err != nil && ok && canSpeculate && err != funcs.ErrCantSpeculate { + // This is a permanent error, not a temporary speculation error. + //return nil, nil, err // XXX: Consider adding this... + } + + // Find the vertex which produces the FuncValue. + g, funcValueFunc, err := obj.funcValueFunc(env) + if err != nil { + return nil, nil, err + } + graph.AddGraph(g) + // Add a vertex for the call itself. edgeName := structs.CallFuncArgNameFunction callFunc := &structs.CallFunc{ @@ -10867,7 +11034,7 @@ func (obj *ExprCall) SetValue(value types.Value) error { if err := obj.typ.Cmp(value.Type()); err != nil { return err } - obj.V = value + obj.V = value // XXX: is this useful or a good idea? return nil } @@ -10875,13 +11042,46 @@ func (obj *ExprCall) SetValue(value types.Value) error { // usually only be valid once the engine has run and values have been produced. // This might get called speculatively (early) during unification to learn more. // It is often unlikely that this kind of speculative execution finds something. -// This particular implementation of the function returns the previously stored -// and cached value as received by SetValue. +// This particular implementation will run a function if all of the needed +// values are known. This is necessary for getting the efficient graph shape of +// ExprCall. func (obj *ExprCall) Value() (types.Value, error) { - if obj.V == nil { + if obj.V != nil { // XXX: is this useful or a good idea? + return obj.V, nil + } + + if obj.expr == nil { return nil, fmt.Errorf("func value does not yet exist") } - return obj.V, nil + + // Speculatively call Value() on obj.expr and each arg. + // XXX: Should we check obj.expr.(*ExprFunc).Info.Pure here ? + value, err := obj.expr.Value() // speculative + if err != nil { + return nil, err + } + + funcValue, ok := value.(*full.FuncValue) + if !ok { + return nil, fmt.Errorf("not a func value") + } + + args := []types.Value{} + for _, arg := range obj.Args { // []interfaces.Expr + a, err := arg.Value() // speculative + if err != nil { + return nil, err + } + args = append(args, a) + } + + // We now have a *full.FuncValue and a []types.Value. We can't call the + // existing: + // Call(..., []interfaces.Func) interfaces.Func` method on the + // FuncValue, we need a speculative: + // Call(..., []types.Value) types.Value + // method. + return funcValue.CallWithValues(context.TODO(), args) } // ExprVar is a representation of a variable lookup. It returns the expression @@ -11370,6 +11570,11 @@ func (obj *ExprParam) SetValue(value types.Value) error { // usually only be valid once the engine has run and values have been produced. // This might get called speculatively (early) during unification to learn more. func (obj *ExprParam) Value() (types.Value, error) { + // XXX: if value env is an arg in Expr.Value(...) + //value, exists := valueEnv[obj] + //if exists { + // return value, nil + //} return nil, fmt.Errorf("no value for ExprParam") } diff --git a/lang/ast/util.go b/lang/ast/util.go index 59fe658d..7507f973 100644 --- a/lang/ast/util.go +++ b/lang/ast/util.go @@ -321,6 +321,11 @@ func getScope(node interfaces.Expr) (*interfaces.Scope, error) { return expr.scope, nil case *ExprVar: return expr.scope, nil + //case *ExprParam: + //case *ExprIterated: + //case *ExprPoly: + //case *ExprTopLevel: + //case *ExprSingleton: case *ExprIf: return expr.scope, nil @@ -329,6 +334,124 @@ func getScope(node interfaces.Expr) (*interfaces.Scope, error) { } } +// CheckParamScope ensures that only the specified ExprParams are free in the +// expression. It is used for graph shape function speculation. This could have +// been an addition to the interfaces.Expr interface, but since it's mostly +// iteration, it felt cleaner like this. +// TODO: Can we replace this with a call to Apply instead. +func checkParamScope(node interfaces.Expr, freeVars map[interfaces.Expr]struct{}) error { + switch obj := node.(type) { + + case *ExprBool: + return nil + + case *ExprStr: + return nil + + case *ExprInt: + return nil + + case *ExprFloat: + return nil + + case *ExprList: + for _, x := range obj.Elements { + if err := checkParamScope(x, freeVars); err != nil { + return err + } + } + return nil + + case *ExprMap: + for _, x := range obj.KVs { + if err := checkParamScope(x.Key, freeVars); err != nil { + return err + } + if err := checkParamScope(x.Val, freeVars); err != nil { + return err + } + } + return nil + + case *ExprStruct: + for _, x := range obj.Fields { + if err := checkParamScope(x.Value, freeVars); err != nil { + return err + } + } + return nil + + case *ExprFunc: + if obj.Body != nil { + newFreeVars := make(map[interfaces.Expr]struct{}) + for k, v := range freeVars { + newFreeVars[k] = v + } + for _, param := range obj.params { + newFreeVars[param] = struct{}{} + } + + if err := checkParamScope(obj.Body, newFreeVars); err != nil { + return err + } + } + // XXX: Do we need to do anything for obj.Function ? + // XXX: Do we need to do anything for obj.Values ? + return nil + + case *ExprCall: + if obj.expr != nil { + if err := checkParamScope(obj.expr, freeVars); err != nil { + return err + } + } + for _, x := range obj.Args { + if err := checkParamScope(x, freeVars); err != nil { + return err + } + } + return nil + + case *ExprVar: + // XXX: is this still correct? + target := obj.scope.Variables[obj.Name] + return checkParamScope(target, freeVars) + + case *ExprParam: + if _, exists := freeVars[obj]; !exists { + return fmt.Errorf("the body uses parameter $%s", obj.Name) + } + return nil + + case *ExprIterated: + return checkParamScope(obj.Definition, freeVars) // XXX: is this what we want? + + case *ExprPoly: + panic("checkParamScope(ExprPoly): should not happen, ExprVar should replace ExprPoly with a copy of its definition before calling checkParamScope") + + case *ExprTopLevel: + return checkParamScope(obj.Definition, freeVars) + + case *ExprSingleton: + return checkParamScope(obj.Definition, freeVars) + + case *ExprIf: + if err := checkParamScope(obj.Condition, freeVars); err != nil { + return err + } + if err := checkParamScope(obj.ThenBranch, freeVars); err != nil { + return err + } + if err := checkParamScope(obj.ElseBranch, freeVars); err != nil { + return err + } + return nil + + default: + return fmt.Errorf("unexpected: %+v", node) + } +} + // trueCallee is a helper function because ExprTopLevel and ExprSingleton are // sometimes added around builtins. This makes it difficult for the type checker // to check if a particular builtin is the callee or not. This function removes diff --git a/lang/core/collect.go b/lang/core/collect.go index fb4085b0..8533aff7 100644 --- a/lang/core/collect.go +++ b/lang/core/collect.go @@ -282,6 +282,8 @@ func (obj *CollectFunc) Info() *interfaces.Info { return &interfaces.Info{ Pure: false, Memo: false, + Fast: false, + Spec: false, Sig: sig, Err: obj.Validate(), } @@ -389,6 +391,9 @@ func (obj *CollectFunc) Stream(ctx context.Context) error { // to do so at this time. This was previously getValue which gets the value // we're looking for. func (obj *CollectFunc) Call(ctx context.Context, args []types.Value) (types.Value, error) { + if len(args) < 2 { + return nil, fmt.Errorf("not enough args") + } kind := args[0].Str() if kind == "" { return nil, fmt.Errorf("resource kind is empty") @@ -448,6 +453,9 @@ func (obj *CollectFunc) Call(ctx context.Context, args []types.Value) (types.Val } } + if obj.init == nil { + return nil, funcs.ErrCantSpeculate + } resOutput, err := obj.init.World.ResCollect(ctx, filters) if err != nil { return nil, err diff --git a/lang/core/concat.go b/lang/core/concat.go index 749bdcbf..4c11d84c 100644 --- a/lang/core/concat.go +++ b/lang/core/concat.go @@ -44,6 +44,12 @@ const ( func init() { simple.Register(ConcatFuncName, &simple.Scaffold{ + I: &simple.Info{ + Pure: true, + Memo: true, + Fast: true, + Spec: true, + }, T: types.NewType("func(a str, b str) str"), F: Concat, }) diff --git a/lang/core/contains.go b/lang/core/contains.go index cbe66fa5..944799f7 100644 --- a/lang/core/contains.go +++ b/lang/core/contains.go @@ -44,6 +44,12 @@ const ( func init() { simple.Register(ContainsFuncName, &simple.Scaffold{ + I: &simple.Info{ + Pure: true, + Memo: true, + Fast: true, + Spec: true, + }, T: types.NewType("func(needle ?1, haystack []?1) bool"), F: Contains, }) diff --git a/lang/core/convert/format_bool.go b/lang/core/convert/format_bool.go index b7000049..ae74df79 100644 --- a/lang/core/convert/format_bool.go +++ b/lang/core/convert/format_bool.go @@ -39,6 +39,12 @@ import ( func init() { simple.ModuleRegister(ModuleName, "format_bool", &simple.Scaffold{ + I: &simple.Info{ + Pure: true, + Memo: true, + Fast: true, + Spec: true, + }, T: types.NewType("func(a bool) str"), F: FormatBool, }) diff --git a/lang/core/convert/parse_bool.go b/lang/core/convert/parse_bool.go index eeff5f72..760e2bb0 100644 --- a/lang/core/convert/parse_bool.go +++ b/lang/core/convert/parse_bool.go @@ -40,6 +40,12 @@ import ( func init() { simple.ModuleRegister(ModuleName, "parse_bool", &simple.Scaffold{ + I: &simple.Info{ + Pure: true, + Memo: true, + Fast: true, + Spec: true, + }, T: types.NewType("func(a str) bool"), F: ParseBool, }) diff --git a/lang/core/convert/to_float.go b/lang/core/convert/to_float.go index 44cc3e36..627114db 100644 --- a/lang/core/convert/to_float.go +++ b/lang/core/convert/to_float.go @@ -38,6 +38,12 @@ import ( func init() { simple.ModuleRegister(ModuleName, "to_float", &simple.Scaffold{ + I: &simple.Info{ + Pure: true, + Memo: true, + Fast: true, + Spec: true, + }, T: types.NewType("func(a int) float"), F: ToFloat, }) diff --git a/lang/core/convert/to_int.go b/lang/core/convert/to_int.go index 9894a25a..0e1bb375 100644 --- a/lang/core/convert/to_int.go +++ b/lang/core/convert/to_int.go @@ -38,6 +38,12 @@ import ( func init() { simple.ModuleRegister(ModuleName, "to_int", &simple.Scaffold{ + I: &simple.Info{ + Pure: true, + Memo: true, + Fast: true, + Spec: true, + }, T: types.NewType("func(a float) int"), F: ToInt, }) diff --git a/lang/core/convert/to_str.go b/lang/core/convert/to_str.go index 494bbf6c..d08e9943 100644 --- a/lang/core/convert/to_str.go +++ b/lang/core/convert/to_str.go @@ -39,6 +39,12 @@ import ( func init() { simple.ModuleRegister(ModuleName, "int_to_str", &simple.Scaffold{ + I: &simple.Info{ + Pure: true, + Memo: true, + Fast: true, + Spec: true, + }, T: types.NewType("func(a int) str"), F: IntToStr, }) diff --git a/lang/core/core_test.go b/lang/core/core_test.go index e79f3211..e0929106 100644 --- a/lang/core/core_test.go +++ b/lang/core/core_test.go @@ -62,8 +62,8 @@ func PureFuncExec(handle interfaces.Func, args []types.Value) (types.Value, erro return nil, fmt.Errorf("func is not pure") } // if function is expensive to run, we won't run it provisionally - if info.Slow { - return nil, fmt.Errorf("func is slow") + if !info.Fast { + return nil, fmt.Errorf("func is not fast") } sig := handle.Info().Sig diff --git a/lang/core/datetime/format.go b/lang/core/datetime/format.go index e90ded82..f475cc99 100644 --- a/lang/core/datetime/format.go +++ b/lang/core/datetime/format.go @@ -40,6 +40,12 @@ import ( func init() { simple.ModuleRegister(ModuleName, "format", &simple.Scaffold{ + I: &simple.Info{ + Pure: true, + Memo: true, + Fast: true, + Spec: true, + }, T: types.NewType("func(a int, b str) str"), F: Format, }) diff --git a/lang/core/datetime/hour.go b/lang/core/datetime/hour.go index 4635dbe6..335555b7 100644 --- a/lang/core/datetime/hour.go +++ b/lang/core/datetime/hour.go @@ -40,6 +40,12 @@ import ( func init() { simple.ModuleRegister(ModuleName, "hour", &simple.Scaffold{ + I: &simple.Info{ + Pure: true, + Memo: true, + Fast: true, + Spec: true, + }, T: types.NewType("func(a int) int"), F: Hour, }) diff --git a/lang/core/datetime/now_fact.go b/lang/core/datetime/now_fact.go index 2b0e784f..0aa90c6c 100644 --- a/lang/core/datetime/now_fact.go +++ b/lang/core/datetime/now_fact.go @@ -67,6 +67,8 @@ func (obj *DateTimeFact) String() string { // Info returns some static info about itself. func (obj *DateTimeFact) Info() *facts.Info { return &facts.Info{ + Pure: false, + Memo: false, Output: types.NewType("int"), } } diff --git a/lang/core/datetime/print.go b/lang/core/datetime/print.go index 3b247437..c971b9c5 100644 --- a/lang/core/datetime/print.go +++ b/lang/core/datetime/print.go @@ -41,6 +41,12 @@ import ( func init() { // FIXME: consider renaming this to printf, and add in a format string? simple.ModuleRegister(ModuleName, "print", &simple.Scaffold{ + I: &simple.Info{ + Pure: true, + Memo: true, + Fast: true, + Spec: true, + }, T: types.NewType("func(a int) str"), F: Print, }) diff --git a/lang/core/datetime/weekday.go b/lang/core/datetime/weekday.go index e0520133..98903146 100644 --- a/lang/core/datetime/weekday.go +++ b/lang/core/datetime/weekday.go @@ -41,6 +41,12 @@ import ( func init() { simple.ModuleRegister(ModuleName, "weekday", &simple.Scaffold{ + I: &simple.Info{ + Pure: true, + Memo: true, + Fast: true, + Spec: true, + }, T: types.NewType("func(a int) str"), F: Weekday, }) diff --git a/lang/core/deploy/abspath.go b/lang/core/deploy/abspath.go index c68d751a..6f0f308d 100644 --- a/lang/core/deploy/abspath.go +++ b/lang/core/deploy/abspath.go @@ -51,6 +51,8 @@ func init() { funcs.ModuleRegister(ModuleName, AbsPathFuncName, func() interfaces.Func { return &AbsPathFunc{} }) // must register the func and name } +var _ interfaces.DataFunc = &AbsPathFunc{} + // AbsPathFunc is a function that returns the absolute, full path in the deploy // from an input path that is relative to the calling file. If you pass it an // empty string, you'll just get the absolute deploy directory path that you're @@ -96,6 +98,8 @@ func (obj *AbsPathFunc) Info() *interfaces.Info { return &interfaces.Info{ Pure: false, // maybe false because the file contents can change Memo: false, + Fast: false, + Spec: false, Sig: types.NewType(fmt.Sprintf("func(%s str) str", absPathArgNamePath)), } } @@ -166,11 +170,26 @@ func (obj *AbsPathFunc) Stream(ctx context.Context) error { } } +// Copy is implemented so that the obj.built value is not lost if we copy this +// function. +func (obj *AbsPathFunc) Copy() interfaces.Func { + return &AbsPathFunc{ + init: obj.init, // likely gets overwritten anyways + data: obj.data, // needed because we don't call SetData twice + } +} + // Call this function with the input args and return the value if it is possible // to do so at this time. func (obj *AbsPathFunc) Call(ctx context.Context, args []types.Value) (types.Value, error) { + if len(args) < 1 { + return nil, fmt.Errorf("not enough args") + } path := args[0].Str() + if obj.data == nil { + return nil, funcs.ErrCantSpeculate + } p := strings.TrimSuffix(obj.data.Base, "/") if p == obj.data.Base { // didn't trim, so we fail // programming error diff --git a/lang/core/deploy/binary.go b/lang/core/deploy/binary.go index f9141076..c1cc0da8 100644 --- a/lang/core/deploy/binary.go +++ b/lang/core/deploy/binary.go @@ -39,6 +39,12 @@ import ( func init() { simple.ModuleRegister(ModuleName, "binary_path", &simple.Scaffold{ + I: &simple.Info{ + Pure: true, + Memo: false, + Fast: true, + Spec: false, + }, T: types.NewType("func() str"), F: BinaryPath, }) diff --git a/lang/core/deploy/bootstrap_packages.go b/lang/core/deploy/bootstrap_packages.go index 4fb88708..8b537789 100644 --- a/lang/core/deploy/bootstrap_packages.go +++ b/lang/core/deploy/bootstrap_packages.go @@ -40,6 +40,12 @@ import ( func init() { simple.ModuleRegister(ModuleName, "bootstrap_packages", &simple.Scaffold{ + I: &simple.Info{ + Pure: true, + Memo: true, + Fast: true, + Spec: true, + }, T: types.NewType("func(str) []str"), F: BootstrapPackages, }) diff --git a/lang/core/deploy/readfile.go b/lang/core/deploy/readfile.go index 3462ee72..515ae689 100644 --- a/lang/core/deploy/readfile.go +++ b/lang/core/deploy/readfile.go @@ -52,6 +52,8 @@ func init() { funcs.ModuleRegister(ModuleName, ReadFileFuncName, func() interfaces.Func { return &ReadFileFunc{} }) // must register the func and name } +var _ interfaces.DataFunc = &ReadFileFunc{} + // ReadFileFunc is a function that reads the full contents from a file in our // deploy. The file contents can only change with a new deploy, so this is // static. Please note that this is different from the readfile function in the @@ -97,6 +99,8 @@ func (obj *ReadFileFunc) Info() *interfaces.Info { return &interfaces.Info{ Pure: false, // maybe false because the file contents can change Memo: false, + Fast: false, + Spec: false, Sig: types.NewType(fmt.Sprintf("func(%s str) str", readFileArgNameFilename)), } } @@ -168,11 +172,26 @@ func (obj *ReadFileFunc) Stream(ctx context.Context) error { } } +// Copy is implemented so that the obj.built value is not lost if we copy this +// function. +func (obj *ReadFileFunc) Copy() interfaces.Func { + return &ReadFileFunc{ + init: obj.init, // likely gets overwritten anyways + data: obj.data, // needed because we don't call SetData twice + } +} + // Call this function with the input args and return the value if it is possible // to do so at this time. func (obj *ReadFileFunc) Call(ctx context.Context, args []types.Value) (types.Value, error) { + if len(args) < 1 { + return nil, fmt.Errorf("not enough args") + } filename := args[0].Str() + if obj.data == nil { + return nil, funcs.ErrCantSpeculate + } p := strings.TrimSuffix(obj.data.Base, "/") if p == obj.data.Base { // didn't trim, so we fail // programming error @@ -186,6 +205,9 @@ func (obj *ReadFileFunc) Call(ctx context.Context, args []types.Value) (types.Va } path += filename + if obj.init == nil || obj.data == nil { + return nil, funcs.ErrCantSpeculate + } fs, err := obj.init.World.Fs(obj.data.FsURI) // open the remote file system if err != nil { return nil, errwrap.Wrapf(err, "can't load code from file system `%s`", obj.data.FsURI) diff --git a/lang/core/deploy/readfileabs.go b/lang/core/deploy/readfileabs.go index 03d45f22..e8facab1 100644 --- a/lang/core/deploy/readfileabs.go +++ b/lang/core/deploy/readfileabs.go @@ -51,6 +51,8 @@ func init() { funcs.ModuleRegister(ModuleName, ReadFileAbsFuncName, func() interfaces.Func { return &ReadFileAbsFunc{} }) // must register the func and name } +var _ interfaces.DataFunc = &ReadFileAbsFunc{} + // ReadFileAbsFunc is a function that reads the full contents from a file in our // deploy. The file contents can only change with a new deploy, so this is // static. In particular, this takes an absolute path relative to the root @@ -97,6 +99,8 @@ func (obj *ReadFileAbsFunc) Info() *interfaces.Info { return &interfaces.Info{ Pure: false, // maybe false because the file contents can change Memo: false, + Fast: false, + Spec: false, Sig: types.NewType(fmt.Sprintf("func(%s str) str", readfileArgNameFilename)), } } @@ -168,11 +172,26 @@ func (obj *ReadFileAbsFunc) Stream(ctx context.Context) error { } } +// Copy is implemented so that the obj.built value is not lost if we copy this +// function. +func (obj *ReadFileAbsFunc) Copy() interfaces.Func { + return &ReadFileAbsFunc{ + init: obj.init, // likely gets overwritten anyways + data: obj.data, // needed because we don't call SetData twice + } +} + // Call this function with the input args and return the value if it is possible // to do so at this time. func (obj *ReadFileAbsFunc) Call(ctx context.Context, args []types.Value) (types.Value, error) { + if len(args) < 1 { + return nil, fmt.Errorf("not enough args") + } filename := args[0].Str() + if obj.init == nil || obj.data == nil { + return nil, funcs.ErrCantSpeculate + } fs, err := obj.init.World.Fs(obj.data.FsURI) // open the remote file system if err != nil { return nil, errwrap.Wrapf(err, "can't load code from file system `%s`", obj.data.FsURI) diff --git a/lang/core/embedded/provisioner/provisioner.go b/lang/core/embedded/provisioner/provisioner.go index 38adb815..1882e1db 100644 --- a/lang/core/embedded/provisioner/provisioner.go +++ b/lang/core/embedded/provisioner/provisioner.go @@ -524,6 +524,12 @@ func (obj *provisioner) Register(moduleName string) error { // Build a few separately... simple.ModuleRegister(moduleName, "cli_password", &simple.Scaffold{ + I: &simple.Info{ + Pure: false, + Memo: false, + Fast: false, + Spec: false, + }, T: types.NewType("func() str"), F: func(ctx context.Context, input []types.Value) (types.Value, error) { if obj.localArgs == nil { diff --git a/lang/core/example/answer.go b/lang/core/example/answer.go index 307ebe3e..f08cafdd 100644 --- a/lang/core/example/answer.go +++ b/lang/core/example/answer.go @@ -41,6 +41,12 @@ const Answer = 42 func init() { simple.ModuleRegister(ModuleName, "answer", &simple.Scaffold{ + I: &simple.Info{ + Pure: true, + Memo: true, + Fast: true, + Spec: true, + }, T: types.NewType("func() int"), F: TheAnswerToLifeTheUniverseAndEverything, }) diff --git a/lang/core/example/errorbool.go b/lang/core/example/errorbool.go index 61ae4170..b8785979 100644 --- a/lang/core/example/errorbool.go +++ b/lang/core/example/errorbool.go @@ -39,6 +39,12 @@ import ( func init() { simple.ModuleRegister(ModuleName, "errorbool", &simple.Scaffold{ + I: &simple.Info{ + Pure: false, // XXX: because it errors do we need to be be safer here? + Memo: false, + Fast: true, + Spec: false, + }, T: types.NewType("func(a bool) str"), F: ErrorBool, }) diff --git a/lang/core/example/flipflop_fact.go b/lang/core/example/flipflop_fact.go index 4d3e6500..bef1bbef 100644 --- a/lang/core/example/flipflop_fact.go +++ b/lang/core/example/flipflop_fact.go @@ -123,6 +123,9 @@ func (obj *FlipFlopFact) Stream(ctx context.Context) error { // Call this fact and return the value if it is possible to do so at this time. func (obj *FlipFlopFact) Call(ctx context.Context) (types.Value, error) { + if obj.mutex == nil { + return nil, facts.ErrCantSpeculate + } obj.mutex.Lock() // TODO: could be a read lock value := obj.value obj.mutex.Unlock() diff --git a/lang/core/example/int2str.go b/lang/core/example/int2str.go index a4fb0636..f9545fcf 100644 --- a/lang/core/example/int2str.go +++ b/lang/core/example/int2str.go @@ -39,6 +39,12 @@ import ( func init() { simple.ModuleRegister(ModuleName, "int2str", &simple.Scaffold{ + I: &simple.Info{ + Pure: true, + Memo: true, + Fast: true, + Spec: true, + }, T: types.NewType("func(a int) str"), F: Int2Str, }) diff --git a/lang/core/example/nested/hello_func.go b/lang/core/example/nested/hello_func.go index 4d47f7be..d042df68 100644 --- a/lang/core/example/nested/hello_func.go +++ b/lang/core/example/nested/hello_func.go @@ -39,6 +39,12 @@ import ( func init() { simple.ModuleRegister(coreexample.ModuleName+"/"+ModuleName, "hello", &simple.Scaffold{ + I: &simple.Info{ + Pure: true, + Memo: true, + Fast: true, + Spec: true, + }, T: types.NewType("func() str"), F: Hello, }) diff --git a/lang/core/example/plus.go b/lang/core/example/plus.go index 24bbc2f0..fb34bfe8 100644 --- a/lang/core/example/plus.go +++ b/lang/core/example/plus.go @@ -38,6 +38,12 @@ import ( func init() { simple.ModuleRegister(ModuleName, "plus", &simple.Scaffold{ + I: &simple.Info{ + Pure: true, + Memo: true, + Fast: true, + Spec: true, + }, T: types.NewType("func(y str, z str) str"), F: Plus, }) diff --git a/lang/core/example/str2int.go b/lang/core/example/str2int.go index 90b2459c..cfee08c4 100644 --- a/lang/core/example/str2int.go +++ b/lang/core/example/str2int.go @@ -39,6 +39,12 @@ import ( func init() { simple.ModuleRegister(ModuleName, "str2int", &simple.Scaffold{ + I: &simple.Info{ + Pure: true, + Memo: true, + Fast: true, + Spec: true, + }, T: types.NewType("func(a str) int"), F: Str2Int, }) diff --git a/lang/core/example/vumeter.go b/lang/core/example/vumeter.go index dad1b02d..436d5eff 100644 --- a/lang/core/example/vumeter.go +++ b/lang/core/example/vumeter.go @@ -131,8 +131,10 @@ func (obj *VUMeterFunc) Validate() error { // Info returns some static info about itself. func (obj *VUMeterFunc) Info() *interfaces.Info { return &interfaces.Info{ - Pure: true, + Pure: false, Memo: false, + Fast: false, + Spec: false, Sig: types.NewType(fmt.Sprintf("func(%s str, %s int, %s float) str", vuMeterArgNameSymbol, vuMeterArgNameMultiplier, vuMeterArgNamePeak)), } } @@ -212,6 +214,9 @@ func (obj *VUMeterFunc) Stream(ctx context.Context) error { // Call this function with the input args and return the value if it is possible // to do so at this time. func (obj *VUMeterFunc) Call(ctx context.Context, args []types.Value) (types.Value, error) { + if len(args) < 3 { + return nil, fmt.Errorf("not enough args") + } symbol := args[0].Str() multiplier := args[1].Int() peak := args[2].Float() diff --git a/lang/core/fmt/printf.go b/lang/core/fmt/printf.go index 8d92dde2..9bb6ad3f 100644 --- a/lang/core/fmt/printf.go +++ b/lang/core/fmt/printf.go @@ -98,7 +98,10 @@ type PrintfFunc struct { // String returns a simple name for this function. This is needed so this struct // can satisfy the pgraph.Vertex interface. func (obj *PrintfFunc) String() string { - return fmt.Sprintf("%s@%p", PrintfFuncName, obj) // be more unique! + if obj.Type != nil { + return fmt.Sprintf("%s: %s", PrintfFuncName, obj.Type) + } + return PrintfFuncName } // ArgGen returns the Nth arg name for this function. @@ -295,7 +298,9 @@ func (obj *PrintfFunc) Info() *interfaces.Info { // getting them from FuncInfer, and not from here. (During unification!) return &interfaces.Info{ Pure: true, - Memo: false, + Memo: true, + Fast: true, + Spec: true, Sig: obj.Type, Err: obj.Validate(), } @@ -367,6 +372,9 @@ func (obj *PrintfFunc) Copy() interfaces.Func { // Call this function with the input args and return the value if it is possible // to do so at this time. func (obj *PrintfFunc) Call(ctx context.Context, args []types.Value) (types.Value, error) { + if len(args) < 1 { + return nil, fmt.Errorf("not enough args") + } format := args[0].Str() values := []types.Value{} diff --git a/lang/core/golang/template.go b/lang/core/golang/template.go index 05c37ce6..c583c814 100644 --- a/lang/core/golang/template.go +++ b/lang/core/golang/template.go @@ -202,8 +202,10 @@ func (obj *TemplateFunc) Info() *interfaces.Info { sig = obj.sig() // helper } return &interfaces.Info{ - Pure: true, + Pure: false, // contents of a template might not be pure Memo: false, + Fast: false, + Spec: false, Sig: sig, Err: obj.Validate(), } @@ -407,6 +409,9 @@ func (obj *TemplateFunc) Copy() interfaces.Func { // Call this function with the input args and return the value if it is possible // to do so at this time. func (obj *TemplateFunc) Call(ctx context.Context, args []types.Value) (types.Value, error) { + if len(args) < 1 { + return nil, fmt.Errorf("not enough args") + } tmpl := args[0].Str() var vars types.Value // nil @@ -414,6 +419,9 @@ func (obj *TemplateFunc) Call(ctx context.Context, args []types.Value) (types.Va vars = args[1] } + if obj.init == nil { + return nil, funcs.ErrCantSpeculate + } result, err := obj.run(ctx, tmpl, vars) if err != nil { return nil, err // no errwrap needed b/c helper func diff --git a/lang/core/history.go b/lang/core/history.go index 28e51024..ff04db75 100644 --- a/lang/core/history.go +++ b/lang/core/history.go @@ -158,6 +158,8 @@ func (obj *HistoryFunc) Info() *interfaces.Info { return &interfaces.Info{ Pure: false, // definitely false Memo: false, + Fast: false, + Spec: false, Sig: obj.sig(), // helper Err: obj.Validate(), } diff --git a/lang/core/iter/filter.go b/lang/core/iter/filter.go index fc771dad..17c72365 100644 --- a/lang/core/iter/filter.go +++ b/lang/core/iter/filter.go @@ -142,8 +142,10 @@ func (obj *FilterFunc) Validate() error { // will return correct data. func (obj *FilterFunc) Info() *interfaces.Info { return &interfaces.Info{ - Pure: false, // TODO: what if the input function isn't pure? + Pure: false, // XXX: what if the input function isn't pure? Memo: false, + Fast: false, + Spec: false, Sig: obj.sig(), // helper Err: obj.Validate(), } @@ -412,9 +414,9 @@ func (obj *FilterFunc) replaceSubGraph(subgraphInput interfaces.Func) error { ) obj.init.Txn.AddVertex(inputElemFunc) - outputElemFunc, err := obj.lastFuncValue.Call(obj.init.Txn, []interfaces.Func{inputElemFunc}) + outputElemFunc, err := obj.lastFuncValue.CallWithFuncs(obj.init.Txn, []interfaces.Func{inputElemFunc}) if err != nil { - return errwrap.Wrapf(err, "could not call obj.lastFuncValue.Call()") + return errwrap.Wrapf(err, "could not call obj.lastFuncValue.CallWithFuncs()") } obj.init.Txn.AddEdge(subgraphInput, inputElemFunc, &interfaces.FuncEdge{ diff --git a/lang/core/iter/map.go b/lang/core/iter/map.go index b03f39b0..62d015d7 100644 --- a/lang/core/iter/map.go +++ b/lang/core/iter/map.go @@ -204,8 +204,10 @@ func (obj *MapFunc) Validate() error { // will return correct data. func (obj *MapFunc) Info() *interfaces.Info { return &interfaces.Info{ - Pure: false, // TODO: what if the input function isn't pure? + Pure: false, // XXX: what if the input function isn't pure? Memo: false, + Fast: false, + Spec: false, Sig: obj.sig(), // helper Err: obj.Validate(), } @@ -439,9 +441,9 @@ func (obj *MapFunc) replaceSubGraph(subgraphInput interfaces.Func) error { ) obj.init.Txn.AddVertex(inputElemFunc) - outputElemFunc, err := obj.lastFuncValue.Call(obj.init.Txn, []interfaces.Func{inputElemFunc}) + outputElemFunc, err := obj.lastFuncValue.CallWithFuncs(obj.init.Txn, []interfaces.Func{inputElemFunc}) if err != nil { - return errwrap.Wrapf(err, "could not call obj.lastFuncValue.Call()") + return errwrap.Wrapf(err, "could not call obj.lastFuncValue.CallWithFuncs()") } obj.init.Txn.AddEdge(subgraphInput, inputElemFunc, &interfaces.FuncEdge{ diff --git a/lang/core/iter/range.go b/lang/core/iter/range.go index 1a016cfc..748f5989 100644 --- a/lang/core/iter/range.go +++ b/lang/core/iter/range.go @@ -161,7 +161,9 @@ func (obj *RangeFunc) Validate() error { func (obj *RangeFunc) Info() *interfaces.Info { return &interfaces.Info{ Pure: true, - Memo: false, + Memo: true, + Fast: true, + Spec: true, Sig: obj.Type, Err: obj.Validate(), } diff --git a/lang/core/len.go b/lang/core/len.go index 36a88296..22437f36 100644 --- a/lang/core/len.go +++ b/lang/core/len.go @@ -39,6 +39,12 @@ import ( func init() { simple.Register("len", &simple.Scaffold{ + I: &simple.Info{ + Pure: true, + Memo: true, + Fast: true, + Spec: true, + }, T: types.NewType("func(?1) int"), // TODO: should we add support for struct or func lengths? C: simple.TypeMatch([]string{ diff --git a/lang/core/list/list_concat.go b/lang/core/list/list_concat.go index 3dc20256..bf01a5ae 100644 --- a/lang/core/list/list_concat.go +++ b/lang/core/list/list_concat.go @@ -158,6 +158,13 @@ func (obj *ListConcatFunc) Build(typ *types.Type) (*types.Type, error) { obj.Func = &wrapped.Func{ Name: obj.String(), + FuncInfo: &wrapped.Info{ + // TODO: dedup with below Info data + Pure: true, + Memo: true, + Fast: true, + Spec: true, + }, Type: typ, // .Copy(), } @@ -188,6 +195,7 @@ func (obj *ListConcatFunc) Copy() interfaces.Func { func (obj *ListConcatFunc) Call(ctx context.Context, args []types.Value) (types.Value, error) { values := []types.Value{} + // TODO: Could speculation pass in non-lists here and cause a panic? for _, x := range args { values = append(values, x.List()...) } @@ -217,7 +225,9 @@ func (obj *ListConcatFunc) Validate() error { func (obj *ListConcatFunc) Info() *interfaces.Info { return &interfaces.Info{ Pure: true, - Memo: false, + Memo: true, + Fast: true, + Spec: true, Sig: obj.sig(), // helper Err: obj.Validate(), } diff --git a/lang/core/list/list_lookup.go b/lang/core/list/list_lookup.go index bfef16d2..71b92bae 100644 --- a/lang/core/list/list_lookup.go +++ b/lang/core/list/list_lookup.go @@ -202,6 +202,13 @@ func (obj *ListLookupFunc) Build(typ *types.Type) (*types.Type, error) { obj.Func = &wrapped.Func{ Name: obj.String(), + FuncInfo: &wrapped.Info{ + // TODO: dedup with below Info data + Pure: true, + Memo: true, + Fast: true, + Spec: true, + }, Type: typ, // .Copy(), } @@ -230,6 +237,10 @@ func (obj *ListLookupFunc) Copy() interfaces.Func { // Call is the actual implementation here. This is used in lieu of the Stream // function which we'd have these contents within. func (obj *ListLookupFunc) Call(ctx context.Context, args []types.Value) (types.Value, error) { + if len(args) < 2 { + return nil, fmt.Errorf("not enough args") + } + // TODO: Could speculation pass in a non-list here and cause a panic? l := (args[0]).(*types.ListValue) index := args[1].Int() //zero := l.Type().Val.New() // the zero value @@ -273,7 +284,9 @@ func (obj *ListLookupFunc) Validate() error { func (obj *ListLookupFunc) Info() *interfaces.Info { return &interfaces.Info{ Pure: true, - Memo: false, + Memo: true, + Fast: true, + Spec: true, Sig: obj.sig(), // helper Err: obj.Validate(), } diff --git a/lang/core/list_lookup.go b/lang/core/list_lookup.go index bcd70948..f909ff48 100644 --- a/lang/core/list_lookup.go +++ b/lang/core/list_lookup.go @@ -45,6 +45,12 @@ const ( func init() { simple.Register(ListLookupFuncName, &simple.Scaffold{ + I: &simple.Info{ + Pure: true, + Memo: true, + Fast: true, + Spec: true, + }, T: types.NewType("func(list []?1, index int) ?1"), F: ListLookup, }) diff --git a/lang/core/list_lookup_default.go b/lang/core/list_lookup_default.go index 20335f9e..8d695677 100644 --- a/lang/core/list_lookup_default.go +++ b/lang/core/list_lookup_default.go @@ -45,6 +45,12 @@ const ( func init() { simple.Register(ListLookupDefaultFuncName, &simple.Scaffold{ + I: &simple.Info{ + Pure: true, + Memo: true, + Fast: true, + Spec: true, + }, T: types.NewType("func(list []?1, index int, default ?1) ?1"), F: ListLookupDefault, }) diff --git a/lang/core/local/pool.go b/lang/core/local/pool.go index 21e4d17c..79bb4365 100644 --- a/lang/core/local/pool.go +++ b/lang/core/local/pool.go @@ -54,6 +54,8 @@ func init() { funcs.ModuleRegister(ModuleName, PoolFuncName, func() interfaces.Func { return &PoolFunc{} }) // must register the func and name } +var _ interfaces.DataFunc = &PoolFunc{} + // PoolFunc is a function that returns a unique integer from a pool of numbers. // Within a given namespace, it returns the same integer for a given name. It is // a simple mechanism to allocate numbers to different inputs when we don't have @@ -93,8 +95,10 @@ func (obj *PoolFunc) Validate() error { // Info returns some static info about itself. func (obj *PoolFunc) Info() *interfaces.Info { return &interfaces.Info{ - Pure: true, + Pure: false, // depends on local API Memo: false, + Fast: false, + Spec: false, Sig: types.NewType(fmt.Sprintf("func(%s str, %s str) int", absPathArgNameNamespace, absPathArgNameUID)), // TODO: add an optional config arg //Sig: types.NewType(fmt.Sprintf("func(%s str, %s str, %s struct{}) int", absPathArgNameNamespace, absPathArgNameUID, absPathArgNameConfig)), @@ -153,15 +157,30 @@ func (obj *PoolFunc) Stream(ctx context.Context) error { } } +// Copy is implemented so that the obj.built value is not lost if we copy this +// function. +func (obj *PoolFunc) Copy() interfaces.Func { + return &PoolFunc{ + init: obj.init, // likely gets overwritten anyways + data: obj.data, // needed because we don't call SetData twice + } +} + // Call this function with the input args and return the value if it is possible // to do so at this time. func (obj *PoolFunc) Call(ctx context.Context, args []types.Value) (types.Value, error) { + if len(args) < 2 { + return nil, fmt.Errorf("not enough args") + } // Validation of these inputs happens in the Local API which does it. namespace := args[0].Str() uid := args[1].Str() // TODO: pass in config //config := args[2].???() + if obj.init == nil { + return nil, funcs.ErrCantSpeculate + } result, err := obj.init.Local.Pool(ctx, namespace, uid, nil) if err != nil { return nil, err diff --git a/lang/core/local/vardir.go b/lang/core/local/vardir.go index 07c02da8..675bb2ae 100644 --- a/lang/core/local/vardir.go +++ b/lang/core/local/vardir.go @@ -61,6 +61,8 @@ func init() { funcs.ModuleRegister(ModuleName, VarDirFuncName, func() interfaces.Func { return &VarDirFunc{} }) // must register the func and name } +var _ interfaces.DataFunc = &VarDirFunc{} + // VarDirFunc is a function that returns the absolute, full path in the deploy // from an input path that is relative to the calling file. If you pass it an // empty string, you'll just get the absolute deploy directory path that you're @@ -102,8 +104,10 @@ func (obj *VarDirFunc) Validate() error { // Info returns some static info about itself. func (obj *VarDirFunc) Info() *interfaces.Info { return &interfaces.Info{ - Pure: true, + Pure: false, // TODO: depends on runtime dir path Memo: false, + Fast: false, + Spec: false, Sig: types.NewType(fmt.Sprintf("func(%s str) str", absPathArgNamePath)), } } @@ -160,9 +164,21 @@ func (obj *VarDirFunc) Stream(ctx context.Context) error { } } +// Copy is implemented so that the obj.built value is not lost if we copy this +// function. +func (obj *VarDirFunc) Copy() interfaces.Func { + return &VarDirFunc{ + init: obj.init, // likely gets overwritten anyways + data: obj.data, // needed because we don't call SetData twice + } +} + // Call this function with the input args and return the value if it is possible // to do so at this time. func (obj *VarDirFunc) Call(ctx context.Context, args []types.Value) (types.Value, error) { + if len(args) < 1 { + return nil, fmt.Errorf("not enough args") + } reldir := args[0].Str() if strings.HasPrefix(reldir, "/") { return nil, fmt.Errorf("path must be relative") @@ -174,6 +190,9 @@ func (obj *VarDirFunc) Call(ctx context.Context, args []types.Value) (types.Valu p := fmt.Sprintf("%s/", path.Join(VarDirFunctionsPrefix, reldir)) + if obj.init == nil { + return nil, funcs.ErrCantSpeculate + } result, err := obj.init.Local.VarDir(ctx, p) if err != nil { return nil, err diff --git a/lang/core/lookup.go b/lang/core/lookup.go index 1fd24848..3f63e43a 100644 --- a/lang/core/lookup.go +++ b/lang/core/lookup.go @@ -148,6 +148,9 @@ func (obj *LookupFunc) FuncInfer(partialType *types.Type, partialValues []types. // runs. func (obj *LookupFunc) Build(typ *types.Type) (*types.Type, error) { // typ is the KindFunc signature we're trying to build... + if typ == nil { + return nil, fmt.Errorf("nil type") // happens b/c of Copy() + } if typ.Kind != types.KindFunc { return nil, fmt.Errorf("input type must be of kind func") } @@ -226,7 +229,9 @@ func (obj *LookupFunc) Info() *interfaces.Info { if obj.fn == nil { return &interfaces.Info{ Pure: true, - Memo: false, + Memo: true, + Fast: true, + Spec: true, Sig: types.NewType("func(?1, ?2) ?3"), // func kind Err: obj.Validate(), } @@ -253,6 +258,9 @@ func (obj *LookupFunc) Stream(ctx context.Context) error { // Call returns the result of this function. func (obj *LookupFunc) Call(ctx context.Context, args []types.Value) (types.Value, error) { + if obj.fn == nil { + return nil, funcs.ErrCantSpeculate + } cf, ok := obj.fn.(interfaces.CallableFunc) if !ok { // programming error diff --git a/lang/core/lookup_default.go b/lang/core/lookup_default.go index 113c34d3..7a78eef1 100644 --- a/lang/core/lookup_default.go +++ b/lang/core/lookup_default.go @@ -93,6 +93,9 @@ func (obj *LookupDefaultFunc) ArgGen(index int) (string, error) { // runs. func (obj *LookupDefaultFunc) Build(typ *types.Type) (*types.Type, error) { // typ is the KindFunc signature we're trying to build... + if typ == nil { + return nil, fmt.Errorf("nil type") // happens b/c of Copy() + } if typ.Kind != types.KindFunc { return nil, fmt.Errorf("input type must be of kind func") } @@ -172,7 +175,9 @@ func (obj *LookupDefaultFunc) Info() *interfaces.Info { if obj.fn == nil { return &interfaces.Info{ Pure: true, - Memo: false, + Memo: true, + Fast: true, + Spec: true, Sig: types.NewType("func(?1, ?2, ?3) ?3"), // func kind Err: obj.Validate(), } @@ -199,6 +204,9 @@ func (obj *LookupDefaultFunc) Stream(ctx context.Context) error { // Call returns the result of this function. func (obj *LookupDefaultFunc) Call(ctx context.Context, args []types.Value) (types.Value, error) { + if obj.fn == nil { + return nil, funcs.ErrCantSpeculate + } cf, ok := obj.fn.(interfaces.CallableFunc) if !ok { // programming error diff --git a/lang/core/map/map_keys.go b/lang/core/map/map_keys.go index f9f04870..ec325215 100644 --- a/lang/core/map/map_keys.go +++ b/lang/core/map/map_keys.go @@ -39,6 +39,12 @@ import ( func init() { simple.ModuleRegister(ModuleName, "keys", &simple.Scaffold{ + I: &simple.Info{ + Pure: true, + Memo: true, + Fast: true, + Spec: true, + }, // TODO: Maybe saying ?0 could mean we don't care about that type? T: types.NewType("func(x map{?1: ?2}) []?1"), F: MapKeys, diff --git a/lang/core/map/map_lookup.go b/lang/core/map/map_lookup.go index fea955e6..bb98947a 100644 --- a/lang/core/map/map_lookup.go +++ b/lang/core/map/map_lookup.go @@ -204,6 +204,13 @@ func (obj *MapLookupFunc) Build(typ *types.Type) (*types.Type, error) { obj.Func = &wrapped.Func{ Name: obj.String(), + FuncInfo: &wrapped.Info{ + // TODO: dedup with below Info data + Pure: true, + Memo: true, + Fast: true, + Spec: true, + }, Type: typ, // .Copy(), } @@ -232,6 +239,9 @@ func (obj *MapLookupFunc) Copy() interfaces.Func { // Call is the actual implementation here. This is used in lieu of the Stream // function which we'd have these contents within. func (obj *MapLookupFunc) Call(ctx context.Context, args []types.Value) (types.Value, error) { + if len(args) < 2 { + return nil, fmt.Errorf("not enough args") + } m := (args[0]).(*types.MapValue) key := args[1] //zero := m.Type().Val.New() // the zero value @@ -266,7 +276,9 @@ func (obj *MapLookupFunc) Validate() error { func (obj *MapLookupFunc) Info() *interfaces.Info { return &interfaces.Info{ Pure: true, - Memo: false, + Memo: true, + Fast: true, + Spec: true, Sig: obj.sig(), // helper Err: obj.Validate(), } diff --git a/lang/core/map/map_values.go b/lang/core/map/map_values.go index 7c1df3e0..1ae19d7b 100644 --- a/lang/core/map/map_values.go +++ b/lang/core/map/map_values.go @@ -39,6 +39,12 @@ import ( func init() { simple.ModuleRegister(ModuleName, "values", &simple.Scaffold{ + I: &simple.Info{ + Pure: true, + Memo: true, + Fast: true, + Spec: true, + }, // TODO: Maybe saying ?0 could mean we don't care about that type? T: types.NewType("func(x map{?1: ?2}) []?2"), F: MapValues, diff --git a/lang/core/map_lookup.go b/lang/core/map_lookup.go index 99933ad1..9077bcbb 100644 --- a/lang/core/map_lookup.go +++ b/lang/core/map_lookup.go @@ -43,6 +43,12 @@ const ( func init() { simple.Register(MapLookupFuncName, &simple.Scaffold{ + I: &simple.Info{ + Pure: true, + Memo: true, + Fast: true, + Spec: true, + }, T: types.NewType("func(map map{?1: ?2}, key ?1) ?2"), F: MapLookup, }) diff --git a/lang/core/map_lookup_default.go b/lang/core/map_lookup_default.go index f6032f41..f19cf7bd 100644 --- a/lang/core/map_lookup_default.go +++ b/lang/core/map_lookup_default.go @@ -43,6 +43,12 @@ const ( func init() { simple.Register(MapLookupDefaultFuncName, &simple.Scaffold{ + I: &simple.Info{ + Pure: true, + Memo: true, + Fast: true, + Spec: true, + }, T: types.NewType("func(map map{?1: ?2}, key ?1, default ?2) ?2"), F: MapLookupDefault, }) diff --git a/lang/core/math/fortytwo.go b/lang/core/math/fortytwo.go index ddd229bf..8d6b1d2d 100644 --- a/lang/core/math/fortytwo.go +++ b/lang/core/math/fortytwo.go @@ -42,6 +42,12 @@ func init() { typInt := types.NewType("func() int") typFloat := types.NewType("func() float") multi.ModuleRegister(ModuleName, "fortytwo", &multi.Scaffold{ + I: &multi.Info{ + Pure: true, + Memo: true, + Fast: true, + Spec: true, + }, T: types.NewType("func() ?1"), M: multi.TypeMatch(map[string]interfaces.FuncSig{ "func() int": fortyTwo(typInt), diff --git a/lang/core/math/minus1.go b/lang/core/math/minus1.go index 0eaaf414..0f364cb3 100644 --- a/lang/core/math/minus1.go +++ b/lang/core/math/minus1.go @@ -38,6 +38,12 @@ import ( func init() { simple.ModuleRegister(ModuleName, "minus1", &simple.Scaffold{ + I: &simple.Info{ + Pure: true, + Memo: true, + Fast: true, + Spec: true, + }, T: types.NewType("func(x int) int"), F: Minus1, }) diff --git a/lang/core/math/mod.go b/lang/core/math/mod.go index bea43e9e..cca544c7 100644 --- a/lang/core/math/mod.go +++ b/lang/core/math/mod.go @@ -40,6 +40,12 @@ import ( func init() { simple.ModuleRegister(ModuleName, "mod", &simple.Scaffold{ + I: &simple.Info{ + Pure: true, + Memo: true, + Fast: true, + Spec: true, + }, T: types.NewType("func(?1, ?1) ?1"), // all int or float C: simple.TypeMatch([]string{ "func(int, int) int", diff --git a/lang/core/math/pow.go b/lang/core/math/pow.go index b0e37f7a..6bd0b745 100644 --- a/lang/core/math/pow.go +++ b/lang/core/math/pow.go @@ -40,6 +40,12 @@ import ( func init() { simple.ModuleRegister(ModuleName, "pow", &simple.Scaffold{ + I: &simple.Info{ + Pure: true, + Memo: true, + Fast: true, + Spec: true, + }, T: types.NewType("func(x float, y float) float"), F: Pow, }) diff --git a/lang/core/math/sqrt.go b/lang/core/math/sqrt.go index f6a67338..cf598522 100644 --- a/lang/core/math/sqrt.go +++ b/lang/core/math/sqrt.go @@ -40,6 +40,12 @@ import ( func init() { simple.ModuleRegister(ModuleName, "sqrt", &simple.Scaffold{ + I: &simple.Info{ + Pure: true, + Memo: true, + Fast: true, + Spec: true, + }, T: types.NewType("func(x float) float"), F: Sqrt, }) diff --git a/lang/core/net/cidr_to_ip.go b/lang/core/net/cidr_to_ip.go index cde44be4..e0ec3086 100644 --- a/lang/core/net/cidr_to_ip.go +++ b/lang/core/net/cidr_to_ip.go @@ -44,22 +44,52 @@ import ( func init() { simple.ModuleRegister(ModuleName, "cidr_to_ip", &simple.Scaffold{ + I: &simple.Info{ + Pure: true, + Memo: true, + Fast: true, + Spec: true, + }, T: types.NewType("func(a str) str"), F: CidrToIP, }) simple.ModuleRegister(ModuleName, "cidr_to_prefix", &simple.Scaffold{ + I: &simple.Info{ + Pure: true, + Memo: true, + Fast: true, + Spec: true, + }, T: types.NewType("func(a str) str"), F: CidrToPrefix, }) simple.ModuleRegister(ModuleName, "cidr_to_mask", &simple.Scaffold{ + I: &simple.Info{ + Pure: true, + Memo: true, + Fast: true, + Spec: true, + }, T: types.NewType("func(a str) str"), F: CidrToMask, }) simple.ModuleRegister(ModuleName, "cidr_to_first", &simple.Scaffold{ + I: &simple.Info{ + Pure: true, + Memo: true, + Fast: true, + Spec: true, + }, T: types.NewType("func(a str) str"), F: CidrToFirst, }) simple.ModuleRegister(ModuleName, "cidr_to_last", &simple.Scaffold{ + I: &simple.Info{ + Pure: true, + Memo: true, + Fast: true, + Spec: true, + }, T: types.NewType("func(a str) str"), F: CidrToLast, }) diff --git a/lang/core/net/macfmt.go b/lang/core/net/macfmt.go index abb42559..145597a7 100644 --- a/lang/core/net/macfmt.go +++ b/lang/core/net/macfmt.go @@ -41,14 +41,32 @@ import ( func init() { simple.ModuleRegister(ModuleName, "is_mac", &simple.Scaffold{ + I: &simple.Info{ + Pure: true, + Memo: true, + Fast: true, + Spec: true, + }, T: types.NewType("func(a str) bool"), F: IsMac, }) simple.ModuleRegister(ModuleName, "macfmt", &simple.Scaffold{ + I: &simple.Info{ + Pure: true, + Memo: true, + Fast: true, + Spec: true, + }, T: types.NewType("func(a str) str"), F: MacFmt, }) simple.ModuleRegister(ModuleName, "oldmacfmt", &simple.Scaffold{ + I: &simple.Info{ + Pure: true, + Memo: true, + Fast: true, + Spec: true, + }, T: types.NewType("func(a str) str"), F: OldMacFmt, }) diff --git a/lang/core/net/macs.go b/lang/core/net/macs.go index 58bbfe20..d9c4e0ea 100644 --- a/lang/core/net/macs.go +++ b/lang/core/net/macs.go @@ -39,6 +39,12 @@ import ( func init() { simple.ModuleRegister(ModuleName, "macs", &simple.Scaffold{ + I: &simple.Info{ + Pure: false, + Memo: false, + Fast: false, + Spec: false, // might be different at real runtime + }, T: types.NewType("func() []str"), F: Macs, }) diff --git a/lang/core/net/url_parser.go b/lang/core/net/url_parser.go index 41b1917a..1f56d6a0 100644 --- a/lang/core/net/url_parser.go +++ b/lang/core/net/url_parser.go @@ -56,6 +56,12 @@ var urlParserReturnType = fmt.Sprintf( func init() { simple.ModuleRegister(ModuleName, "url_parser", &simple.Scaffold{ + I: &simple.Info{ + Pure: true, + Memo: true, + Fast: true, + Spec: true, + }, T: types.NewType(fmt.Sprintf("func(str) %s", urlParserReturnType)), F: URLParser, }) diff --git a/lang/core/os/args.go b/lang/core/os/args.go index ca0a0c3a..c2d4d0fa 100644 --- a/lang/core/os/args.go +++ b/lang/core/os/args.go @@ -39,6 +39,13 @@ import ( func init() { simple.ModuleRegister(ModuleName, "args", &simple.Scaffold{ + I: &simple.Info{ + // XXX: make sure nobody changes it at runtime + Pure: true, + Memo: true, + Fast: true, + Spec: true, + }, T: types.NewType("func() []str"), F: Args, }) diff --git a/lang/core/os/distro.go b/lang/core/os/distro.go index 364c5999..3501079e 100644 --- a/lang/core/os/distro.go +++ b/lang/core/os/distro.go @@ -44,6 +44,12 @@ var typeParseDistroUID = types.NewType(fmt.Sprintf("func(str) %s", structDistroU func init() { simple.ModuleRegister(ModuleName, "parse_distro_uid", &simple.Scaffold{ + I: &simple.Info{ + Pure: true, + Memo: true, + Fast: true, + Spec: true, + }, T: typeParseDistroUID, F: ParseDistroUID, }) diff --git a/lang/core/os/expand_home.go b/lang/core/os/expand_home.go index 20eeb088..6500f712 100644 --- a/lang/core/os/expand_home.go +++ b/lang/core/os/expand_home.go @@ -39,6 +39,13 @@ import ( func init() { simple.ModuleRegister(ModuleName, "expand_home", &simple.Scaffold{ + // These might change depending on user database... + I: &simple.Info{ + Pure: false, + Memo: false, + Fast: true, + Spec: false, + }, T: types.NewType("func(str) str"), F: ExpandHome, }) diff --git a/lang/core/os/family.go b/lang/core/os/family.go index 4cddc94d..54c9100a 100644 --- a/lang/core/os/family.go +++ b/lang/core/os/family.go @@ -40,6 +40,13 @@ import ( func init() { simple.ModuleRegister(ModuleName, "family", &simple.Scaffold{ + I: &simple.Info{ + // In theory these are dependent on runtime. + Pure: false, + Memo: false, + Fast: true, + Spec: false, + }, T: types.NewType("func() str"), F: Family, }) @@ -62,14 +69,35 @@ func init() { // TODO: Create a family method that will return a giant struct. simple.ModuleRegister(ModuleName, "is_family_redhat", &simple.Scaffold{ + I: &simple.Info{ + // In theory these are dependent on runtime. + Pure: false, + Memo: false, + Fast: true, + Spec: false, + }, T: types.NewType("func() bool"), F: IsFamilyRedHat, }) simple.ModuleRegister(ModuleName, "is_family_debian", &simple.Scaffold{ + I: &simple.Info{ + // In theory these are dependent on runtime. + Pure: false, + Memo: false, + Fast: true, + Spec: false, + }, T: types.NewType("func() bool"), F: IsFamilyDebian, }) simple.ModuleRegister(ModuleName, "is_family_archlinux", &simple.Scaffold{ + I: &simple.Info{ + // In theory these are dependent on runtime. + Pure: false, + Memo: false, + Fast: true, + Spec: false, + }, T: types.NewType("func() bool"), F: IsFamilyArchLinux, }) diff --git a/lang/core/os/is_virtual.go b/lang/core/os/is_virtual.go index 3ca7d852..3abf8271 100644 --- a/lang/core/os/is_virtual.go +++ b/lang/core/os/is_virtual.go @@ -77,6 +77,13 @@ var virtualizationVendorSlice = []string{ func init() { simple.ModuleRegister(ModuleName, "is_virtual", &simple.Scaffold{ + I: &simple.Info{ + // In theory these are dependent on runtime. + Pure: false, + Memo: false, + Fast: false, // network requests to API's? + Spec: false, + }, T: types.NewType("func() bool"), F: IsVirtual, }) diff --git a/lang/core/os/readfile.go b/lang/core/os/readfile.go index 2351d903..8bb06b47 100644 --- a/lang/core/os/readfile.go +++ b/lang/core/os/readfile.go @@ -97,6 +97,8 @@ func (obj *ReadFileFunc) Info() *interfaces.Info { return &interfaces.Info{ Pure: false, // maybe false because the file contents can change Memo: false, + Fast: false, + Spec: false, Sig: types.NewType(fmt.Sprintf("func(%s str) str", readFileArgNameFilename)), } } @@ -249,6 +251,9 @@ func (obj *ReadFileFunc) Stream(ctx context.Context) error { // Call this function with the input args and return the value if it is possible // to do so at this time. func (obj *ReadFileFunc) Call(ctx context.Context, args []types.Value) (types.Value, error) { + if len(args) < 1 { + return nil, fmt.Errorf("not enough args") + } filename := args[0].Str() // read file... diff --git a/lang/core/os/system.go b/lang/core/os/system.go index fca6a156..8d7a67f8 100644 --- a/lang/core/os/system.go +++ b/lang/core/os/system.go @@ -91,6 +91,8 @@ func (obj *SystemFunc) Info() *interfaces.Info { return &interfaces.Info{ Pure: false, // definitely false Memo: false, + Fast: false, + Spec: false, Sig: types.NewType(fmt.Sprintf("func(%s str) str", systemArgNameCmd)), Err: obj.Validate(), } diff --git a/lang/core/panic.go b/lang/core/panic.go index 6efdf6b7..bdf5384b 100644 --- a/lang/core/panic.go +++ b/lang/core/panic.go @@ -39,6 +39,12 @@ import ( func init() { simple.Register("panic", &simple.Scaffold{ + I: &simple.Info{ + Pure: false, // n/a + Memo: false, + Fast: true, + Spec: false, // important! + }, T: types.NewType("func(x ?1) bool"), // ?1 is bool or str C: simple.TypeMatch([]string{ "func(x bool) bool", diff --git a/lang/core/random1.go b/lang/core/random1.go index 7967665c..6d156cc7 100644 --- a/lang/core/random1.go +++ b/lang/core/random1.go @@ -96,6 +96,9 @@ func (obj *Random1Func) Validate() error { func (obj *Random1Func) Info() *interfaces.Info { return &interfaces.Info{ Pure: false, + Memo: false, + Fast: false, + Spec: false, Sig: types.NewType(fmt.Sprintf("func(%s int) str", random1ArgNameLength)), Err: obj.Validate(), } diff --git a/lang/core/regexp/match.go b/lang/core/regexp/match.go index 6a597bc7..90605a7a 100644 --- a/lang/core/regexp/match.go +++ b/lang/core/regexp/match.go @@ -40,6 +40,12 @@ import ( func init() { simple.ModuleRegister(ModuleName, "match", &simple.Scaffold{ + I: &simple.Info{ + Pure: true, + Memo: true, + Fast: false, // TODO: should we consider this fast? + Spec: true, + }, T: types.NewType("func(pattern str, s str) bool"), F: Match, }) diff --git a/lang/core/strings/join_nonempty.go b/lang/core/strings/join_nonempty.go index d450b6b1..4ce94a62 100644 --- a/lang/core/strings/join_nonempty.go +++ b/lang/core/strings/join_nonempty.go @@ -39,6 +39,12 @@ import ( func init() { simple.ModuleRegister(ModuleName, "join_nonempty", &simple.Scaffold{ + I: &simple.Info{ + Pure: true, + Memo: true, + Fast: true, + Spec: true, + }, T: types.NewType("func(s []str, sep str) str"), F: JoinNonempty, }) diff --git a/lang/core/strings/pad.go b/lang/core/strings/pad.go index 0c7dac76..e647308e 100644 --- a/lang/core/strings/pad.go +++ b/lang/core/strings/pad.go @@ -39,10 +39,22 @@ import ( func init() { simple.ModuleRegister(ModuleName, "left_pad", &simple.Scaffold{ + I: &simple.Info{ + Pure: true, + Memo: true, + Fast: true, + Spec: true, + }, T: types.NewType("func(s str, pad str, len int) str"), F: LeftPad, }) simple.ModuleRegister(ModuleName, "right_pad", &simple.Scaffold{ + I: &simple.Info{ + Pure: true, + Memo: true, + Fast: true, + Spec: true, + }, T: types.NewType("func(s str, pad str, len int) str"), F: RightPad, }) diff --git a/lang/core/strings/split.go b/lang/core/strings/split.go index 5070c17b..a8d72686 100644 --- a/lang/core/strings/split.go +++ b/lang/core/strings/split.go @@ -39,6 +39,12 @@ import ( func init() { simple.ModuleRegister(ModuleName, "split", &simple.Scaffold{ + I: &simple.Info{ + Pure: true, + Memo: true, + Fast: true, + Spec: true, + }, T: types.NewType("func(a str, b str) []str"), F: Split, }) diff --git a/lang/core/strings/substring.go b/lang/core/strings/substring.go index e92290f1..45004649 100644 --- a/lang/core/strings/substring.go +++ b/lang/core/strings/substring.go @@ -39,6 +39,12 @@ import ( func init() { simple.ModuleRegister(ModuleName, "substr", &simple.Scaffold{ + I: &simple.Info{ + Pure: true, + Memo: true, + Fast: true, + Spec: true, + }, T: types.NewType("func(s str, low int, high int) str"), F: SubStr, }) diff --git a/lang/core/strings/to_lower.go b/lang/core/strings/to_lower.go index d8f88119..0b65b4dd 100644 --- a/lang/core/strings/to_lower.go +++ b/lang/core/strings/to_lower.go @@ -39,6 +39,12 @@ import ( func init() { simple.ModuleRegister(ModuleName, "to_lower", &simple.Scaffold{ + I: &simple.Info{ + Pure: true, + Memo: true, + Fast: true, + Spec: true, + }, T: types.NewType("func(a str) str"), F: ToLower, }) diff --git a/lang/core/struct_lookup.go b/lang/core/struct_lookup.go index 40875362..96ece315 100644 --- a/lang/core/struct_lookup.go +++ b/lang/core/struct_lookup.go @@ -269,7 +269,9 @@ func (obj *StructLookupFunc) Info() *interfaces.Info { } return &interfaces.Info{ Pure: true, - Memo: false, + Memo: true, + Fast: true, + Spec: true, Sig: sig, Err: obj.Validate(), } @@ -339,6 +341,9 @@ func (obj *StructLookupFunc) Stream(ctx context.Context) error { // Call returns the result of this function. func (obj *StructLookupFunc) Call(ctx context.Context, args []types.Value) (types.Value, error) { + if len(args) < 2 { + return nil, fmt.Errorf("not enough args") + } st := args[0].(*types.StructValue) field := args[1].Str() diff --git a/lang/core/struct_lookup_optional.go b/lang/core/struct_lookup_optional.go index 7f46d2fe..9f02c725 100644 --- a/lang/core/struct_lookup_optional.go +++ b/lang/core/struct_lookup_optional.go @@ -266,7 +266,9 @@ func (obj *StructLookupOptionalFunc) Info() *interfaces.Info { } return &interfaces.Info{ Pure: true, - Memo: false, + Memo: true, + Fast: true, + Spec: true, Sig: sig, Err: obj.Validate(), } @@ -345,6 +347,9 @@ func (obj *StructLookupOptionalFunc) Stream(ctx context.Context) error { // Call returns the result of this function. func (obj *StructLookupOptionalFunc) Call(ctx context.Context, args []types.Value) (types.Value, error) { + if len(args) < 3 { + return nil, fmt.Errorf("not enough args") + } st := args[0].(*types.StructValue) field := args[1].Str() optional := args[2] diff --git a/lang/core/sys/cpucount_fact.go b/lang/core/sys/cpucount_fact.go index 8fc9a2a8..a9ce32ff 100644 --- a/lang/core/sys/cpucount_fact.go +++ b/lang/core/sys/cpucount_fact.go @@ -76,6 +76,8 @@ func (obj *CPUCountFact) String() string { // Info returns static typing info about what the fact returns. func (obj *CPUCountFact) Info() *facts.Info { return &facts.Info{ + Pure: false, + Memo: false, Output: types.NewType("int"), } } diff --git a/lang/core/sys/env.go b/lang/core/sys/env.go index e1f2f86b..ef5a4231 100644 --- a/lang/core/sys/env.go +++ b/lang/core/sys/env.go @@ -40,18 +40,42 @@ import ( func init() { simple.ModuleRegister(ModuleName, "getenv", &simple.Scaffold{ + I: &simple.Info{ + Pure: false, + Memo: false, + Fast: true, + Spec: false, + }, T: types.NewType("func(str) str"), F: GetEnv, }) simple.ModuleRegister(ModuleName, "defaultenv", &simple.Scaffold{ + I: &simple.Info{ + Pure: false, + Memo: false, + Fast: true, + Spec: false, + }, T: types.NewType("func(str, str) str"), F: DefaultEnv, }) simple.ModuleRegister(ModuleName, "hasenv", &simple.Scaffold{ + I: &simple.Info{ + Pure: false, + Memo: false, + Fast: true, + Spec: false, + }, T: types.NewType("func(str) bool"), F: HasEnv, }) simple.ModuleRegister(ModuleName, "env", &simple.Scaffold{ + I: &simple.Info{ + Pure: false, + Memo: false, + Fast: true, + Spec: false, + }, T: types.NewType("func() map{str: str}"), F: Env, }) diff --git a/lang/core/sys/load_fact.go b/lang/core/sys/load_fact.go index 9e4c03d1..0d2bf78d 100644 --- a/lang/core/sys/load_fact.go +++ b/lang/core/sys/load_fact.go @@ -70,6 +70,8 @@ func (obj *LoadFact) String() string { // Info returns some static info about itself. func (obj *LoadFact) Info() *facts.Info { return &facts.Info{ + Pure: false, + Memo: false, Output: types.NewType(loadSignature), } } diff --git a/lang/core/sys/uptime_fact.go b/lang/core/sys/uptime_fact.go index 5a8ea21e..57064e30 100644 --- a/lang/core/sys/uptime_fact.go +++ b/lang/core/sys/uptime_fact.go @@ -62,6 +62,8 @@ func (obj *UptimeFact) String() string { // Info returns some static info about itself. func (obj *UptimeFact) Info() *facts.Info { return &facts.Info{ + Pure: false, + Memo: false, Output: types.TypeInt, } } diff --git a/lang/core/test/fastcount_fact.go b/lang/core/test/fastcount_fact.go index 3de528ca..3011ac3c 100644 --- a/lang/core/test/fastcount_fact.go +++ b/lang/core/test/fastcount_fact.go @@ -71,6 +71,8 @@ func (obj *FastCountFact) String() string { // Info returns some static info about itself. func (obj *FastCountFact) Info() *facts.Info { return &facts.Info{ + Pure: false, + Memo: false, Output: types.NewType("int"), } } @@ -108,6 +110,9 @@ func (obj *FastCountFact) Stream(ctx context.Context) error { // Call this fact and return the value if it is possible to do so at this time. func (obj *FastCountFact) Call(ctx context.Context) (types.Value, error) { + if obj.mutex == nil { + return nil, facts.ErrCantSpeculate + } obj.mutex.Lock() // TODO: could be a read lock count := obj.count obj.mutex.Unlock() diff --git a/lang/core/test/oneinstance_fact.go b/lang/core/test/oneinstance_fact.go index 827c9ee4..a41c6c8e 100644 --- a/lang/core/test/oneinstance_fact.go +++ b/lang/core/test/oneinstance_fact.go @@ -122,6 +122,12 @@ func init() { }) simple.ModuleRegister(ModuleName, OneInstanceBFuncName, &simple.Scaffold{ + I: &simple.Info{ + Pure: true, + Memo: true, + Fast: true, + Spec: false, // don't break tests + }, T: types.NewType("func() str"), F: func(context.Context, []types.Value) (types.Value, error) { oneInstanceBMutex.Lock() @@ -135,6 +141,12 @@ func init() { D: &OneInstanceFact{}, }) simple.ModuleRegister(ModuleName, OneInstanceDFuncName, &simple.Scaffold{ + I: &simple.Info{ + Pure: true, + Memo: true, + Fast: true, + Spec: false, // don't break tests + }, T: types.NewType("func() str"), F: func(context.Context, []types.Value) (types.Value, error) { oneInstanceDMutex.Lock() @@ -148,6 +160,12 @@ func init() { D: &OneInstanceFact{}, }) simple.ModuleRegister(ModuleName, OneInstanceFFuncName, &simple.Scaffold{ + I: &simple.Info{ + Pure: true, + Memo: true, + Fast: true, + Spec: false, // don't break tests + }, T: types.NewType("func() str"), F: func(context.Context, []types.Value) (types.Value, error) { oneInstanceFMutex.Lock() @@ -161,6 +179,12 @@ func init() { D: &OneInstanceFact{}, }) simple.ModuleRegister(ModuleName, OneInstanceHFuncName, &simple.Scaffold{ + I: &simple.Info{ + Pure: true, + Memo: true, + Fast: true, + Spec: false, // don't break tests + }, T: types.NewType("func() str"), F: func(context.Context, []types.Value) (types.Value, error) { oneInstanceHMutex.Lock() diff --git a/lang/core/util/hostname_mapper.go b/lang/core/util/hostname_mapper.go index 53cb1e16..58923351 100644 --- a/lang/core/util/hostname_mapper.go +++ b/lang/core/util/hostname_mapper.go @@ -41,6 +41,12 @@ import ( func init() { simple.ModuleRegister(ModuleName, "hostname_mapper", &simple.Scaffold{ + I: &simple.Info{ + Pure: false, + Memo: false, + Fast: false, + Spec: false, + }, T: types.NewType("func(map{str:str}) str"), F: HostnameMapper, }) diff --git a/lang/core/value/get.go b/lang/core/value/get.go index 18f17249..608f4bfb 100644 --- a/lang/core/value/get.go +++ b/lang/core/value/get.go @@ -206,6 +206,8 @@ func (obj *GetFunc) Info() *interfaces.Info { return &interfaces.Info{ Pure: false, // definitely false Memo: false, + Fast: false, + Spec: false, Sig: sig, Err: obj.Validate(), } @@ -310,6 +312,9 @@ func (obj *GetFunc) Stream(ctx context.Context) error { // to do so at this time. This was previously getValue which gets the value // we're looking for. func (obj *GetFunc) Call(ctx context.Context, args []types.Value) (types.Value, error) { + if len(args) < 1 { + return nil, fmt.Errorf("not enough args") + } key := args[0].Str() typ, exists := obj.Info().Sig.Out.Map[getFieldNameValue] // type of value field @@ -326,6 +331,9 @@ func (obj *GetFunc) Call(ctx context.Context, args []types.Value) (types.Value, // step that might be needed if the value started out empty... // TODO: We could even add a stored: bool field in the returned struct! isReady := true // assume true + if obj.init == nil { + return nil, funcs.ErrCantSpeculate + } val, err := obj.init.Local.ValueGet(ctx, key) if err != nil { return nil, errwrap.Wrapf(err, "channel read failed on `%s`", key) diff --git a/lang/core/world/collect/res.go b/lang/core/world/collect/res.go index 315a909d..2088b39a 100644 --- a/lang/core/world/collect/res.go +++ b/lang/core/world/collect/res.go @@ -118,6 +118,8 @@ func (obj *ResFunc) Info() *interfaces.Info { return &interfaces.Info{ Pure: false, Memo: false, + Fast: false, + Spec: false, Sig: obj.sig(), Err: obj.Validate(), } @@ -225,6 +227,9 @@ func (obj *ResFunc) Stream(ctx context.Context) error { // to do so at this time. This was previously getValue which gets the value // we're looking for. func (obj *ResFunc) Call(ctx context.Context, args []types.Value) (types.Value, error) { + if len(args) < 1 { + return nil, fmt.Errorf("not enough args") + } kind := args[0].Str() if kind == "" { return nil, fmt.Errorf("resource kind is empty") @@ -241,6 +246,9 @@ func (obj *ResFunc) Call(ctx context.Context, args []types.Value) (types.Value, } filters = append(filters, filter) + if obj.init == nil { + return nil, funcs.ErrCantSpeculate + } resOutput, err := obj.init.World.ResCollect(ctx, filters) if err != nil { return nil, err diff --git a/lang/core/world/exchange.go b/lang/core/world/exchange.go index 11cb2336..dfa6ab5b 100644 --- a/lang/core/world/exchange.go +++ b/lang/core/world/exchange.go @@ -91,6 +91,8 @@ func (obj *ExchangeFunc) Info() *interfaces.Info { return &interfaces.Info{ Pure: false, // definitely false Memo: false, + Fast: false, + Spec: false, // TODO: do we want to allow this to be statically polymorphic, // and have value be any type we might want? // output is map of: hostname => value diff --git a/lang/core/world/getval.go b/lang/core/world/getval.go index b6b5d940..cf144557 100644 --- a/lang/core/world/getval.go +++ b/lang/core/world/getval.go @@ -97,6 +97,8 @@ func (obj *GetValFunc) Info() *interfaces.Info { return &interfaces.Info{ Pure: false, // definitely false Memo: false, + Fast: false, + Spec: false, // output is a struct with two fields: // value is the zero value if not exists. A bool for that in other field. Sig: types.NewType(fmt.Sprintf("func(%s str) struct{%s str; %s bool}", getValArgNameKey, getValFieldNameValue, getValFieldNameExists)), @@ -206,8 +208,14 @@ func (obj *GetValFunc) Stream(ctx context.Context) error { // to do so at this time. This was previously getValue which gets the value // we're looking for. func (obj *GetValFunc) Call(ctx context.Context, args []types.Value) (types.Value, error) { + if len(args) < 1 { + return nil, fmt.Errorf("not enough args") + } key := args[0].Str() exists := true // assume true + if obj.init == nil { + return nil, funcs.ErrCantSpeculate + } val, err := obj.init.World.StrGet(ctx, key) if err != nil && obj.init.World.StrIsNotExist(err) { exists = false // val doesn't exist diff --git a/lang/core/world/kvlookup.go b/lang/core/world/kvlookup.go index 728b0d53..c8c444bc 100644 --- a/lang/core/world/kvlookup.go +++ b/lang/core/world/kvlookup.go @@ -93,6 +93,8 @@ func (obj *KVLookupFunc) Info() *interfaces.Info { return &interfaces.Info{ Pure: false, // definitely false Memo: false, + Fast: false, + Spec: false, // output is map of: hostname => value Sig: types.NewType(fmt.Sprintf("func(%s str) map{str: str}", kvLookupArgNameNamespace)), Err: obj.Validate(), @@ -211,7 +213,13 @@ func (obj *KVLookupFunc) Stream(ctx context.Context) error { // to do so at this time. This was previously buildMap, which builds the result // map which we'll need. It uses struct variables. func (obj *KVLookupFunc) Call(ctx context.Context, args []types.Value) (types.Value, error) { + if len(args) < 1 { + return nil, fmt.Errorf("not enough args") + } namespace := args[0].Str() + if obj.init == nil { + return nil, funcs.ErrCantSpeculate + } keyMap, err := obj.init.World.StrMapGet(ctx, namespace) if err != nil { return nil, errwrap.Wrapf(err, "channel read failed on `%s`", namespace) diff --git a/lang/core/world/schedule.go b/lang/core/world/schedule.go index 94961a40..452498d0 100644 --- a/lang/core/world/schedule.go +++ b/lang/core/world/schedule.go @@ -283,6 +283,8 @@ func (obj *ScheduleFunc) Info() *interfaces.Info { return &interfaces.Info{ Pure: false, // definitely false Memo: false, + Fast: false, + Spec: false, // output is list of hostnames chosen Sig: sig, // func kind Err: obj.Validate(), diff --git a/lang/funcs/facts/facts.go b/lang/funcs/facts/facts.go index 7a372fee..75056f1d 100644 --- a/lang/funcs/facts/facts.go +++ b/lang/funcs/facts/facts.go @@ -40,6 +40,17 @@ import ( "github.com/purpleidea/mgmt/lang/types" ) +const ( + // ErrCantSpeculate is an error that explains that we can't speculate + // when trying to Call a function. This often gets called by the Value() + // method of the Expr. This can be useful if we want to distinguish + // between "something is broken" and "I just can't produce a value at + // this time", which can be identified and skipped over. If it's the + // former, then it's okay to error early and shut everything down since + // we know this function is never going to work the way it's called. + ErrCantSpeculate = funcs.ErrCantSpeculate +) + // RegisteredFacts is a global map of all possible facts which can be used. You // should never touch this map directly. Use methods like Register instead. var RegisteredFacts = make(map[string]func() Fact) // must initialize @@ -79,6 +90,10 @@ func ModuleRegister(module, name string, fn func() Fact) { // used for static analysis and type checking. If you break this contract, you // might cause a panic. type Info struct { + Pure bool // is the function pure? (can it be memoized?) + Memo bool // should the function be memoized? (false if too much output) + Fast bool // is the function fast? (avoid speculative execution) + Spec bool // can we speculatively execute it? (true for most) Output *types.Type // output value type (must not change over time!) Err error // did this fact validate? } diff --git a/lang/funcs/facts/func.go b/lang/funcs/facts/func.go index 13495a3b..f1c7b53a 100644 --- a/lang/funcs/facts/func.go +++ b/lang/funcs/facts/func.go @@ -64,8 +64,10 @@ func (obj *FactFunc) Validate() error { // Info returns some static info about itself. func (obj *FactFunc) Info() *interfaces.Info { return &interfaces.Info{ - Pure: false, - Memo: false, + Pure: obj.Fact.Info().Pure, + Memo: obj.Fact.Info().Memo, + Fast: obj.Fact.Info().Fast, + Spec: obj.Fact.Info().Spec, Sig: &types.Type{ Kind: types.KindFunc, // if Ord or Map are nil, this will panic things! diff --git a/lang/funcs/funcgen/fixtures/func_base.tpl b/lang/funcs/funcgen/fixtures/func_base.tpl index db992dd3..77327695 100644 --- a/lang/funcs/funcgen/fixtures/func_base.tpl +++ b/lang/funcs/funcgen/fixtures/func_base.tpl @@ -40,26 +40,68 @@ import ( func init() { simple.ModuleRegister("golang/testpkg", "all_kind", &simple.Scaffold{ + // XXX: pull these from a database, remove the impure functions + I: &simple.Info{ + Pure: true, + Memo: true, + Fast: true, + Spec: true, + }, T: types.NewType("func(x int, y str) float"), F: TestpkgAllKind, }) simple.ModuleRegister("golang/testpkg", "to_upper", &simple.Scaffold{ + // XXX: pull these from a database, remove the impure functions + I: &simple.Info{ + Pure: true, + Memo: true, + Fast: true, + Spec: true, + }, T: types.NewType("func(s str) str"), F: TestpkgToUpper, }) simple.ModuleRegister("golang/testpkg", "max", &simple.Scaffold{ + // XXX: pull these from a database, remove the impure functions + I: &simple.Info{ + Pure: true, + Memo: true, + Fast: true, + Spec: true, + }, T: types.NewType("func(x float, y float) float"), F: TestpkgMax, }) simple.ModuleRegister("golang/testpkg", "with_error", &simple.Scaffold{ + // XXX: pull these from a database, remove the impure functions + I: &simple.Info{ + Pure: true, + Memo: true, + Fast: true, + Spec: true, + }, T: types.NewType("func(s str) str"), F: TestpkgWithError, }) simple.ModuleRegister("golang/testpkg", "with_int", &simple.Scaffold{ + // XXX: pull these from a database, remove the impure functions + I: &simple.Info{ + Pure: true, + Memo: true, + Fast: true, + Spec: true, + }, T: types.NewType("func(s float, i int, x int, j int, k int, b bool, t str) str"), F: TestpkgWithInt, }) simple.ModuleRegister("golang/testpkg", "super_byte", &simple.Scaffold{ + // XXX: pull these from a database, remove the impure functions + I: &simple.Info{ + Pure: true, + Memo: true, + Fast: true, + Spec: true, + }, T: types.NewType("func(s str, t str) str"), F: TestpkgSuperByte, }) diff --git a/lang/funcs/funcgen/templates/generated_funcs.go.tpl b/lang/funcs/funcgen/templates/generated_funcs.go.tpl index 4cca6bf8..6b44e0cb 100644 --- a/lang/funcs/funcgen/templates/generated_funcs.go.tpl +++ b/lang/funcs/funcgen/templates/generated_funcs.go.tpl @@ -40,6 +40,13 @@ import ( func init() { {{ range $i, $func := .Functions }} simple.ModuleRegister("{{$func.MgmtPackage}}", "{{$func.MclName}}", &simple.Scaffold{ + // XXX: pull these from a database, remove the impure functions + I: &simple.Info{ + Pure: true, + Memo: true, + Fast: true, + Spec: true, + }, T: types.NewType("{{$func.Signature}}"), F: {{$func.InternalName}}, }) diff --git a/lang/funcs/funcs.go b/lang/funcs/funcs.go index 183bf504..1a1e5c3d 100644 --- a/lang/funcs/funcs.go +++ b/lang/funcs/funcs.go @@ -38,6 +38,7 @@ import ( docsUtil "github.com/purpleidea/mgmt/docs/util" "github.com/purpleidea/mgmt/lang/interfaces" + "github.com/purpleidea/mgmt/util" ) const ( @@ -113,6 +114,15 @@ const ( // CollectFuncOutType is the expected return type, the data field is an // encoded resource blob. CollectFuncOutType = "[]" + CollectFuncOutStruct + + // ErrCantSpeculate is an error that explains that we can't speculate + // when trying to Call a function. This often gets called by the Value() + // method of the Expr. This can be useful if we want to distinguish + // between "something is broken" and "I just can't produce a value at + // this time", which can be identified and skipped over. If it's the + // former, then it's okay to error early and shut everything down since + // we know this function is never going to work the way it's called. + ErrCantSpeculate = util.Error("can't speculate") ) // registeredFuncs is a global map of all possible funcs which can be used. You diff --git a/lang/funcs/multi/multi.go b/lang/funcs/multi/multi.go index 08bc3940..93440e9a 100644 --- a/lang/funcs/multi/multi.go +++ b/lang/funcs/multi/multi.go @@ -45,9 +45,20 @@ import ( // RegisteredFuncs maps a function name to the corresponding function scaffold. var RegisteredFuncs = make(map[string]*Scaffold) // must initialize +// Info holds some information about this function. +type Info struct { + Pure bool // is the function pure? (can it be memoized?) + Memo bool // should the function be memoized? (false if too much output) + Fast bool // is the function slow? (avoid speculative execution) + Spec bool // can we speculatively execute it? (true for most) +} + // Scaffold holds the necessary data to build a (possibly polymorphic) function // with this API. type Scaffold struct { + // I is some general info about the function. + I *Info + // T is the type of the function. It can include unification variables. // At a minimum, this must be a `func(?1) ?2` as a naked `?1` is not // allowed. (TODO: Because of ArgGen.) @@ -102,12 +113,23 @@ func Register(name string, scaffold *Scaffold) { panic(fmt.Sprintf("could not locate function filename for %s", name)) } + funcInfo := &wrapped.Info{} + if scaffold.I != nil { + funcInfo = &wrapped.Info{ + Pure: scaffold.I.Pure, + Memo: scaffold.I.Memo, + Fast: scaffold.I.Fast, + Spec: scaffold.I.Spec, + } + } + // register a copy in the main function database funcs.Register(name, func() interfaces.Func { return &Func{ Metadata: metadata, WrappedFunc: &wrapped.Func{ - Name: name, + Name: name, + FuncInfo: funcInfo, // NOTE: It might be more correct to Copy here, // but we do the copy inside of ExprFunc.Copy() // instead, so that the same type can be unified diff --git a/lang/funcs/operators/operators.go b/lang/funcs/operators/operators.go index b0731421..954d34b4 100644 --- a/lang/funcs/operators/operators.go +++ b/lang/funcs/operators/operators.go @@ -56,7 +56,14 @@ const ( ) func init() { + info := &simple.Info{ + Pure: true, + Memo: true, + Fast: true, + Spec: true, + } RegisterOperator("+", &simple.Scaffold{ + I: info, T: types.NewType("func(?1, ?1) ?1"), C: simple.TypeMatch([]string{ "func(str, str) str", // concatenation @@ -91,6 +98,7 @@ func init() { }) RegisterOperator("-", &simple.Scaffold{ + I: info, T: types.NewType("func(?1, ?1) ?1"), C: simple.TypeMatch([]string{ "func(int, int) int", // subtraction @@ -115,6 +123,7 @@ func init() { }) RegisterOperator("*", &simple.Scaffold{ + I: info, T: types.NewType("func(?1, ?1) ?1"), C: simple.TypeMatch([]string{ "func(int, int) int", // multiplication @@ -141,6 +150,7 @@ func init() { // don't add: `func(int, float) float` or: `func(float, int) float` RegisterOperator("/", &simple.Scaffold{ + I: info, T: types.NewType("func(?1, ?1) float"), C: simple.TypeMatch([]string{ "func(int, int) float", // division @@ -173,6 +183,7 @@ func init() { }) RegisterOperator("==", &simple.Scaffold{ + I: info, T: types.NewType("func(?1, ?1) bool"), C: func(typ *types.Type) error { //if typ == nil { // happens within iter @@ -218,6 +229,7 @@ func init() { }) RegisterOperator("!=", &simple.Scaffold{ + I: info, T: types.NewType("func(?1, ?1) bool"), C: func(typ *types.Type) error { //if typ == nil { // happens within iter @@ -263,6 +275,7 @@ func init() { }) RegisterOperator("<", &simple.Scaffold{ + I: info, T: types.NewType("func(?1, ?1) bool"), C: simple.TypeMatch([]string{ "func(int, int) bool", // less-than @@ -288,6 +301,7 @@ func init() { }) RegisterOperator(">", &simple.Scaffold{ + I: info, T: types.NewType("func(?1, ?1) bool"), C: simple.TypeMatch([]string{ "func(int, int) bool", // greater-than @@ -313,6 +327,7 @@ func init() { }) RegisterOperator("<=", &simple.Scaffold{ + I: info, T: types.NewType("func(?1, ?1) bool"), C: simple.TypeMatch([]string{ "func(int, int) bool", // less-than-equal @@ -338,6 +353,7 @@ func init() { }) RegisterOperator(">=", &simple.Scaffold{ + I: info, T: types.NewType("func(?1, ?1) bool"), C: simple.TypeMatch([]string{ "func(int, int) bool", // greater-than-equal @@ -366,6 +382,7 @@ func init() { // TODO: is there a way for the engine to have // short-circuit operators, and does it matter? RegisterOperator("and", &simple.Scaffold{ + I: info, T: types.NewType("func(bool, bool) bool"), F: func(ctx context.Context, input []types.Value) (types.Value, error) { return &types.BoolValue{ @@ -376,6 +393,7 @@ func init() { // logical or RegisterOperator("or", &simple.Scaffold{ + I: info, T: types.NewType("func(bool, bool) bool"), F: func(ctx context.Context, input []types.Value) (types.Value, error) { return &types.BoolValue{ @@ -386,6 +404,7 @@ func init() { // logical not (unary operator) RegisterOperator("not", &simple.Scaffold{ + I: info, T: types.NewType("func(bool) bool"), F: func(ctx context.Context, input []types.Value) (types.Value, error) { return &types.BoolValue{ @@ -396,6 +415,7 @@ func init() { // pi operator (this is an easter egg to demo a zero arg operator) RegisterOperator("π", &simple.Scaffold{ + I: info, T: types.NewType("func() float"), F: func(ctx context.Context, input []types.Value) (types.Value, error) { return &types.FloatValue{ @@ -645,8 +665,11 @@ func (obj *OperatorFunc) Info() *interfaces.Info { // avoid an accidental return of unification variables when we should be // getting them from FuncInfer, and not from here. (During unification!) return &interfaces.Info{ + // XXX: get these from the scaffold Pure: true, - Memo: false, + Memo: true, + Fast: true, + Spec: true, Sig: obj.Type, // func kind, which includes operator arg as input Err: obj.Validate(), } diff --git a/lang/funcs/simple/simple.go b/lang/funcs/simple/simple.go index b6c7effb..eaf94d7b 100644 --- a/lang/funcs/simple/simple.go +++ b/lang/funcs/simple/simple.go @@ -54,9 +54,20 @@ const ( // RegisteredFuncs maps a function name to the corresponding function scaffold. var RegisteredFuncs = make(map[string]*Scaffold) // must initialize +// Info holds some information about this function. +type Info struct { + Pure bool // is the function pure? (can it be memoized?) + Memo bool // should the function be memoized? (false if too much output) + Fast bool // is the function slow? (avoid speculative execution) + Spec bool // can we speculatively execute it? (true for most) +} + // Scaffold holds the necessary data to build a (possibly polymorphic) function // with this API. type Scaffold struct { + // I is some general info about the function. + I *Info + // T is the type of the function. It can include unification variables. // At a minimum, this must be a `func(?1) ?2` as a naked `?1` is not // allowed. (TODO: Because of ArgGen.) @@ -124,12 +135,23 @@ func Register(name string, scaffold *Scaffold) { panic(fmt.Sprintf("could not locate function filename for %s", name)) } + funcInfo := &wrapped.Info{} + if scaffold.I != nil { + funcInfo = &wrapped.Info{ + Pure: scaffold.I.Pure, + Memo: scaffold.I.Memo, + Fast: scaffold.I.Fast, + Spec: scaffold.I.Spec, + } + } + // register a copy in the main function database funcs.Register(name, func() interfaces.Func { return &Func{ Metadata: metadata, WrappedFunc: &wrapped.Func{ - Name: name, + Name: name, + FuncInfo: funcInfo, // NOTE: It might be more correct to Copy here, // but we do the copy inside of ExprFunc.Copy() // instead, so that the same type can be unified diff --git a/lang/funcs/structs/call.go b/lang/funcs/structs/call.go index e471ace6..d0a1eb62 100644 --- a/lang/funcs/structs/call.go +++ b/lang/funcs/structs/call.go @@ -223,7 +223,7 @@ func (obj *CallFunc) replaceSubGraph(newFuncValue *full.FuncValue) error { // methods called on it. Nothing else. It will _not_ call Commit or // Reverse. It adds to the graph, and our Commit and Reverse operations // are the ones that actually make the change. - outputFunc, err := newFuncValue.Call(obj.init.Txn, obj.ArgVertices) + outputFunc, err := newFuncValue.CallWithFuncs(obj.init.Txn, obj.ArgVertices) if err != nil { return errwrap.Wrapf(err, "could not call newFuncValue.Call()") } diff --git a/lang/funcs/structs/util.go b/lang/funcs/structs/util.go index 932192b9..a68b25e1 100644 --- a/lang/funcs/structs/util.go +++ b/lang/funcs/structs/util.go @@ -74,6 +74,7 @@ func SimpleFnToFuncValue(name string, fv *types.FuncValue) *full.FuncValue { } return wrappedFunc, nil }, + F: nil, // unused T: fv.T, } } @@ -84,3 +85,42 @@ func SimpleFnToFuncValue(name string, fv *types.FuncValue) *full.FuncValue { func SimpleFnToConstFunc(name string, fv *types.FuncValue) interfaces.Func { return FuncValueToConstFunc(SimpleFnToFuncValue(name, fv)) } + +// FuncToFullFuncValue creates a *full.FuncValue which adds the given +// interfaces.Func to the graph. Note that this means the *full.FuncValue can +// only be called once. +func FuncToFullFuncValue(makeFunc func() interfaces.Func, typ *types.Type) *full.FuncValue { + + v := func(txn interfaces.Txn, args []interfaces.Func) (interfaces.Func, error) { + valueTransformingFunc := makeFunc() // do this once here + buildableFunc, ok := valueTransformingFunc.(interfaces.BuildableFunc) + if ok { + // Set the type in case it's not already done. + if _, err := buildableFunc.Build(typ); err != nil { + // programming error? + return nil, err + } + } + for i, arg := range args { + argName := typ.Ord[i] + txn.AddEdge(arg, valueTransformingFunc, &interfaces.FuncEdge{ + Args: []string{argName}, + }) + } + return valueTransformingFunc, nil + } + + var f interfaces.FuncSig + callableFunc, ok := makeFunc().(interfaces.CallableFunc) + if ok { + f = callableFunc.Call + } + + // This has the "V" implementation and the simpler "F" implementation + // which can occasionally be used if the interfaces.Func supports that! + return &full.FuncValue{ + V: v, + F: f, + T: typ, + } +} diff --git a/lang/funcs/wrapped/wrapped.go b/lang/funcs/wrapped/wrapped.go index 2c5f8eda..79a2796c 100644 --- a/lang/funcs/wrapped/wrapped.go +++ b/lang/funcs/wrapped/wrapped.go @@ -39,6 +39,14 @@ import ( var _ interfaces.Func = &Func{} // ensure it meets this expectation +// Info holds some information about this function. +type Info struct { + Pure bool // is the function pure? (can it be memoized?) + Memo bool // should the function be memoized? (false if too much output) + Fast bool // is the function slow? (avoid speculative execution) + Spec bool // can we speculatively execute it? (true for most) +} + // Func is a wrapped scaffolding function struct which fulfills the boiler-plate // for the function API, but that can run a very simple, static, pure, function. // It can be wrapped by other structs that support polymorphism in various ways. @@ -48,6 +56,9 @@ type Func struct { // Name is a unique string name for the function. Name string + // Info is some general info about the function. + FuncInfo *Info + // Type is the type of the function. It can include unification // variables when this struct is wrapped in one that can build this out. Type *types.Type @@ -64,7 +75,16 @@ type Func struct { // String returns a simple name for this function. This is needed so this struct // can satisfy the pgraph.Vertex interface. func (obj *Func) String() string { - return fmt.Sprintf("%s @ %p", obj.Name, obj) // be more unique! + //if obj.Fn != nil { // TODO: would this work and be useful? + // return fmt.Sprintf("%s: %s", obj.Name, obj.Fn) + //} + //if obj.Type != nil { // TODO: would this work and be useful? + // return fmt.Sprintf("%s: %s", obj.Name, obj.Type) + //} + if obj.Name == "" { + return "" + } + return obj.Name } // ArgGen returns the Nth arg name for this function. @@ -104,12 +124,21 @@ func (obj *Func) Info() *interfaces.Info { typ = obj.Fn.Type() } - return &interfaces.Info{ - Pure: true, - Memo: false, // TODO: should this be something we specify here? + info := &interfaces.Info{ + Pure: false, + Memo: false, + Fast: false, + Spec: false, Sig: typ, Err: obj.Validate(), } + if fi := obj.FuncInfo; fi != nil { + info.Pure = fi.Pure + info.Memo = fi.Memo + info.Fast = fi.Fast + info.Spec = fi.Spec + } + return info } // Init runs some startup code for this function. @@ -184,5 +213,9 @@ func (obj *Func) Stream(ctx context.Context) error { // Call this function with the input args and return the value if it is possible // to do so at this time. func (obj *Func) Call(ctx context.Context, args []types.Value) (types.Value, error) { + if obj.Fn == nil { + // happens with speculative graph shape code paths + return nil, fmt.Errorf("nil function") + } return obj.Fn.Call(ctx, args) // (Value, error) } diff --git a/lang/interfaces/ast.go b/lang/interfaces/ast.go index 55cd1171..4ad7335a 100644 --- a/lang/interfaces/ast.go +++ b/lang/interfaces/ast.go @@ -508,6 +508,41 @@ func (obj *FuncSingleton) GraphFunc() (*pgraph.Graph, Func, error) { return obj.g, obj.f, nil } +// ValueEnv is a future potential argument to the Value() method on Expr. +// XXX: Consider using this if we want to improve graph shape even more. +type ValueEnv struct { + Variables map[Expr]types.Value +} + +// EmptyValueEnv returns the zero, empty value for the value env, with all the +// internal maps initialized appropriately. +func EmptyValueEnv() *ValueEnv { + return &ValueEnv{ + Variables: make(map[Expr]types.Value), + } +} + +// Copy makes a copy of the ValueEnv struct. This ensures that if the internal +// maps are changed, it doesn't affect other copies of the ValueEnv. 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 *ValueEnv) Copy() *ValueEnv { + if obj == nil { // allow copying nil envs + return EmptyValueEnv() + } + + variables := make(map[Expr]types.Value) + + for k, v := range obj.Variables { // copy + variables[k] = v // we don't copy the values! + } + + return &ValueEnv{ + Variables: variables, + } +} + // Arg represents a name identifier for a func or class argument declaration and // is sometimes accompanied by a type. This does not satisfy the Expr interface. type Arg struct { diff --git a/lang/interfaces/func.go b/lang/interfaces/func.go index ed111776..7d8779ee 100644 --- a/lang/interfaces/func.go +++ b/lang/interfaces/func.go @@ -44,6 +44,10 @@ import ( // FuncSig is the simple signature that is used throughout our implementations. type FuncSig = func(context.Context, []types.Value) (types.Value, error) +// GraphSig is the simple signature that is used throughout our implementations. +// TODO: Rename this? +type GraphSig = func(Txn, []Func) (Func, error) + // Compile-time guarantee that *types.FuncValue accepts a func of type FuncSig. var _ = &types.FuncValue{V: FuncSig(nil)} @@ -53,7 +57,8 @@ var _ = &types.FuncValue{V: FuncSig(nil)} type Info struct { Pure bool // is the function pure? (can it be memoized?) Memo bool // should the function be memoized? (false if too much output) - Slow bool // is the function slow? (avoid speculative execution) + Fast bool // is the function slow? (avoid speculative execution) + Spec bool // can we speculatively execute it? (true for most) Sig *types.Type // the signature of the function, must be KindFunc Err error // is this a valid function, or was it created improperly? } @@ -116,6 +121,8 @@ type Func interface { // It must close the Output chan if it's done sending new values out. It // must send at least one value, or return an error. It may also return // an error at anytime if it can't continue. + // XXX: Remove this from here, it should appear as StreamableFunc and + // funcs should implement StreamableFunc or CallableFunc or maybe both. Stream(context.Context) error } @@ -137,6 +144,7 @@ type Func interface { // the InferableFunc interface as well. type BuildableFunc interface { Func // implement everything in Func but add the additional requirements + // XXX: Should this be CopyableFunc instead? // Build takes the known or unified type signature for this function and // finalizes this structure so that it is now determined, and ready to @@ -273,7 +281,7 @@ type FuncData struct { // special functions that are useful in core. // TODO: This could be replaced if a func ever needs a SetScope method... type DataFunc interface { - Func // implement everything in Func but add the additional requirements + CopyableFunc // implement everything, but make it also have Copy // SetData is used by the language to pass our function some code-level // context. diff --git a/lang/interpret_test/TestAstFunc1/changing-func.txtar b/lang/interpret_test/TestAstFunc1/changing-func.txtar index d482a522..859c76b8 100644 --- a/lang/interpret_test/TestAstFunc1/changing-func.txtar +++ b/lang/interpret_test/TestAstFunc1/changing-func.txtar @@ -21,15 +21,21 @@ $out2 = $fn2() test "${out1}" {} test "${out2}" {} -- OUTPUT -- -Edge: FuncValue -> call # fn -Edge: FuncValue -> call # fn -Edge: call -> call # fn -Edge: call -> call # fn +Edge: FuncValue -> if # a +Edge: FuncValue -> if # a +Edge: FuncValue -> if # b +Edge: FuncValue -> if # b +Edge: const: bool(false) -> if # c +Edge: const: bool(true) -> if # c +Edge: if -> call # fn +Edge: if -> call # fn +Vertex: FuncValue +Vertex: FuncValue Vertex: FuncValue Vertex: FuncValue -Vertex: call -Vertex: call Vertex: call Vertex: call Vertex: const: bool(false) Vertex: const: bool(true) +Vertex: if +Vertex: if diff --git a/lang/interpret_test/TestAstFunc1/classes-polyfuncs.txtar b/lang/interpret_test/TestAstFunc1/classes-polyfuncs.txtar index 496d052a..2e1272e7 100644 --- a/lang/interpret_test/TestAstFunc1/classes-polyfuncs.txtar +++ b/lang/interpret_test/TestAstFunc1/classes-polyfuncs.txtar @@ -6,17 +6,14 @@ class c1($b) { test [fmt.printf("len is: %d", len($b)),] {} # len is 4 } -- OUTPUT -- -Edge: FuncValue -> call # fn -Edge: FuncValue -> call # fn -Edge: call -> composite: []str # 0 +Edge: composite: []int -> len # 0 Edge: const: int(-37) -> composite: []int # 3 Edge: const: int(0) -> composite: []int # 2 Edge: const: int(13) -> composite: []int # 0 Edge: const: int(42) -> composite: []int # 1 -Vertex: FuncValue -Vertex: FuncValue -Vertex: call -Vertex: call +Edge: const: str("len is: %d") -> printf: func(format str, a int) str # format +Edge: len -> printf: func(format str, a int) str # a +Edge: printf: func(format str, a int) str -> composite: []str # 0 Vertex: composite: []int Vertex: composite: []str Vertex: const: int(-37) @@ -24,3 +21,5 @@ Vertex: const: int(0) Vertex: const: int(13) Vertex: const: int(42) Vertex: const: str("len is: %d") +Vertex: len +Vertex: printf: func(format str, a int) str diff --git a/lang/interpret_test/TestAstFunc1/doubleclass.txtar b/lang/interpret_test/TestAstFunc1/doubleclass.txtar index 39627921..0383ea4f 100644 --- a/lang/interpret_test/TestAstFunc1/doubleclass.txtar +++ b/lang/interpret_test/TestAstFunc1/doubleclass.txtar @@ -15,36 +15,42 @@ include foo(1) include foo(2) include foo(3) -- OUTPUT -- -Edge: FuncValue -> call # fn -Edge: FuncValue -> call # fn -Edge: FuncValue -> call # fn -Edge: FuncValue -> call # fn -Edge: FuncValue -> call # fn -Edge: FuncValue -> call # fn -Edge: FuncValue -> call # fn -Edge: FuncValue -> call # fn -Edge: FuncValue -> call # fn -Edge: call -> composite: []str # 0 -Edge: call -> composite: []str # 0 -Edge: call -> composite: []str # 0 -Vertex: FuncValue -Vertex: FuncValue -Vertex: FuncValue -Vertex: FuncValue -Vertex: FuncValue -Vertex: FuncValue -Vertex: FuncValue -Vertex: FuncValue -Vertex: FuncValue -Vertex: call -Vertex: call -Vertex: call -Vertex: call -Vertex: call -Vertex: call -Vertex: call -Vertex: call -Vertex: call +Edge: _operator -> _operator # a +Edge: _operator -> _operator # a +Edge: _operator -> _operator # a +Edge: _operator -> printf: func(format str, a int, b int) str # b +Edge: _operator -> printf: func(format str, a int, b int) str # b +Edge: _operator -> printf: func(format str, a int, b int) str # b +Edge: const: int(1) -> printf: func(format str, a int, b int) str # a +Edge: const: int(13) -> _operator # b +Edge: const: int(13) -> _operator # b +Edge: const: int(13) -> _operator # b +Edge: const: int(2) -> printf: func(format str, a int, b int) str # a +Edge: const: int(3) -> printf: func(format str, a int, b int) str # a +Edge: const: int(4) -> _operator # b +Edge: const: int(4) -> _operator # b +Edge: const: int(4) -> _operator # b +Edge: const: int(42) -> _operator # a +Edge: const: int(42) -> _operator # a +Edge: const: int(42) -> _operator # a +Edge: const: str("+") -> _operator # op +Edge: const: str("+") -> _operator # op +Edge: const: str("+") -> _operator # op +Edge: const: str("+") -> _operator # op +Edge: const: str("+") -> _operator # op +Edge: const: str("+") -> _operator # op +Edge: const: str("test-%d-%d") -> printf: func(format str, a int, b int) str # format +Edge: const: str("test-%d-%d") -> printf: func(format str, a int, b int) str # format +Edge: const: str("test-%d-%d") -> printf: func(format str, a int, b int) str # format +Edge: printf: func(format str, a int, b int) str -> composite: []str # 0 +Edge: printf: func(format str, a int, b int) str -> composite: []str # 0 +Edge: printf: func(format str, a int, b int) str -> composite: []str # 0 +Vertex: _operator +Vertex: _operator +Vertex: _operator +Vertex: _operator +Vertex: _operator +Vertex: _operator Vertex: composite: []str Vertex: composite: []str Vertex: composite: []str @@ -65,3 +71,6 @@ Vertex: const: str("+") Vertex: const: str("test-%d-%d") Vertex: const: str("test-%d-%d") Vertex: const: str("test-%d-%d") +Vertex: printf: func(format str, a int, b int) str +Vertex: printf: func(format str, a int, b int) str +Vertex: printf: func(format str, a int, b int) str diff --git a/lang/interpret_test/TestAstFunc1/duplicate_resource.txtar b/lang/interpret_test/TestAstFunc1/duplicate_resource.txtar index 9b38787e..e042698e 100644 --- a/lang/interpret_test/TestAstFunc1/duplicate_resource.txtar +++ b/lang/interpret_test/TestAstFunc1/duplicate_resource.txtar @@ -17,9 +17,7 @@ pkg "cowsay" { state => "newest", } -- OUTPUT -- -Edge: FuncValue -> call # fn -Vertex: FuncValue -Vertex: call +Edge: const: str("hello world") -> printf: func(format str) str # format Vertex: const: str("/tmp/foo") Vertex: const: str("/tmp/foo") Vertex: const: str("cowsay") @@ -28,3 +26,4 @@ Vertex: const: str("hello world") Vertex: const: str("hello world") Vertex: const: str("installed") Vertex: const: str("newest") +Vertex: printf: func(format str) str diff --git a/lang/interpret_test/TestAstFunc1/efficient-lambda.txtar b/lang/interpret_test/TestAstFunc1/efficient-lambda.txtar index ddb6f00b..e7e2e795 100644 --- a/lang/interpret_test/TestAstFunc1/efficient-lambda.txtar +++ b/lang/interpret_test/TestAstFunc1/efficient-lambda.txtar @@ -10,10 +10,17 @@ $out2 = $prefixer("b") test "${out1}" {} # helloa test "${out2}" {} # hellob -- OUTPUT -- -Edge: FuncValue -> call # fn -Edge: FuncValue -> call # fn -Vertex: FuncValue -Vertex: call -Vertex: call +Edge: const: str("+") -> _operator # op +Edge: const: str("+") -> _operator # op +Edge: const: str("a") -> _operator # b +Edge: const: str("b") -> _operator # b +Edge: const: str("hello") -> _operator # a +Edge: const: str("hello") -> _operator # a +Vertex: _operator +Vertex: _operator +Vertex: const: str("+") +Vertex: const: str("+") Vertex: const: str("a") Vertex: const: str("b") +Vertex: const: str("hello") +Vertex: const: str("hello") diff --git a/lang/interpret_test/TestAstFunc1/graph5.txtar b/lang/interpret_test/TestAstFunc1/graph5.txtar index 1d18f7c0..9ebc08cb 100644 --- a/lang/interpret_test/TestAstFunc1/graph5.txtar +++ b/lang/interpret_test/TestAstFunc1/graph5.txtar @@ -4,9 +4,10 @@ test "t" { int64ptr => 42 + 13, } -- OUTPUT -- -Edge: FuncValue -> call # fn -Vertex: FuncValue -Vertex: call +Edge: const: int(13) -> _operator # b +Edge: const: int(42) -> _operator # a +Edge: const: str("+") -> _operator # op +Vertex: _operator Vertex: const: int(13) Vertex: const: int(42) Vertex: const: str("+") diff --git a/lang/interpret_test/TestAstFunc1/graph6.txtar b/lang/interpret_test/TestAstFunc1/graph6.txtar index 6d97d88b..48515047 100644 --- a/lang/interpret_test/TestAstFunc1/graph6.txtar +++ b/lang/interpret_test/TestAstFunc1/graph6.txtar @@ -4,12 +4,14 @@ test "t" { int64ptr => 42 + 13 - 99, } -- OUTPUT -- -Edge: FuncValue -> call # fn -Edge: FuncValue -> call # fn -Vertex: FuncValue -Vertex: FuncValue -Vertex: call -Vertex: call +Edge: _operator -> _operator # a +Edge: const: int(13) -> _operator # b +Edge: const: int(42) -> _operator # a +Edge: const: int(99) -> _operator # b +Edge: const: str("+") -> _operator # op +Edge: const: str("-") -> _operator # op +Vertex: _operator +Vertex: _operator Vertex: const: int(13) Vertex: const: int(42) Vertex: const: int(99) diff --git a/lang/interpret_test/TestAstFunc1/graph7.txtar b/lang/interpret_test/TestAstFunc1/graph7.txtar index 97677968..ef239ae7 100644 --- a/lang/interpret_test/TestAstFunc1/graph7.txtar +++ b/lang/interpret_test/TestAstFunc1/graph7.txtar @@ -8,9 +8,10 @@ if true { } $i = 13 -- OUTPUT -- -Edge: FuncValue -> call # fn -Vertex: FuncValue -Vertex: call +Edge: const: int(13) -> _operator # b +Edge: const: int(42) -> _operator # a +Edge: const: str("+") -> _operator # op +Vertex: _operator Vertex: const: bool(true) Vertex: const: int(13) Vertex: const: int(42) diff --git a/lang/interpret_test/TestAstFunc1/hello0.txtar b/lang/interpret_test/TestAstFunc1/hello0.txtar index 94440bf4..251aadd3 100644 --- a/lang/interpret_test/TestAstFunc1/hello0.txtar +++ b/lang/interpret_test/TestAstFunc1/hello0.txtar @@ -7,9 +7,9 @@ test "greeting" { anotherstr => fmt.printf("hello: %s", $s), } -- OUTPUT -- -Edge: FuncValue -> call # fn -Vertex: FuncValue -Vertex: call +Edge: const: str("hello: %s") -> printf: func(format str, a str) str # format +Edge: const: str("world") -> printf: func(format str, a str) str # a Vertex: const: str("greeting") Vertex: const: str("hello: %s") Vertex: const: str("world") +Vertex: printf: func(format str, a str) str diff --git a/lang/interpret_test/TestAstFunc1/importscope0.txtar b/lang/interpret_test/TestAstFunc1/importscope0.txtar index ec34957e..dc02cd8b 100644 --- a/lang/interpret_test/TestAstFunc1/importscope0.txtar +++ b/lang/interpret_test/TestAstFunc1/importscope0.txtar @@ -16,13 +16,11 @@ class xclass { } } -- OUTPUT -- -Edge: FuncValue -> call # fn -Edge: call -> if # c Edge: const: str("bbb") -> if # a Edge: const: str("ccc") -> if # b -Vertex: FuncValue -Vertex: call +Edge: os.is_family_debian -> if # c Vertex: const: str("bbb") Vertex: const: str("ccc") Vertex: const: str("hello") Vertex: if +Vertex: os.is_family_debian diff --git a/lang/interpret_test/TestAstFunc1/importscope2.txtar b/lang/interpret_test/TestAstFunc1/importscope2.txtar index 79acdda8..4a06ba4c 100644 --- a/lang/interpret_test/TestAstFunc1/importscope2.txtar +++ b/lang/interpret_test/TestAstFunc1/importscope2.txtar @@ -15,13 +15,11 @@ class xclass { } } -- OUTPUT -- -Edge: FuncValue -> call # fn -Edge: call -> if # c Edge: const: str("bbb") -> if # a Edge: const: str("ccc") -> if # b -Vertex: FuncValue -Vertex: call +Edge: os.is_family_debian -> if # c Vertex: const: str("bbb") Vertex: const: str("ccc") Vertex: const: str("hello") Vertex: if +Vertex: os.is_family_debian diff --git a/lang/interpret_test/TestAstFunc1/lambda-chained.txtar b/lang/interpret_test/TestAstFunc1/lambda-chained.txtar index 19da0824..f62f0452 100644 --- a/lang/interpret_test/TestAstFunc1/lambda-chained.txtar +++ b/lang/interpret_test/TestAstFunc1/lambda-chained.txtar @@ -12,9 +12,27 @@ $out2 = $prefixer($out1) test "${out1}" {} test "${out2}" {} -- OUTPUT -- -Edge: FuncValue -> call # fn -Edge: FuncValue -> call # fn -Vertex: FuncValue -Vertex: call -Vertex: call +Edge: _operator -> _operator # a +Edge: _operator -> _operator # a +Edge: _operator -> _operator # b +Edge: const: str("+") -> _operator # op +Edge: const: str("+") -> _operator # op +Edge: const: str("+") -> _operator # op +Edge: const: str("+") -> _operator # op +Edge: const: str(":") -> _operator # b +Edge: const: str(":") -> _operator # b +Edge: const: str("hello") -> _operator # a +Edge: const: str("hello") -> _operator # a +Edge: const: str("world") -> _operator # b +Vertex: _operator +Vertex: _operator +Vertex: _operator +Vertex: _operator +Vertex: const: str("+") +Vertex: const: str("+") +Vertex: const: str("+") +Vertex: const: str("+") +Vertex: const: str(":") +Vertex: const: str(":") +Vertex: const: str("hello") Vertex: const: str("world") diff --git a/lang/interpret_test/TestAstFunc1/module_search1.txtar b/lang/interpret_test/TestAstFunc1/module_search1.txtar index 3f3a97bd..4e3f770f 100644 --- a/lang/interpret_test/TestAstFunc1/module_search1.txtar +++ b/lang/interpret_test/TestAstFunc1/module_search1.txtar @@ -62,27 +62,29 @@ $ex1 = $example1.name $name = "i am github.com/purpleidea/mgmt-example2/ and i contain: " + $ex1 -- OUTPUT -- -Edge: FuncValue -> call # fn -Edge: FuncValue -> call # fn -Edge: FuncValue -> call # fn -Edge: FuncValue -> call # fn -Edge: FuncValue -> call # fn -Edge: FuncValue -> call # fn -Edge: FuncValue -> call # fn -Vertex: FuncValue -Vertex: FuncValue -Vertex: FuncValue -Vertex: FuncValue -Vertex: FuncValue -Vertex: FuncValue -Vertex: FuncValue -Vertex: call -Vertex: call -Vertex: call -Vertex: call -Vertex: call -Vertex: call -Vertex: call +Edge: _operator -> printf: func(format str, a int) str # a +Edge: _operator -> printf: func(format str, a str) str # a +Edge: _operator -> printf: func(format str, a str, b str) str # a +Edge: _operator -> printf: func(format str, a str, b str) str # b +Edge: const: int(3) -> _operator # b +Edge: const: int(42) -> _operator # a +Edge: const: str("+") -> _operator # op +Edge: const: str("+") -> _operator # op +Edge: const: str("+") -> _operator # op +Edge: const: str("+") -> _operator # op +Edge: const: str("i am github.com/purpleidea/mgmt-example1/ and i contain: ") -> _operator # a +Edge: const: str("i am github.com/purpleidea/mgmt-example1/ and i contain: ") -> _operator # a +Edge: const: str("i imported local: %s") -> printf: func(format str, a str) str # format +Edge: const: str("i imported remote: %s and %s") -> printf: func(format str, a str, b str) str # format +Edge: const: str("the answer is: %d") -> printf: func(format str, a int) str # format +Edge: const: str("this is module mod1 which contains: ") -> _operator # a +Edge: const: str("this is the nested git module mod1") -> _operator # b +Edge: const: str("this is the nested git module mod1") -> _operator # b +Edge: const: str("this is the nested local module mod1") -> _operator # b +Vertex: _operator +Vertex: _operator +Vertex: _operator +Vertex: _operator Vertex: const: int(3) Vertex: const: int(42) Vertex: const: str("+") @@ -101,3 +103,6 @@ Vertex: const: str("this is module mod1 which contains: ") Vertex: const: str("this is the nested git module mod1") Vertex: const: str("this is the nested git module mod1") Vertex: const: str("this is the nested local module mod1") +Vertex: printf: func(format str, a int) str +Vertex: printf: func(format str, a str) str +Vertex: printf: func(format str, a str, b str) str diff --git a/lang/interpret_test/TestAstFunc1/polydoubleinclude.txtar b/lang/interpret_test/TestAstFunc1/polydoubleinclude.txtar index ca0da33c..d933b7f5 100644 --- a/lang/interpret_test/TestAstFunc1/polydoubleinclude.txtar +++ b/lang/interpret_test/TestAstFunc1/polydoubleinclude.txtar @@ -10,22 +10,16 @@ class c1($a, $b) { } } -- OUTPUT -- -Edge: FuncValue -> call # fn -Edge: FuncValue -> call # fn -Edge: FuncValue -> call # fn -Edge: FuncValue -> call # fn +Edge: composite: []int -> len # 0 Edge: const: int(-37) -> composite: []int # 3 Edge: const: int(0) -> composite: []int # 2 Edge: const: int(13) -> composite: []int # 0 Edge: const: int(42) -> composite: []int # 1 -Vertex: FuncValue -Vertex: FuncValue -Vertex: FuncValue -Vertex: FuncValue -Vertex: call -Vertex: call -Vertex: call -Vertex: call +Edge: const: str("hello") -> len # 0 +Edge: const: str("len is: %d") -> printf: func(format str, a int) str # format +Edge: const: str("len is: %d") -> printf: func(format str, a int) str # format +Edge: len -> printf: func(format str, a int) str # a +Edge: len -> printf: func(format str, a int) str # a Vertex: composite: []int Vertex: const: int(-37) Vertex: const: int(0) @@ -36,3 +30,7 @@ Vertex: const: str("len is: %d") Vertex: const: str("len is: %d") Vertex: const: str("t1") Vertex: const: str("t2") +Vertex: len +Vertex: len +Vertex: printf: func(format str, a int) str +Vertex: printf: func(format str, a int) str diff --git a/lang/interpret_test/TestAstFunc1/recursive_module1.txtar b/lang/interpret_test/TestAstFunc1/recursive_module1.txtar index fb306f56..2fc6f798 100644 --- a/lang/interpret_test/TestAstFunc1/recursive_module1.txtar +++ b/lang/interpret_test/TestAstFunc1/recursive_module1.txtar @@ -60,27 +60,29 @@ $ex1 = $example1.name $name = "i am github.com/purpleidea/mgmt-example2/ and i contain: " + $ex1 -- OUTPUT -- -Edge: FuncValue -> call # fn -Edge: FuncValue -> call # fn -Edge: FuncValue -> call # fn -Edge: FuncValue -> call # fn -Edge: FuncValue -> call # fn -Edge: FuncValue -> call # fn -Edge: FuncValue -> call # fn -Vertex: FuncValue -Vertex: FuncValue -Vertex: FuncValue -Vertex: FuncValue -Vertex: FuncValue -Vertex: FuncValue -Vertex: FuncValue -Vertex: call -Vertex: call -Vertex: call -Vertex: call -Vertex: call -Vertex: call -Vertex: call +Edge: _operator -> printf: func(format str, a int) str # a +Edge: _operator -> printf: func(format str, a str) str # a +Edge: _operator -> printf: func(format str, a str, b str) str # a +Edge: _operator -> printf: func(format str, a str, b str) str # b +Edge: const: int(3) -> _operator # b +Edge: const: int(42) -> _operator # a +Edge: const: str("+") -> _operator # op +Edge: const: str("+") -> _operator # op +Edge: const: str("+") -> _operator # op +Edge: const: str("+") -> _operator # op +Edge: const: str("i am github.com/purpleidea/mgmt-example1/ and i contain: ") -> _operator # a +Edge: const: str("i am github.com/purpleidea/mgmt-example1/ and i contain: ") -> _operator # a +Edge: const: str("i imported local: %s") -> printf: func(format str, a str) str # format +Edge: const: str("i imported remote: %s and %s") -> printf: func(format str, a str, b str) str # format +Edge: const: str("the answer is: %d") -> printf: func(format str, a int) str # format +Edge: const: str("this is module mod1 which contains: ") -> _operator # a +Edge: const: str("this is the nested git module mod1") -> _operator # b +Edge: const: str("this is the nested git module mod1") -> _operator # b +Edge: const: str("this is the nested local module mod1") -> _operator # b +Vertex: _operator +Vertex: _operator +Vertex: _operator +Vertex: _operator Vertex: const: int(3) Vertex: const: int(42) Vertex: const: str("+") @@ -99,3 +101,6 @@ Vertex: const: str("this is module mod1 which contains: ") Vertex: const: str("this is the nested git module mod1") Vertex: const: str("this is the nested git module mod1") Vertex: const: str("this is the nested local module mod1") +Vertex: printf: func(format str, a int) str +Vertex: printf: func(format str, a str) str +Vertex: printf: func(format str, a str, b str) str diff --git a/lang/interpret_test/TestAstFunc1/returned-func.txtar b/lang/interpret_test/TestAstFunc1/returned-func.txtar index dfb42837..5b882b19 100644 --- a/lang/interpret_test/TestAstFunc1/returned-func.txtar +++ b/lang/interpret_test/TestAstFunc1/returned-func.txtar @@ -12,7 +12,5 @@ $out = $fn() test "${out}" {} -- OUTPUT -- Edge: FuncValue -> call # fn -Edge: call -> call # fn Vertex: FuncValue Vertex: call -Vertex: call diff --git a/lang/interpret_test/TestAstFunc1/returned-lambda.txtar b/lang/interpret_test/TestAstFunc1/returned-lambda.txtar index 4684f8f2..8e7ead2a 100644 --- a/lang/interpret_test/TestAstFunc1/returned-lambda.txtar +++ b/lang/interpret_test/TestAstFunc1/returned-lambda.txtar @@ -11,7 +11,5 @@ $out = $fn() test "${out}" {} -- OUTPUT -- Edge: FuncValue -> call # fn -Edge: call -> call # fn Vertex: FuncValue Vertex: call -Vertex: call diff --git a/lang/interpret_test/TestAstFunc1/shape0.txtar b/lang/interpret_test/TestAstFunc1/shape0.txtar new file mode 100644 index 00000000..cd8987df --- /dev/null +++ b/lang/interpret_test/TestAstFunc1/shape0.txtar @@ -0,0 +1,24 @@ +-- main.mcl -- +# this was originally: optimized-higher-order-function.txtar +import "fmt" + +func apply($f, $x) { + $f($x) +} +$add1 = func($x) { + $x + 1 +} +$z = apply($add1, 1) + +test [fmt.printf("%d", $z),] {} +-- OUTPUT -- +Edge: FuncValue -> call # fn +Edge: call -> printf: func(format str, a int) str # a +Edge: const: str("%d") -> printf: func(format str, a int) str # format +Edge: printf: func(format str, a int) str -> composite: []str # 0 +Vertex: FuncValue +Vertex: call +Vertex: composite: []str +Vertex: const: int(1) +Vertex: const: str("%d") +Vertex: printf: func(format str, a int) str diff --git a/lang/interpret_test/TestAstFunc1/shape1.txtar b/lang/interpret_test/TestAstFunc1/shape1.txtar new file mode 100644 index 00000000..3ee4f976 --- /dev/null +++ b/lang/interpret_test/TestAstFunc1/shape1.txtar @@ -0,0 +1,12 @@ +-- main.mcl -- +print ["a" + "b",] {} +-- OUTPUT -- +Edge: _operator -> composite: []str # 0 +Edge: const: str("+") -> _operator # op +Edge: const: str("a") -> _operator # a +Edge: const: str("b") -> _operator # b +Vertex: _operator +Vertex: composite: []str +Vertex: const: str("+") +Vertex: const: str("a") +Vertex: const: str("b") diff --git a/lang/interpret_test/TestAstFunc1/shape2.txtar b/lang/interpret_test/TestAstFunc1/shape2.txtar new file mode 100644 index 00000000..42f9b54b --- /dev/null +++ b/lang/interpret_test/TestAstFunc1/shape2.txtar @@ -0,0 +1,16 @@ +-- main.mcl -- +$lambda = func($x) { + $x + "!" +} + +test [$lambda("hello"),] {} +-- OUTPUT -- +Edge: _operator -> composite: []str # 0 +Edge: const: str("!") -> _operator # b +Edge: const: str("+") -> _operator # op +Edge: const: str("hello") -> _operator # a +Vertex: _operator +Vertex: composite: []str +Vertex: const: str("!") +Vertex: const: str("+") +Vertex: const: str("hello") diff --git a/lang/interpret_test/TestAstFunc1/shape3.txtar b/lang/interpret_test/TestAstFunc1/shape3.txtar new file mode 100644 index 00000000..c0ab33c9 --- /dev/null +++ b/lang/interpret_test/TestAstFunc1/shape3.txtar @@ -0,0 +1,25 @@ +-- main.mcl -- +$lambda1 = func($x) { + $x + "!" +} +$lambda2 = func($x) { + $x + "?" +} + +$lambda = if true { # must be a const, otherwise this is a dynamic graph + $lambda1 +} else { + $lambda2 +} + +test [$lambda("hello"),] {} +-- OUTPUT -- +Edge: _operator -> composite: []str # 0 +Edge: const: str("!") -> _operator # b +Edge: const: str("+") -> _operator # op +Edge: const: str("hello") -> _operator # a +Vertex: _operator +Vertex: composite: []str +Vertex: const: str("!") +Vertex: const: str("+") +Vertex: const: str("hello") diff --git a/lang/interpret_test/TestAstFunc1/shape4.txtar b/lang/interpret_test/TestAstFunc1/shape4.txtar new file mode 100644 index 00000000..a94b9fd5 --- /dev/null +++ b/lang/interpret_test/TestAstFunc1/shape4.txtar @@ -0,0 +1,25 @@ +-- main.mcl -- +$lambda1 = func($x) { + $x + "!" +} +$lambda2 = func($x) { + $x + "?" +} + +$lambda = if 10 > 0 { # must be a const, otherwise this is a dynamic graph + $lambda1 +} else { + $lambda2 +} + +test [$lambda("hello"),] {} +-- OUTPUT -- +Edge: _operator -> composite: []str # 0 +Edge: const: str("!") -> _operator # b +Edge: const: str("+") -> _operator # op +Edge: const: str("hello") -> _operator # a +Vertex: _operator +Vertex: composite: []str +Vertex: const: str("!") +Vertex: const: str("+") +Vertex: const: str("hello") diff --git a/lang/interpret_test/TestAstFunc1/shape5.txtar b/lang/interpret_test/TestAstFunc1/shape5.txtar new file mode 100644 index 00000000..f43fba03 --- /dev/null +++ b/lang/interpret_test/TestAstFunc1/shape5.txtar @@ -0,0 +1,34 @@ +-- main.mcl -- +import "datetime" +import "fmt" + +$lambda1 = func($x) { + $x + "!" +} +$lambda2 = func($x) { + $x + "?" +} + +$lambda = if 10 > 0 { # must be a const, otherwise this is a dynamic graph + $lambda1 +} else { + $lambda2 +} + +$s = fmt.printf("%d", datetime.now()) + +test [$lambda($s),] {} +-- OUTPUT -- +Edge: _operator -> composite: []str # 0 +Edge: const: str("!") -> _operator # b +Edge: const: str("%d") -> printf: func(format str, a int) str # format +Edge: const: str("+") -> _operator # op +Edge: now -> printf: func(format str, a int) str # a +Edge: printf: func(format str, a int) str -> _operator # a +Vertex: _operator +Vertex: composite: []str +Vertex: const: str("!") +Vertex: const: str("%d") +Vertex: const: str("+") +Vertex: now +Vertex: printf: func(format str, a int) str diff --git a/lang/interpret_test/TestAstFunc1/shape6.txtar b/lang/interpret_test/TestAstFunc1/shape6.txtar new file mode 100644 index 00000000..beaffabc --- /dev/null +++ b/lang/interpret_test/TestAstFunc1/shape6.txtar @@ -0,0 +1,33 @@ +-- main.mcl -- +import "iter" +import "golang" + +$fn = func($x) { # notable because concrete type is fn(t1) t2, where t1 != t2 + len($x) +} + +$in1 = ["a", "bb", "ccc",] + +$out1 = iter.map($in1, $fn) + +$s = golang.template("out1: {{ . }}", $out1) + +test [$s,] {} +-- OUTPUT -- +Edge: FuncValue -> map # function +Edge: composite: []str -> map # inputs +Edge: const: str("a") -> composite: []str # 0 +Edge: const: str("bb") -> composite: []str # 1 +Edge: const: str("ccc") -> composite: []str # 2 +Edge: const: str("out1: {{ . }}") -> template # template +Edge: map -> template # vars +Edge: template -> composite: []str # 0 +Vertex: FuncValue +Vertex: composite: []str +Vertex: composite: []str +Vertex: const: str("a") +Vertex: const: str("bb") +Vertex: const: str("ccc") +Vertex: const: str("out1: {{ . }}") +Vertex: map +Vertex: template diff --git a/lang/interpret_test/TestAstFunc1/shape7.txtar b/lang/interpret_test/TestAstFunc1/shape7.txtar new file mode 100644 index 00000000..9c450e05 --- /dev/null +++ b/lang/interpret_test/TestAstFunc1/shape7.txtar @@ -0,0 +1,57 @@ +-- main.mcl -- +import "datetime" +import "iter" +import "golang" + +$lambda1 = func($x) { + $x + "!" +} +$lambda2 = func($x) { + $x + "?" +} + +$lambda = if 10 > 0 { # must be a const, otherwise this is a dynamic graph + $lambda1 +} else { + $lambda2 +} + +$fn = func($x) { # notable because concrete type is fn(t1) t2, where t1 != t2 + len($x) +} + +$in1 = ["a", "bb", "ccc", "dddd", "eeeee",] + +$out1 = iter.map($in1, $fn) + +$s = golang.template("out1: {{ . }}", $out1) + +test [$lambda($s),] {} +-- OUTPUT -- +Edge: FuncValue -> map # function +Edge: _operator -> composite: []str # 0 +Edge: composite: []str -> map # inputs +Edge: const: str("!") -> _operator # b +Edge: const: str("+") -> _operator # op +Edge: const: str("a") -> composite: []str # 0 +Edge: const: str("bb") -> composite: []str # 1 +Edge: const: str("ccc") -> composite: []str # 2 +Edge: const: str("dddd") -> composite: []str # 3 +Edge: const: str("eeeee") -> composite: []str # 4 +Edge: const: str("out1: {{ . }}") -> template # template +Edge: map -> template # vars +Edge: template -> _operator # a +Vertex: FuncValue +Vertex: _operator +Vertex: composite: []str +Vertex: composite: []str +Vertex: const: str("!") +Vertex: const: str("+") +Vertex: const: str("a") +Vertex: const: str("bb") +Vertex: const: str("ccc") +Vertex: const: str("dddd") +Vertex: const: str("eeeee") +Vertex: const: str("out1: {{ . }}") +Vertex: map +Vertex: template diff --git a/lang/interpret_test/TestAstFunc1/shape8.txtar b/lang/interpret_test/TestAstFunc1/shape8.txtar new file mode 100644 index 00000000..b2febe09 --- /dev/null +++ b/lang/interpret_test/TestAstFunc1/shape8.txtar @@ -0,0 +1,38 @@ +-- main.mcl -- +$gt = func($one, $two) { + $one > $two +} + +$lambda1 = func($x) { + $x + "!" +} +$lambda2 = func($x) { + $x + "?" +} + +$lambda = if $gt(10, 0) { # must be a const, otherwise this is a dynamic graph + $lambda1 +} else { + $lambda2 +} + +test [$lambda("hello"),] {} +-- OUTPUT -- +Edge: FuncValue -> if # a +Edge: FuncValue -> if # b +Edge: _operator -> if # c +Edge: call -> composite: []str # 0 +Edge: const: int(0) -> _operator # b +Edge: const: int(10) -> _operator # a +Edge: const: str(">") -> _operator # op +Edge: if -> call # fn +Vertex: FuncValue +Vertex: FuncValue +Vertex: _operator +Vertex: call +Vertex: composite: []str +Vertex: const: int(0) +Vertex: const: int(10) +Vertex: const: str(">") +Vertex: const: str("hello") +Vertex: if diff --git a/lang/interpret_test/TestAstFunc1/simple-func1.txtar b/lang/interpret_test/TestAstFunc1/simple-func1.txtar index db710415..07da063a 100644 --- a/lang/interpret_test/TestAstFunc1/simple-func1.txtar +++ b/lang/interpret_test/TestAstFunc1/simple-func1.txtar @@ -7,6 +7,4 @@ $out1 = answer() test "${out1}" {} -- OUTPUT -- -Edge: FuncValue -> call # fn -Vertex: FuncValue -Vertex: call +Vertex: const: str("the answer is 42") diff --git a/lang/interpret_test/TestAstFunc1/simple-func2.txtar b/lang/interpret_test/TestAstFunc1/simple-func2.txtar index 47ad52b2..a34772b9 100644 --- a/lang/interpret_test/TestAstFunc1/simple-func2.txtar +++ b/lang/interpret_test/TestAstFunc1/simple-func2.txtar @@ -8,15 +8,12 @@ $out2 = answer() test [$out1 + $out2,] {} -- OUTPUT -- -Edge: FuncValue -> call # fn -Edge: FuncValue -> call # fn -Edge: FuncValue -> call # fn -Edge: call -> composite: []str # 0 -Vertex: FuncValue -Vertex: FuncValue -Vertex: FuncValue -Vertex: call -Vertex: call -Vertex: call +Edge: _operator -> composite: []str # 0 +Edge: const: str("+") -> _operator # op +Edge: const: str("the answer is 42") -> _operator # a +Edge: const: str("the answer is 42") -> _operator # b +Vertex: _operator Vertex: composite: []str Vertex: const: str("+") +Vertex: const: str("the answer is 42") +Vertex: const: str("the answer is 42") diff --git a/lang/interpret_test/TestAstFunc1/simple-lambda1.txtar b/lang/interpret_test/TestAstFunc1/simple-lambda1.txtar index 7cb942fd..aa81961c 100644 --- a/lang/interpret_test/TestAstFunc1/simple-lambda1.txtar +++ b/lang/interpret_test/TestAstFunc1/simple-lambda1.txtar @@ -10,6 +10,4 @@ $out = $answer() test "${out}" {} -- OUTPUT -- -Edge: FuncValue -> call # fn -Vertex: FuncValue -Vertex: call +Vertex: const: str("the answer is 42") diff --git a/lang/interpret_test/TestAstFunc1/simple-lambda2.txtar b/lang/interpret_test/TestAstFunc1/simple-lambda2.txtar index 7509bf5d..31f94bfd 100644 --- a/lang/interpret_test/TestAstFunc1/simple-lambda2.txtar +++ b/lang/interpret_test/TestAstFunc1/simple-lambda2.txtar @@ -11,14 +11,12 @@ $out2 = $answer() test [$out1 + $out2,] {} -- OUTPUT -- -Edge: FuncValue -> call # fn -Edge: FuncValue -> call # fn -Edge: FuncValue -> call # fn -Edge: call -> composite: []str # 0 -Vertex: FuncValue -Vertex: FuncValue -Vertex: call -Vertex: call -Vertex: call +Edge: _operator -> composite: []str # 0 +Edge: const: str("+") -> _operator # op +Edge: const: str("the answer is 42") -> _operator # a +Edge: const: str("the answer is 42") -> _operator # b +Vertex: _operator Vertex: composite: []str Vertex: const: str("+") +Vertex: const: str("the answer is 42") +Vertex: const: str("the answer is 42") diff --git a/lang/interpret_test/TestAstFunc1/slow_unification0.txtar b/lang/interpret_test/TestAstFunc1/slow_unification0.txtar index 6aab423a..0313f0da 100644 --- a/lang/interpret_test/TestAstFunc1/slow_unification0.txtar +++ b/lang/interpret_test/TestAstFunc1/slow_unification0.txtar @@ -52,27 +52,31 @@ if $state == "three" { Exec["timer"] -> Kv["${ns}"] } -- OUTPUT -- -Edge: FuncValue -> call # fn -Edge: FuncValue -> call # fn -Edge: FuncValue -> call # fn -Edge: FuncValue -> call # fn -Edge: FuncValue -> call # fn -Edge: FuncValue -> call # fn -Edge: FuncValue -> call # fn -Vertex: FuncValue -Vertex: FuncValue -Vertex: FuncValue -Vertex: FuncValue -Vertex: FuncValue -Vertex: FuncValue -Vertex: FuncValue -Vertex: call -Vertex: call -Vertex: call -Vertex: call -Vertex: call -Vertex: call -Vertex: call +Edge: _lookup_default -> _operator # a +Edge: _lookup_default -> _operator # a +Edge: _lookup_default -> _operator # a +Edge: _lookup_default -> _operator # a +Edge: _operator -> _operator # a +Edge: _operator -> _operator # b +Edge: const: str("") -> _lookup_default # indexorkey +Edge: const: str("==") -> _operator # op +Edge: const: str("==") -> _operator # op +Edge: const: str("==") -> _operator # op +Edge: const: str("==") -> _operator # op +Edge: const: str("default") -> _lookup_default # default +Edge: const: str("default") -> _operator # b +Edge: const: str("estate") -> kvlookup # namespace +Edge: const: str("one") -> _operator # b +Edge: const: str("or") -> _operator # op +Edge: const: str("three") -> _operator # b +Edge: const: str("two") -> _operator # b +Edge: kvlookup -> _lookup_default # listormap +Vertex: _lookup_default +Vertex: _operator +Vertex: _operator +Vertex: _operator +Vertex: _operator +Vertex: _operator Vertex: const: str("") Vertex: const: str("/tmp/mgmt/state") Vertex: const: str("/tmp/mgmt/state") @@ -103,3 +107,4 @@ Vertex: const: str("timer") Vertex: const: str("timer") Vertex: const: str("two") Vertex: const: str("two") +Vertex: kvlookup diff --git a/lang/interpret_test/TestAstFunc1/static-function0.txtar b/lang/interpret_test/TestAstFunc1/static-function0.txtar index a9d7523a..a7935028 100644 --- a/lang/interpret_test/TestAstFunc1/static-function0.txtar +++ b/lang/interpret_test/TestAstFunc1/static-function0.txtar @@ -16,13 +16,9 @@ test "greeting3" { anotherstr => $fn(), } -- OUTPUT -- -Edge: FuncValue -> call # fn -Edge: FuncValue -> call # fn -Edge: FuncValue -> call # fn -Vertex: FuncValue -Vertex: call -Vertex: call -Vertex: call Vertex: const: str("greeting1") Vertex: const: str("greeting2") Vertex: const: str("greeting3") +Vertex: const: str("hello world") +Vertex: const: str("hello world") +Vertex: const: str("hello world") diff --git a/lang/interpret_test/TestAstFunc1/static-function1.txtar b/lang/interpret_test/TestAstFunc1/static-function1.txtar index 5e0916ce..1044e2fd 100644 --- a/lang/interpret_test/TestAstFunc1/static-function1.txtar +++ b/lang/interpret_test/TestAstFunc1/static-function1.txtar @@ -18,13 +18,41 @@ test "greeting3" { anotherstr => $fn(), } -- OUTPUT -- -Edge: FuncValue -> call # fn -Edge: FuncValue -> call # fn -Edge: FuncValue -> call # fn -Vertex: FuncValue -Vertex: call -Vertex: call -Vertex: call +Edge: _operator -> _operator # a +Edge: _operator -> _operator # a +Edge: _operator -> _operator # a +Edge: const: str(" ") -> _operator # b +Edge: const: str(" ") -> _operator # b +Edge: const: str(" ") -> _operator # b +Edge: const: str("+") -> _operator # op +Edge: const: str("+") -> _operator # op +Edge: const: str("+") -> _operator # op +Edge: const: str("+") -> _operator # op +Edge: const: str("+") -> _operator # op +Edge: const: str("+") -> _operator # op +Edge: const: str("hello") -> _operator # a +Edge: const: str("hello") -> _operator # a +Edge: const: str("hello") -> _operator # a +Edge: const: str("world") -> _operator # b +Edge: const: str("world") -> _operator # b +Edge: const: str("world") -> _operator # b +Vertex: _operator +Vertex: _operator +Vertex: _operator +Vertex: _operator +Vertex: _operator +Vertex: _operator +Vertex: const: str(" ") +Vertex: const: str(" ") +Vertex: const: str(" ") +Vertex: const: str("+") +Vertex: const: str("+") +Vertex: const: str("+") +Vertex: const: str("+") +Vertex: const: str("+") +Vertex: const: str("+") Vertex: const: str("greeting1") Vertex: const: str("greeting2") Vertex: const: str("greeting3") +Vertex: const: str("hello") +Vertex: const: str("world") diff --git a/lang/parser/lexparse_test.go b/lang/parser/lexparse_test.go index 10c1bada..d3821f7b 100644 --- a/lang/parser/lexparse_test.go +++ b/lang/parser/lexparse_test.go @@ -1744,7 +1744,8 @@ func TestLexParse0(t *testing.T) { &ast.StmtFunc{ Name: "f1", Func: &ast.ExprFunc{ - Args: []*interfaces.Arg{}, + Title: "f1", + Args: []*interfaces.Arg{}, Body: &ast.ExprInt{ V: 42, }, @@ -1765,6 +1766,7 @@ func TestLexParse0(t *testing.T) { } { fn := &ast.ExprFunc{ + Title: "f2", Args: []*interfaces.Arg{}, Return: types.TypeInt, Body: &ast.ExprCall{ @@ -1809,6 +1811,7 @@ func TestLexParse0(t *testing.T) { } { fn := &ast.ExprFunc{ + Title: "f3", Args: []*interfaces.Arg{ { Name: "a", @@ -1860,6 +1863,7 @@ func TestLexParse0(t *testing.T) { } { fn := &ast.ExprFunc{ + Title: "f4", Args: []*interfaces.Arg{ { Name: "x", @@ -2048,7 +2052,8 @@ func TestLexParse0(t *testing.T) { Name: "funcgen", // This is the outer function... Func: &ast.ExprFunc{ - Args: []*interfaces.Arg{}, + Title: "funcgen", + Args: []*interfaces.Arg{}, // This is the inner function... Body: &ast.ExprFunc{ Args: []*interfaces.Arg{}, diff --git a/lang/parser/parser.y b/lang/parser/parser.y index 891f104f..e04eafc2 100644 --- a/lang/parser/parser.y +++ b/lang/parser/parser.y @@ -255,9 +255,10 @@ stmt: $$.stmt = &ast.StmtFunc{ Name: $2.str, Func: &ast.ExprFunc{ - Args: $4.args, - //Return: nil, - Body: $7.expr, + Title: $2.str, + Args: $4.args, + Return: nil, + Body: $7.expr, }, } locate(yylex, $1, yyDollar[len(yyDollar)-1], $$.stmt) @@ -266,6 +267,7 @@ stmt: | FUNC_IDENTIFIER IDENTIFIER OPEN_PAREN args CLOSE_PAREN type OPEN_CURLY expr CLOSE_CURLY { fn := &ast.ExprFunc{ + Title: $2.str, Args: $4.args, Return: $6.typ, // return type is known Body: $8.expr, diff --git a/lang/types/full/full.go b/lang/types/full/full.go index b6500477..97966d07 100644 --- a/lang/types/full/full.go +++ b/lang/types/full/full.go @@ -30,6 +30,7 @@ package full import ( + "context" "fmt" "github.com/purpleidea/mgmt/lang/interfaces" @@ -49,11 +50,17 @@ import ( // particular output node, which the function returns so that the caller may // connect that output node to more nodes down the line. // +// That's all done with the V field. You call it using the CallWithFuncs method. +// This function also has a variant that takes a direct function implementation +// which it can get called if needed. That's done with the F field. You call it +// using the CallWithValues method. +// // The function can also return an error which could represent that something // went horribly wrong. (Think, an internal panic.) type FuncValue struct { types.Base - V func(interfaces.Txn, []interfaces.Func) (interfaces.Func, error) + V interfaces.GraphSig + F interfaces.FuncSig T *types.Type // contains ordered field types, arg names are a bonus part } @@ -65,8 +72,12 @@ func NewFunc(t *types.Type) *FuncValue { v := func(interfaces.Txn, []interfaces.Func) (interfaces.Func, error) { return nil, fmt.Errorf("nil function") // TODO: is this correct? } + f := func(context.Context, []types.Value) (types.Value, error) { + return nil, fmt.Errorf("nil function") // TODO: is this correct? + } return &FuncValue{ V: v, + F: f, T: t, } } @@ -145,12 +156,20 @@ func (obj *FuncValue) Func() interface{} { } // Set sets the function value to be a new function. -func (obj *FuncValue) Set(fn func(interfaces.Txn, []interfaces.Func) (interfaces.Func, error)) error { // TODO: change method name? +func (obj *FuncValue) Set(fn interfaces.GraphSig) error { // TODO: change method name? obj.V = fn return nil // TODO: can we do any sort of checking here? } -// Call calls the function with the provided txn and args. -func (obj *FuncValue) Call(txn interfaces.Txn, args []interfaces.Func) (interfaces.Func, error) { +// CallWithValues calls the function with the provided value args. +func (obj *FuncValue) CallWithValues(ctx context.Context, args []types.Value) (types.Value, error) { + if obj.F == nil { + return nil, fmt.Errorf("nil function") // TODO: Should we use ErrCantSpeculate? + } + return obj.F(ctx, args) +} + +// CallWithFuncs calls the function with the provided txn and func args. +func (obj *FuncValue) CallWithFuncs(txn interfaces.Txn, args []interfaces.Func) (interfaces.Func, error) { return obj.V(txn, args) } diff --git a/lang/types/value.go b/lang/types/value.go index 3c5892e9..8bb2f2c8 100644 --- a/lang/types/value.go +++ b/lang/types/value.go @@ -1321,6 +1321,9 @@ func (obj *FuncValue) Value() interface{} { // inappropriate input types, or if it returns an inappropriate output type. func (obj *FuncValue) Call(ctx context.Context, args []Value) (Value, error) { // cmp input args type to obj.T + if obj.T == nil { + return nil, fmt.Errorf("the type is nil") + } length := len(obj.T.Ord) if length != len(args) { return nil, fmt.Errorf("arg length of %d does not match expected of %d", len(args), length)