lang: Improve graph shape with speculative execution

Most of the time, we don't need to have a dynamic call sub graph, since
the actual function call could be represented statically as it
originally was before lambda functions were implemented. Simplifying the
graph shape has important performance benefits in terms of both keep the
graph smaller (memory, etc) and in avoiding the need to run transactions
at runtime (speed) to reshape the graph.

Co-authored-by: Samuel Gélineau <gelisam@gmail.com>
This commit is contained in:
James Shubin
2025-03-17 02:31:01 -04:00
parent 9c9f2f558a
commit 37bb67dffd
139 changed files with 1871 additions and 262 deletions

View File

@@ -33,6 +33,7 @@ package ast
import ( import (
"bytes" "bytes"
"context"
"fmt" "fmt"
"reflect" "reflect"
"sort" "sort"
@@ -45,7 +46,9 @@ import (
"github.com/purpleidea/mgmt/lang/core" "github.com/purpleidea/mgmt/lang/core"
"github.com/purpleidea/mgmt/lang/embedded" "github.com/purpleidea/mgmt/lang/embedded"
"github.com/purpleidea/mgmt/lang/funcs" "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/structs"
"github.com/purpleidea/mgmt/lang/funcs/txn"
"github.com/purpleidea/mgmt/lang/inputs" "github.com/purpleidea/mgmt/lang/inputs"
"github.com/purpleidea/mgmt/lang/interfaces" "github.com/purpleidea/mgmt/lang/interfaces"
"github.com/purpleidea/mgmt/lang/types" "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. // 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. // 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) { func (obj *ExprFunc) Ordering(produces map[string]interfaces.Node) (*pgraph.Graph, map[interfaces.Node]string, error) {
graph, err := pgraph.NewGraph("ordering") graph, err := pgraph.NewGraph("ordering")
if err != nil { if err != nil {
@@ -9502,7 +9504,6 @@ func (obj *ExprFunc) Ordering(produces map[string]interfaces.Node) (*pgraph.Grap
cons := make(map[interfaces.Node]string) cons := make(map[interfaces.Node]string)
// XXX: do we need ordering for other aspects of ExprFunc ?
if obj.Body != nil { if obj.Body != nil {
g, c, err := obj.Body.Ordering(newProduces) g, c, err := obj.Body.Ordering(newProduces)
if err != nil { if err != nil {
@@ -9851,6 +9852,11 @@ func (obj *ExprFunc) Graph(env *interfaces.Env) (*pgraph.Graph, interfaces.Func,
var funcValueFunc interfaces.Func var funcValueFunc interfaces.Func
if obj.Body != nil { 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{ funcValueFunc = structs.FuncValueToConstFunc(&full.FuncValue{
V: func(innerTxn interfaces.Txn, args []interfaces.Func) (interfaces.Func, error) { V: func(innerTxn interfaces.Txn, args []interfaces.Func) (interfaces.Func, error) {
// Extend the environment with the arguments. // Extend the environment with the arguments.
@@ -9888,13 +9894,27 @@ func (obj *ExprFunc) Graph(env *interfaces.Env) (*pgraph.Graph, interfaces.Func,
return bodyFunc, nil return bodyFunc, nil
}, },
F: f,
T: obj.typ, T: obj.typ,
}) })
} else if obj.Function != nil { } 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 // obj.function is a node which transforms input values into
// an output value, but we need to construct a node which takes no // an output value, but we need to construct a node which takes no
// inputs and produces a FuncValue, so we need to wrap it. // inputs and produces a FuncValue, so we need to wrap it.
funcValueFunc = structs.FuncValueToConstFunc(&full.FuncValue{ funcValueFunc = structs.FuncValueToConstFunc(&full.FuncValue{
V: func(txn interfaces.Txn, args []interfaces.Func) (interfaces.Func, error) { V: func(txn interfaces.Txn, args []interfaces.Func) (interfaces.Func, error) {
// Copy obj.function so that the underlying ExprFunc.function gets // 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 return valueTransformingFunc, nil
}, },
F: fn,
T: obj.typ, T: obj.typ,
}) })
} else /* len(obj.Values) > 0 */ { } 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. // This particular value is always known since it is a constant.
func (obj *ExprFunc) Value() (types.Value, error) { func (obj *ExprFunc) Value() (types.Value, error) {
// Don't panic because we call Value speculatively for partial values! // 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")
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) if obj.Body != nil {
//return &full.FuncValue{ // We can only return a Value if we know the value of all the
// V: obj.V, // ExprParams. We don't have an environment, so this is only
// T: obj.typ, // possible if there are no ExprParams at all.
//}, nil // 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 // 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") 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 // 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 // to the function vertex. Instead, each time the call vertex (which we
// create below) receives a FuncValue from the function node, it creates the // 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) 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. // Add a vertex for the call itself.
edgeName := structs.CallFuncArgNameFunction edgeName := structs.CallFuncArgNameFunction
callFunc := &structs.CallFunc{ callFunc := &structs.CallFunc{
@@ -10867,7 +11034,7 @@ func (obj *ExprCall) SetValue(value types.Value) error {
if err := obj.typ.Cmp(value.Type()); err != nil { if err := obj.typ.Cmp(value.Type()); err != nil {
return err return err
} }
obj.V = value obj.V = value // XXX: is this useful or a good idea?
return nil 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. // 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. // This might get called speculatively (early) during unification to learn more.
// It is often unlikely that this kind of speculative execution finds something. // It is often unlikely that this kind of speculative execution finds something.
// This particular implementation of the function returns the previously stored // This particular implementation will run a function if all of the needed
// and cached value as received by SetValue. // values are known. This is necessary for getting the efficient graph shape of
// ExprCall.
func (obj *ExprCall) Value() (types.Value, error) { 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 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 // 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. // 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. // This might get called speculatively (early) during unification to learn more.
func (obj *ExprParam) Value() (types.Value, error) { 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") return nil, fmt.Errorf("no value for ExprParam")
} }

View File

@@ -321,6 +321,11 @@ func getScope(node interfaces.Expr) (*interfaces.Scope, error) {
return expr.scope, nil return expr.scope, nil
case *ExprVar: case *ExprVar:
return expr.scope, nil return expr.scope, nil
//case *ExprParam:
//case *ExprIterated:
//case *ExprPoly:
//case *ExprTopLevel:
//case *ExprSingleton:
case *ExprIf: case *ExprIf:
return expr.scope, nil 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 // trueCallee is a helper function because ExprTopLevel and ExprSingleton are
// sometimes added around builtins. This makes it difficult for the type checker // 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 // to check if a particular builtin is the callee or not. This function removes

View File

@@ -282,6 +282,8 @@ func (obj *CollectFunc) Info() *interfaces.Info {
return &interfaces.Info{ return &interfaces.Info{
Pure: false, Pure: false,
Memo: false, Memo: false,
Fast: false,
Spec: false,
Sig: sig, Sig: sig,
Err: obj.Validate(), 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 // to do so at this time. This was previously getValue which gets the value
// we're looking for. // we're looking for.
func (obj *CollectFunc) Call(ctx context.Context, args []types.Value) (types.Value, error) { 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() kind := args[0].Str()
if kind == "" { if kind == "" {
return nil, fmt.Errorf("resource kind is empty") 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) resOutput, err := obj.init.World.ResCollect(ctx, filters)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@@ -44,6 +44,12 @@ const (
func init() { func init() {
simple.Register(ConcatFuncName, &simple.Scaffold{ 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"), T: types.NewType("func(a str, b str) str"),
F: Concat, F: Concat,
}) })

View File

@@ -44,6 +44,12 @@ const (
func init() { func init() {
simple.Register(ContainsFuncName, &simple.Scaffold{ 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"), T: types.NewType("func(needle ?1, haystack []?1) bool"),
F: Contains, F: Contains,
}) })

View File

@@ -39,6 +39,12 @@ import (
func init() { func init() {
simple.ModuleRegister(ModuleName, "format_bool", &simple.Scaffold{ 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"), T: types.NewType("func(a bool) str"),
F: FormatBool, F: FormatBool,
}) })

View File

@@ -40,6 +40,12 @@ import (
func init() { func init() {
simple.ModuleRegister(ModuleName, "parse_bool", &simple.Scaffold{ 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"), T: types.NewType("func(a str) bool"),
F: ParseBool, F: ParseBool,
}) })

View File

@@ -38,6 +38,12 @@ import (
func init() { func init() {
simple.ModuleRegister(ModuleName, "to_float", &simple.Scaffold{ 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"), T: types.NewType("func(a int) float"),
F: ToFloat, F: ToFloat,
}) })

View File

@@ -38,6 +38,12 @@ import (
func init() { func init() {
simple.ModuleRegister(ModuleName, "to_int", &simple.Scaffold{ 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"), T: types.NewType("func(a float) int"),
F: ToInt, F: ToInt,
}) })

View File

@@ -39,6 +39,12 @@ import (
func init() { func init() {
simple.ModuleRegister(ModuleName, "int_to_str", &simple.Scaffold{ 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"), T: types.NewType("func(a int) str"),
F: IntToStr, F: IntToStr,
}) })

View File

@@ -62,8 +62,8 @@ func PureFuncExec(handle interfaces.Func, args []types.Value) (types.Value, erro
return nil, fmt.Errorf("func is not pure") return nil, fmt.Errorf("func is not pure")
} }
// if function is expensive to run, we won't run it provisionally // if function is expensive to run, we won't run it provisionally
if info.Slow { if !info.Fast {
return nil, fmt.Errorf("func is slow") return nil, fmt.Errorf("func is not fast")
} }
sig := handle.Info().Sig sig := handle.Info().Sig

View File

@@ -40,6 +40,12 @@ import (
func init() { func init() {
simple.ModuleRegister(ModuleName, "format", &simple.Scaffold{ 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"), T: types.NewType("func(a int, b str) str"),
F: Format, F: Format,
}) })

View File

@@ -40,6 +40,12 @@ import (
func init() { func init() {
simple.ModuleRegister(ModuleName, "hour", &simple.Scaffold{ simple.ModuleRegister(ModuleName, "hour", &simple.Scaffold{
I: &simple.Info{
Pure: true,
Memo: true,
Fast: true,
Spec: true,
},
T: types.NewType("func(a int) int"), T: types.NewType("func(a int) int"),
F: Hour, F: Hour,
}) })

View File

@@ -67,6 +67,8 @@ func (obj *DateTimeFact) String() string {
// Info returns some static info about itself. // Info returns some static info about itself.
func (obj *DateTimeFact) Info() *facts.Info { func (obj *DateTimeFact) Info() *facts.Info {
return &facts.Info{ return &facts.Info{
Pure: false,
Memo: false,
Output: types.NewType("int"), Output: types.NewType("int"),
} }
} }

View File

@@ -41,6 +41,12 @@ import (
func init() { func init() {
// FIXME: consider renaming this to printf, and add in a format string? // FIXME: consider renaming this to printf, and add in a format string?
simple.ModuleRegister(ModuleName, "print", &simple.Scaffold{ simple.ModuleRegister(ModuleName, "print", &simple.Scaffold{
I: &simple.Info{
Pure: true,
Memo: true,
Fast: true,
Spec: true,
},
T: types.NewType("func(a int) str"), T: types.NewType("func(a int) str"),
F: Print, F: Print,
}) })

View File

@@ -41,6 +41,12 @@ import (
func init() { func init() {
simple.ModuleRegister(ModuleName, "weekday", &simple.Scaffold{ simple.ModuleRegister(ModuleName, "weekday", &simple.Scaffold{
I: &simple.Info{
Pure: true,
Memo: true,
Fast: true,
Spec: true,
},
T: types.NewType("func(a int) str"), T: types.NewType("func(a int) str"),
F: Weekday, F: Weekday,
}) })

View File

@@ -51,6 +51,8 @@ func init() {
funcs.ModuleRegister(ModuleName, AbsPathFuncName, func() interfaces.Func { return &AbsPathFunc{} }) // must register the func and name 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 // 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 // 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 // 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{ return &interfaces.Info{
Pure: false, // maybe false because the file contents can change Pure: false, // maybe false because the file contents can change
Memo: false, Memo: false,
Fast: false,
Spec: false,
Sig: types.NewType(fmt.Sprintf("func(%s str) str", absPathArgNamePath)), 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 // Call this function with the input args and return the value if it is possible
// to do so at this time. // to do so at this time.
func (obj *AbsPathFunc) Call(ctx context.Context, args []types.Value) (types.Value, error) { 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() path := args[0].Str()
if obj.data == nil {
return nil, funcs.ErrCantSpeculate
}
p := strings.TrimSuffix(obj.data.Base, "/") p := strings.TrimSuffix(obj.data.Base, "/")
if p == obj.data.Base { // didn't trim, so we fail if p == obj.data.Base { // didn't trim, so we fail
// programming error // programming error

View File

@@ -39,6 +39,12 @@ import (
func init() { func init() {
simple.ModuleRegister(ModuleName, "binary_path", &simple.Scaffold{ simple.ModuleRegister(ModuleName, "binary_path", &simple.Scaffold{
I: &simple.Info{
Pure: true,
Memo: false,
Fast: true,
Spec: false,
},
T: types.NewType("func() str"), T: types.NewType("func() str"),
F: BinaryPath, F: BinaryPath,
}) })

View File

@@ -40,6 +40,12 @@ import (
func init() { func init() {
simple.ModuleRegister(ModuleName, "bootstrap_packages", &simple.Scaffold{ simple.ModuleRegister(ModuleName, "bootstrap_packages", &simple.Scaffold{
I: &simple.Info{
Pure: true,
Memo: true,
Fast: true,
Spec: true,
},
T: types.NewType("func(str) []str"), T: types.NewType("func(str) []str"),
F: BootstrapPackages, F: BootstrapPackages,
}) })

View File

@@ -52,6 +52,8 @@ func init() {
funcs.ModuleRegister(ModuleName, ReadFileFuncName, func() interfaces.Func { return &ReadFileFunc{} }) // must register the func and name 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 // 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 // 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 // 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{ return &interfaces.Info{
Pure: false, // maybe false because the file contents can change Pure: false, // maybe false because the file contents can change
Memo: false, Memo: false,
Fast: false,
Spec: false,
Sig: types.NewType(fmt.Sprintf("func(%s str) str", readFileArgNameFilename)), 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 // Call this function with the input args and return the value if it is possible
// to do so at this time. // to do so at this time.
func (obj *ReadFileFunc) Call(ctx context.Context, args []types.Value) (types.Value, error) { 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() filename := args[0].Str()
if obj.data == nil {
return nil, funcs.ErrCantSpeculate
}
p := strings.TrimSuffix(obj.data.Base, "/") p := strings.TrimSuffix(obj.data.Base, "/")
if p == obj.data.Base { // didn't trim, so we fail if p == obj.data.Base { // didn't trim, so we fail
// programming error // programming error
@@ -186,6 +205,9 @@ func (obj *ReadFileFunc) Call(ctx context.Context, args []types.Value) (types.Va
} }
path += filename 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 fs, err := obj.init.World.Fs(obj.data.FsURI) // open the remote file system
if err != nil { if err != nil {
return nil, errwrap.Wrapf(err, "can't load code from file system `%s`", obj.data.FsURI) return nil, errwrap.Wrapf(err, "can't load code from file system `%s`", obj.data.FsURI)

View File

@@ -51,6 +51,8 @@ func init() {
funcs.ModuleRegister(ModuleName, ReadFileAbsFuncName, func() interfaces.Func { return &ReadFileAbsFunc{} }) // must register the func and name 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 // 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 // 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 // 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{ return &interfaces.Info{
Pure: false, // maybe false because the file contents can change Pure: false, // maybe false because the file contents can change
Memo: false, Memo: false,
Fast: false,
Spec: false,
Sig: types.NewType(fmt.Sprintf("func(%s str) str", readfileArgNameFilename)), 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 // Call this function with the input args and return the value if it is possible
// to do so at this time. // to do so at this time.
func (obj *ReadFileAbsFunc) Call(ctx context.Context, args []types.Value) (types.Value, error) { 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() 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 fs, err := obj.init.World.Fs(obj.data.FsURI) // open the remote file system
if err != nil { if err != nil {
return nil, errwrap.Wrapf(err, "can't load code from file system `%s`", obj.data.FsURI) return nil, errwrap.Wrapf(err, "can't load code from file system `%s`", obj.data.FsURI)

View File

@@ -524,6 +524,12 @@ func (obj *provisioner) Register(moduleName string) error {
// Build a few separately... // Build a few separately...
simple.ModuleRegister(moduleName, "cli_password", &simple.Scaffold{ simple.ModuleRegister(moduleName, "cli_password", &simple.Scaffold{
I: &simple.Info{
Pure: false,
Memo: false,
Fast: false,
Spec: false,
},
T: types.NewType("func() str"), T: types.NewType("func() str"),
F: func(ctx context.Context, input []types.Value) (types.Value, error) { F: func(ctx context.Context, input []types.Value) (types.Value, error) {
if obj.localArgs == nil { if obj.localArgs == nil {

View File

@@ -41,6 +41,12 @@ const Answer = 42
func init() { func init() {
simple.ModuleRegister(ModuleName, "answer", &simple.Scaffold{ simple.ModuleRegister(ModuleName, "answer", &simple.Scaffold{
I: &simple.Info{
Pure: true,
Memo: true,
Fast: true,
Spec: true,
},
T: types.NewType("func() int"), T: types.NewType("func() int"),
F: TheAnswerToLifeTheUniverseAndEverything, F: TheAnswerToLifeTheUniverseAndEverything,
}) })

View File

@@ -39,6 +39,12 @@ import (
func init() { func init() {
simple.ModuleRegister(ModuleName, "errorbool", &simple.Scaffold{ 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"), T: types.NewType("func(a bool) str"),
F: ErrorBool, F: ErrorBool,
}) })

View File

@@ -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. // 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) { 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 obj.mutex.Lock() // TODO: could be a read lock
value := obj.value value := obj.value
obj.mutex.Unlock() obj.mutex.Unlock()

View File

@@ -39,6 +39,12 @@ import (
func init() { func init() {
simple.ModuleRegister(ModuleName, "int2str", &simple.Scaffold{ simple.ModuleRegister(ModuleName, "int2str", &simple.Scaffold{
I: &simple.Info{
Pure: true,
Memo: true,
Fast: true,
Spec: true,
},
T: types.NewType("func(a int) str"), T: types.NewType("func(a int) str"),
F: Int2Str, F: Int2Str,
}) })

View File

@@ -39,6 +39,12 @@ import (
func init() { func init() {
simple.ModuleRegister(coreexample.ModuleName+"/"+ModuleName, "hello", &simple.Scaffold{ simple.ModuleRegister(coreexample.ModuleName+"/"+ModuleName, "hello", &simple.Scaffold{
I: &simple.Info{
Pure: true,
Memo: true,
Fast: true,
Spec: true,
},
T: types.NewType("func() str"), T: types.NewType("func() str"),
F: Hello, F: Hello,
}) })

View File

@@ -38,6 +38,12 @@ import (
func init() { func init() {
simple.ModuleRegister(ModuleName, "plus", &simple.Scaffold{ 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"), T: types.NewType("func(y str, z str) str"),
F: Plus, F: Plus,
}) })

View File

@@ -39,6 +39,12 @@ import (
func init() { func init() {
simple.ModuleRegister(ModuleName, "str2int", &simple.Scaffold{ simple.ModuleRegister(ModuleName, "str2int", &simple.Scaffold{
I: &simple.Info{
Pure: true,
Memo: true,
Fast: true,
Spec: true,
},
T: types.NewType("func(a str) int"), T: types.NewType("func(a str) int"),
F: Str2Int, F: Str2Int,
}) })

View File

@@ -131,8 +131,10 @@ func (obj *VUMeterFunc) Validate() error {
// Info returns some static info about itself. // Info returns some static info about itself.
func (obj *VUMeterFunc) Info() *interfaces.Info { func (obj *VUMeterFunc) Info() *interfaces.Info {
return &interfaces.Info{ return &interfaces.Info{
Pure: true, Pure: false,
Memo: false, Memo: false,
Fast: false,
Spec: false,
Sig: types.NewType(fmt.Sprintf("func(%s str, %s int, %s float) str", vuMeterArgNameSymbol, vuMeterArgNameMultiplier, vuMeterArgNamePeak)), 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 // Call this function with the input args and return the value if it is possible
// to do so at this time. // to do so at this time.
func (obj *VUMeterFunc) Call(ctx context.Context, args []types.Value) (types.Value, error) { 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() symbol := args[0].Str()
multiplier := args[1].Int() multiplier := args[1].Int()
peak := args[2].Float() peak := args[2].Float()

View File

@@ -98,7 +98,10 @@ type PrintfFunc struct {
// String returns a simple name for this function. This is needed so this struct // String returns a simple name for this function. This is needed so this struct
// can satisfy the pgraph.Vertex interface. // can satisfy the pgraph.Vertex interface.
func (obj *PrintfFunc) String() string { 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. // 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!) // getting them from FuncInfer, and not from here. (During unification!)
return &interfaces.Info{ return &interfaces.Info{
Pure: true, Pure: true,
Memo: false, Memo: true,
Fast: true,
Spec: true,
Sig: obj.Type, Sig: obj.Type,
Err: obj.Validate(), 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 // Call this function with the input args and return the value if it is possible
// to do so at this time. // to do so at this time.
func (obj *PrintfFunc) Call(ctx context.Context, args []types.Value) (types.Value, error) { 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() format := args[0].Str()
values := []types.Value{} values := []types.Value{}

View File

@@ -202,8 +202,10 @@ func (obj *TemplateFunc) Info() *interfaces.Info {
sig = obj.sig() // helper sig = obj.sig() // helper
} }
return &interfaces.Info{ return &interfaces.Info{
Pure: true, Pure: false, // contents of a template might not be pure
Memo: false, Memo: false,
Fast: false,
Spec: false,
Sig: sig, Sig: sig,
Err: obj.Validate(), 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 // Call this function with the input args and return the value if it is possible
// to do so at this time. // to do so at this time.
func (obj *TemplateFunc) Call(ctx context.Context, args []types.Value) (types.Value, error) { 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() tmpl := args[0].Str()
var vars types.Value // nil var vars types.Value // nil
@@ -414,6 +419,9 @@ func (obj *TemplateFunc) Call(ctx context.Context, args []types.Value) (types.Va
vars = args[1] vars = args[1]
} }
if obj.init == nil {
return nil, funcs.ErrCantSpeculate
}
result, err := obj.run(ctx, tmpl, vars) result, err := obj.run(ctx, tmpl, vars)
if err != nil { if err != nil {
return nil, err // no errwrap needed b/c helper func return nil, err // no errwrap needed b/c helper func

View File

@@ -158,6 +158,8 @@ func (obj *HistoryFunc) Info() *interfaces.Info {
return &interfaces.Info{ return &interfaces.Info{
Pure: false, // definitely false Pure: false, // definitely false
Memo: false, Memo: false,
Fast: false,
Spec: false,
Sig: obj.sig(), // helper Sig: obj.sig(), // helper
Err: obj.Validate(), Err: obj.Validate(),
} }

View File

@@ -142,8 +142,10 @@ func (obj *FilterFunc) Validate() error {
// will return correct data. // will return correct data.
func (obj *FilterFunc) Info() *interfaces.Info { func (obj *FilterFunc) Info() *interfaces.Info {
return &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, Memo: false,
Fast: false,
Spec: false,
Sig: obj.sig(), // helper Sig: obj.sig(), // helper
Err: obj.Validate(), Err: obj.Validate(),
} }
@@ -412,9 +414,9 @@ func (obj *FilterFunc) replaceSubGraph(subgraphInput interfaces.Func) error {
) )
obj.init.Txn.AddVertex(inputElemFunc) 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 { 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{ obj.init.Txn.AddEdge(subgraphInput, inputElemFunc, &interfaces.FuncEdge{

View File

@@ -204,8 +204,10 @@ func (obj *MapFunc) Validate() error {
// will return correct data. // will return correct data.
func (obj *MapFunc) Info() *interfaces.Info { func (obj *MapFunc) Info() *interfaces.Info {
return &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, Memo: false,
Fast: false,
Spec: false,
Sig: obj.sig(), // helper Sig: obj.sig(), // helper
Err: obj.Validate(), Err: obj.Validate(),
} }
@@ -439,9 +441,9 @@ func (obj *MapFunc) replaceSubGraph(subgraphInput interfaces.Func) error {
) )
obj.init.Txn.AddVertex(inputElemFunc) 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 { 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{ obj.init.Txn.AddEdge(subgraphInput, inputElemFunc, &interfaces.FuncEdge{

View File

@@ -161,7 +161,9 @@ func (obj *RangeFunc) Validate() error {
func (obj *RangeFunc) Info() *interfaces.Info { func (obj *RangeFunc) Info() *interfaces.Info {
return &interfaces.Info{ return &interfaces.Info{
Pure: true, Pure: true,
Memo: false, Memo: true,
Fast: true,
Spec: true,
Sig: obj.Type, Sig: obj.Type,
Err: obj.Validate(), Err: obj.Validate(),
} }

View File

@@ -39,6 +39,12 @@ import (
func init() { func init() {
simple.Register("len", &simple.Scaffold{ simple.Register("len", &simple.Scaffold{
I: &simple.Info{
Pure: true,
Memo: true,
Fast: true,
Spec: true,
},
T: types.NewType("func(?1) int"), T: types.NewType("func(?1) int"),
// TODO: should we add support for struct or func lengths? // TODO: should we add support for struct or func lengths?
C: simple.TypeMatch([]string{ C: simple.TypeMatch([]string{

View File

@@ -158,6 +158,13 @@ func (obj *ListConcatFunc) Build(typ *types.Type) (*types.Type, error) {
obj.Func = &wrapped.Func{ obj.Func = &wrapped.Func{
Name: obj.String(), Name: obj.String(),
FuncInfo: &wrapped.Info{
// TODO: dedup with below Info data
Pure: true,
Memo: true,
Fast: true,
Spec: true,
},
Type: typ, // .Copy(), 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) { func (obj *ListConcatFunc) Call(ctx context.Context, args []types.Value) (types.Value, error) {
values := []types.Value{} values := []types.Value{}
// TODO: Could speculation pass in non-lists here and cause a panic?
for _, x := range args { for _, x := range args {
values = append(values, x.List()...) values = append(values, x.List()...)
} }
@@ -217,7 +225,9 @@ func (obj *ListConcatFunc) Validate() error {
func (obj *ListConcatFunc) Info() *interfaces.Info { func (obj *ListConcatFunc) Info() *interfaces.Info {
return &interfaces.Info{ return &interfaces.Info{
Pure: true, Pure: true,
Memo: false, Memo: true,
Fast: true,
Spec: true,
Sig: obj.sig(), // helper Sig: obj.sig(), // helper
Err: obj.Validate(), Err: obj.Validate(),
} }

View File

@@ -202,6 +202,13 @@ func (obj *ListLookupFunc) Build(typ *types.Type) (*types.Type, error) {
obj.Func = &wrapped.Func{ obj.Func = &wrapped.Func{
Name: obj.String(), Name: obj.String(),
FuncInfo: &wrapped.Info{
// TODO: dedup with below Info data
Pure: true,
Memo: true,
Fast: true,
Spec: true,
},
Type: typ, // .Copy(), 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 // Call is the actual implementation here. This is used in lieu of the Stream
// function which we'd have these contents within. // function which we'd have these contents within.
func (obj *ListLookupFunc) Call(ctx context.Context, args []types.Value) (types.Value, error) { 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) l := (args[0]).(*types.ListValue)
index := args[1].Int() index := args[1].Int()
//zero := l.Type().Val.New() // the zero value //zero := l.Type().Val.New() // the zero value
@@ -273,7 +284,9 @@ func (obj *ListLookupFunc) Validate() error {
func (obj *ListLookupFunc) Info() *interfaces.Info { func (obj *ListLookupFunc) Info() *interfaces.Info {
return &interfaces.Info{ return &interfaces.Info{
Pure: true, Pure: true,
Memo: false, Memo: true,
Fast: true,
Spec: true,
Sig: obj.sig(), // helper Sig: obj.sig(), // helper
Err: obj.Validate(), Err: obj.Validate(),
} }

View File

@@ -45,6 +45,12 @@ const (
func init() { func init() {
simple.Register(ListLookupFuncName, &simple.Scaffold{ 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"), T: types.NewType("func(list []?1, index int) ?1"),
F: ListLookup, F: ListLookup,
}) })

View File

@@ -45,6 +45,12 @@ const (
func init() { func init() {
simple.Register(ListLookupDefaultFuncName, &simple.Scaffold{ 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"), T: types.NewType("func(list []?1, index int, default ?1) ?1"),
F: ListLookupDefault, F: ListLookupDefault,
}) })

View File

@@ -54,6 +54,8 @@ func init() {
funcs.ModuleRegister(ModuleName, PoolFuncName, func() interfaces.Func { return &PoolFunc{} }) // must register the func and name 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. // 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 // 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 // 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. // Info returns some static info about itself.
func (obj *PoolFunc) Info() *interfaces.Info { func (obj *PoolFunc) Info() *interfaces.Info {
return &interfaces.Info{ return &interfaces.Info{
Pure: true, Pure: false, // depends on local API
Memo: false, Memo: false,
Fast: false,
Spec: false,
Sig: types.NewType(fmt.Sprintf("func(%s str, %s str) int", absPathArgNameNamespace, absPathArgNameUID)), Sig: types.NewType(fmt.Sprintf("func(%s str, %s str) int", absPathArgNameNamespace, absPathArgNameUID)),
// TODO: add an optional config arg // TODO: add an optional config arg
//Sig: types.NewType(fmt.Sprintf("func(%s str, %s str, %s struct{}) int", absPathArgNameNamespace, absPathArgNameUID, absPathArgNameConfig)), //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 // Call this function with the input args and return the value if it is possible
// to do so at this time. // to do so at this time.
func (obj *PoolFunc) Call(ctx context.Context, args []types.Value) (types.Value, error) { 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. // Validation of these inputs happens in the Local API which does it.
namespace := args[0].Str() namespace := args[0].Str()
uid := args[1].Str() uid := args[1].Str()
// TODO: pass in config // TODO: pass in config
//config := args[2].???() //config := args[2].???()
if obj.init == nil {
return nil, funcs.ErrCantSpeculate
}
result, err := obj.init.Local.Pool(ctx, namespace, uid, nil) result, err := obj.init.Local.Pool(ctx, namespace, uid, nil)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@@ -61,6 +61,8 @@ func init() {
funcs.ModuleRegister(ModuleName, VarDirFuncName, func() interfaces.Func { return &VarDirFunc{} }) // must register the func and name 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 // 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 // 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 // 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. // Info returns some static info about itself.
func (obj *VarDirFunc) Info() *interfaces.Info { func (obj *VarDirFunc) Info() *interfaces.Info {
return &interfaces.Info{ return &interfaces.Info{
Pure: true, Pure: false, // TODO: depends on runtime dir path
Memo: false, Memo: false,
Fast: false,
Spec: false,
Sig: types.NewType(fmt.Sprintf("func(%s str) str", absPathArgNamePath)), 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 // Call this function with the input args and return the value if it is possible
// to do so at this time. // to do so at this time.
func (obj *VarDirFunc) Call(ctx context.Context, args []types.Value) (types.Value, error) { 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() reldir := args[0].Str()
if strings.HasPrefix(reldir, "/") { if strings.HasPrefix(reldir, "/") {
return nil, fmt.Errorf("path must be relative") 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)) p := fmt.Sprintf("%s/", path.Join(VarDirFunctionsPrefix, reldir))
if obj.init == nil {
return nil, funcs.ErrCantSpeculate
}
result, err := obj.init.Local.VarDir(ctx, p) result, err := obj.init.Local.VarDir(ctx, p)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@@ -148,6 +148,9 @@ func (obj *LookupFunc) FuncInfer(partialType *types.Type, partialValues []types.
// runs. // runs.
func (obj *LookupFunc) Build(typ *types.Type) (*types.Type, error) { func (obj *LookupFunc) Build(typ *types.Type) (*types.Type, error) {
// typ is the KindFunc signature we're trying to build... // 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 { if typ.Kind != types.KindFunc {
return nil, fmt.Errorf("input type must be of kind func") 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 { if obj.fn == nil {
return &interfaces.Info{ return &interfaces.Info{
Pure: true, Pure: true,
Memo: false, Memo: true,
Fast: true,
Spec: true,
Sig: types.NewType("func(?1, ?2) ?3"), // func kind Sig: types.NewType("func(?1, ?2) ?3"), // func kind
Err: obj.Validate(), Err: obj.Validate(),
} }
@@ -253,6 +258,9 @@ func (obj *LookupFunc) Stream(ctx context.Context) error {
// Call returns the result of this function. // Call returns the result of this function.
func (obj *LookupFunc) Call(ctx context.Context, args []types.Value) (types.Value, error) { 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) cf, ok := obj.fn.(interfaces.CallableFunc)
if !ok { if !ok {
// programming error // programming error

View File

@@ -93,6 +93,9 @@ func (obj *LookupDefaultFunc) ArgGen(index int) (string, error) {
// runs. // runs.
func (obj *LookupDefaultFunc) Build(typ *types.Type) (*types.Type, error) { func (obj *LookupDefaultFunc) Build(typ *types.Type) (*types.Type, error) {
// typ is the KindFunc signature we're trying to build... // 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 { if typ.Kind != types.KindFunc {
return nil, fmt.Errorf("input type must be of kind func") 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 { if obj.fn == nil {
return &interfaces.Info{ return &interfaces.Info{
Pure: true, Pure: true,
Memo: false, Memo: true,
Fast: true,
Spec: true,
Sig: types.NewType("func(?1, ?2, ?3) ?3"), // func kind Sig: types.NewType("func(?1, ?2, ?3) ?3"), // func kind
Err: obj.Validate(), Err: obj.Validate(),
} }
@@ -199,6 +204,9 @@ func (obj *LookupDefaultFunc) Stream(ctx context.Context) error {
// Call returns the result of this function. // Call returns the result of this function.
func (obj *LookupDefaultFunc) Call(ctx context.Context, args []types.Value) (types.Value, error) { 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) cf, ok := obj.fn.(interfaces.CallableFunc)
if !ok { if !ok {
// programming error // programming error

View File

@@ -39,6 +39,12 @@ import (
func init() { func init() {
simple.ModuleRegister(ModuleName, "keys", &simple.Scaffold{ 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? // TODO: Maybe saying ?0 could mean we don't care about that type?
T: types.NewType("func(x map{?1: ?2}) []?1"), T: types.NewType("func(x map{?1: ?2}) []?1"),
F: MapKeys, F: MapKeys,

View File

@@ -204,6 +204,13 @@ func (obj *MapLookupFunc) Build(typ *types.Type) (*types.Type, error) {
obj.Func = &wrapped.Func{ obj.Func = &wrapped.Func{
Name: obj.String(), Name: obj.String(),
FuncInfo: &wrapped.Info{
// TODO: dedup with below Info data
Pure: true,
Memo: true,
Fast: true,
Spec: true,
},
Type: typ, // .Copy(), 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 // Call is the actual implementation here. This is used in lieu of the Stream
// function which we'd have these contents within. // function which we'd have these contents within.
func (obj *MapLookupFunc) Call(ctx context.Context, args []types.Value) (types.Value, error) { 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) m := (args[0]).(*types.MapValue)
key := args[1] key := args[1]
//zero := m.Type().Val.New() // the zero value //zero := m.Type().Val.New() // the zero value
@@ -266,7 +276,9 @@ func (obj *MapLookupFunc) Validate() error {
func (obj *MapLookupFunc) Info() *interfaces.Info { func (obj *MapLookupFunc) Info() *interfaces.Info {
return &interfaces.Info{ return &interfaces.Info{
Pure: true, Pure: true,
Memo: false, Memo: true,
Fast: true,
Spec: true,
Sig: obj.sig(), // helper Sig: obj.sig(), // helper
Err: obj.Validate(), Err: obj.Validate(),
} }

View File

@@ -39,6 +39,12 @@ import (
func init() { func init() {
simple.ModuleRegister(ModuleName, "values", &simple.Scaffold{ 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? // TODO: Maybe saying ?0 could mean we don't care about that type?
T: types.NewType("func(x map{?1: ?2}) []?2"), T: types.NewType("func(x map{?1: ?2}) []?2"),
F: MapValues, F: MapValues,

View File

@@ -43,6 +43,12 @@ const (
func init() { func init() {
simple.Register(MapLookupFuncName, &simple.Scaffold{ 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"), T: types.NewType("func(map map{?1: ?2}, key ?1) ?2"),
F: MapLookup, F: MapLookup,
}) })

View File

@@ -43,6 +43,12 @@ const (
func init() { func init() {
simple.Register(MapLookupDefaultFuncName, &simple.Scaffold{ 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"), T: types.NewType("func(map map{?1: ?2}, key ?1, default ?2) ?2"),
F: MapLookupDefault, F: MapLookupDefault,
}) })

View File

@@ -42,6 +42,12 @@ func init() {
typInt := types.NewType("func() int") typInt := types.NewType("func() int")
typFloat := types.NewType("func() float") typFloat := types.NewType("func() float")
multi.ModuleRegister(ModuleName, "fortytwo", &multi.Scaffold{ multi.ModuleRegister(ModuleName, "fortytwo", &multi.Scaffold{
I: &multi.Info{
Pure: true,
Memo: true,
Fast: true,
Spec: true,
},
T: types.NewType("func() ?1"), T: types.NewType("func() ?1"),
M: multi.TypeMatch(map[string]interfaces.FuncSig{ M: multi.TypeMatch(map[string]interfaces.FuncSig{
"func() int": fortyTwo(typInt), "func() int": fortyTwo(typInt),

View File

@@ -38,6 +38,12 @@ import (
func init() { func init() {
simple.ModuleRegister(ModuleName, "minus1", &simple.Scaffold{ simple.ModuleRegister(ModuleName, "minus1", &simple.Scaffold{
I: &simple.Info{
Pure: true,
Memo: true,
Fast: true,
Spec: true,
},
T: types.NewType("func(x int) int"), T: types.NewType("func(x int) int"),
F: Minus1, F: Minus1,
}) })

View File

@@ -40,6 +40,12 @@ import (
func init() { func init() {
simple.ModuleRegister(ModuleName, "mod", &simple.Scaffold{ 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 T: types.NewType("func(?1, ?1) ?1"), // all int or float
C: simple.TypeMatch([]string{ C: simple.TypeMatch([]string{
"func(int, int) int", "func(int, int) int",

View File

@@ -40,6 +40,12 @@ import (
func init() { func init() {
simple.ModuleRegister(ModuleName, "pow", &simple.Scaffold{ 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"), T: types.NewType("func(x float, y float) float"),
F: Pow, F: Pow,
}) })

View File

@@ -40,6 +40,12 @@ import (
func init() { func init() {
simple.ModuleRegister(ModuleName, "sqrt", &simple.Scaffold{ simple.ModuleRegister(ModuleName, "sqrt", &simple.Scaffold{
I: &simple.Info{
Pure: true,
Memo: true,
Fast: true,
Spec: true,
},
T: types.NewType("func(x float) float"), T: types.NewType("func(x float) float"),
F: Sqrt, F: Sqrt,
}) })

View File

@@ -44,22 +44,52 @@ import (
func init() { func init() {
simple.ModuleRegister(ModuleName, "cidr_to_ip", &simple.Scaffold{ 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"), T: types.NewType("func(a str) str"),
F: CidrToIP, F: CidrToIP,
}) })
simple.ModuleRegister(ModuleName, "cidr_to_prefix", &simple.Scaffold{ 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"), T: types.NewType("func(a str) str"),
F: CidrToPrefix, F: CidrToPrefix,
}) })
simple.ModuleRegister(ModuleName, "cidr_to_mask", &simple.Scaffold{ 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"), T: types.NewType("func(a str) str"),
F: CidrToMask, F: CidrToMask,
}) })
simple.ModuleRegister(ModuleName, "cidr_to_first", &simple.Scaffold{ 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"), T: types.NewType("func(a str) str"),
F: CidrToFirst, F: CidrToFirst,
}) })
simple.ModuleRegister(ModuleName, "cidr_to_last", &simple.Scaffold{ 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"), T: types.NewType("func(a str) str"),
F: CidrToLast, F: CidrToLast,
}) })

View File

@@ -41,14 +41,32 @@ import (
func init() { func init() {
simple.ModuleRegister(ModuleName, "is_mac", &simple.Scaffold{ 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"), T: types.NewType("func(a str) bool"),
F: IsMac, F: IsMac,
}) })
simple.ModuleRegister(ModuleName, "macfmt", &simple.Scaffold{ simple.ModuleRegister(ModuleName, "macfmt", &simple.Scaffold{
I: &simple.Info{
Pure: true,
Memo: true,
Fast: true,
Spec: true,
},
T: types.NewType("func(a str) str"), T: types.NewType("func(a str) str"),
F: MacFmt, F: MacFmt,
}) })
simple.ModuleRegister(ModuleName, "oldmacfmt", &simple.Scaffold{ simple.ModuleRegister(ModuleName, "oldmacfmt", &simple.Scaffold{
I: &simple.Info{
Pure: true,
Memo: true,
Fast: true,
Spec: true,
},
T: types.NewType("func(a str) str"), T: types.NewType("func(a str) str"),
F: OldMacFmt, F: OldMacFmt,
}) })

View File

@@ -39,6 +39,12 @@ import (
func init() { func init() {
simple.ModuleRegister(ModuleName, "macs", &simple.Scaffold{ 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"), T: types.NewType("func() []str"),
F: Macs, F: Macs,
}) })

View File

@@ -56,6 +56,12 @@ var urlParserReturnType = fmt.Sprintf(
func init() { func init() {
simple.ModuleRegister(ModuleName, "url_parser", &simple.Scaffold{ 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)), T: types.NewType(fmt.Sprintf("func(str) %s", urlParserReturnType)),
F: URLParser, F: URLParser,
}) })

View File

@@ -39,6 +39,13 @@ import (
func init() { func init() {
simple.ModuleRegister(ModuleName, "args", &simple.Scaffold{ 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"), T: types.NewType("func() []str"),
F: Args, F: Args,
}) })

View File

@@ -44,6 +44,12 @@ var typeParseDistroUID = types.NewType(fmt.Sprintf("func(str) %s", structDistroU
func init() { func init() {
simple.ModuleRegister(ModuleName, "parse_distro_uid", &simple.Scaffold{ simple.ModuleRegister(ModuleName, "parse_distro_uid", &simple.Scaffold{
I: &simple.Info{
Pure: true,
Memo: true,
Fast: true,
Spec: true,
},
T: typeParseDistroUID, T: typeParseDistroUID,
F: ParseDistroUID, F: ParseDistroUID,
}) })

View File

@@ -39,6 +39,13 @@ import (
func init() { func init() {
simple.ModuleRegister(ModuleName, "expand_home", &simple.Scaffold{ 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"), T: types.NewType("func(str) str"),
F: ExpandHome, F: ExpandHome,
}) })

View File

@@ -40,6 +40,13 @@ import (
func init() { func init() {
simple.ModuleRegister(ModuleName, "family", &simple.Scaffold{ 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"), T: types.NewType("func() str"),
F: Family, F: Family,
}) })
@@ -62,14 +69,35 @@ func init() {
// TODO: Create a family method that will return a giant struct. // TODO: Create a family method that will return a giant struct.
simple.ModuleRegister(ModuleName, "is_family_redhat", &simple.Scaffold{ 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"), T: types.NewType("func() bool"),
F: IsFamilyRedHat, F: IsFamilyRedHat,
}) })
simple.ModuleRegister(ModuleName, "is_family_debian", &simple.Scaffold{ 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"), T: types.NewType("func() bool"),
F: IsFamilyDebian, F: IsFamilyDebian,
}) })
simple.ModuleRegister(ModuleName, "is_family_archlinux", &simple.Scaffold{ 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"), T: types.NewType("func() bool"),
F: IsFamilyArchLinux, F: IsFamilyArchLinux,
}) })

View File

@@ -77,6 +77,13 @@ var virtualizationVendorSlice = []string{
func init() { func init() {
simple.ModuleRegister(ModuleName, "is_virtual", &simple.Scaffold{ 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"), T: types.NewType("func() bool"),
F: IsVirtual, F: IsVirtual,
}) })

View File

@@ -97,6 +97,8 @@ func (obj *ReadFileFunc) Info() *interfaces.Info {
return &interfaces.Info{ return &interfaces.Info{
Pure: false, // maybe false because the file contents can change Pure: false, // maybe false because the file contents can change
Memo: false, Memo: false,
Fast: false,
Spec: false,
Sig: types.NewType(fmt.Sprintf("func(%s str) str", readFileArgNameFilename)), 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 // Call this function with the input args and return the value if it is possible
// to do so at this time. // to do so at this time.
func (obj *ReadFileFunc) Call(ctx context.Context, args []types.Value) (types.Value, error) { 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() filename := args[0].Str()
// read file... // read file...

View File

@@ -91,6 +91,8 @@ func (obj *SystemFunc) Info() *interfaces.Info {
return &interfaces.Info{ return &interfaces.Info{
Pure: false, // definitely false Pure: false, // definitely false
Memo: false, Memo: false,
Fast: false,
Spec: false,
Sig: types.NewType(fmt.Sprintf("func(%s str) str", systemArgNameCmd)), Sig: types.NewType(fmt.Sprintf("func(%s str) str", systemArgNameCmd)),
Err: obj.Validate(), Err: obj.Validate(),
} }

View File

@@ -39,6 +39,12 @@ import (
func init() { func init() {
simple.Register("panic", &simple.Scaffold{ 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 T: types.NewType("func(x ?1) bool"), // ?1 is bool or str
C: simple.TypeMatch([]string{ C: simple.TypeMatch([]string{
"func(x bool) bool", "func(x bool) bool",

View File

@@ -96,6 +96,9 @@ func (obj *Random1Func) Validate() error {
func (obj *Random1Func) Info() *interfaces.Info { func (obj *Random1Func) Info() *interfaces.Info {
return &interfaces.Info{ return &interfaces.Info{
Pure: false, Pure: false,
Memo: false,
Fast: false,
Spec: false,
Sig: types.NewType(fmt.Sprintf("func(%s int) str", random1ArgNameLength)), Sig: types.NewType(fmt.Sprintf("func(%s int) str", random1ArgNameLength)),
Err: obj.Validate(), Err: obj.Validate(),
} }

View File

@@ -40,6 +40,12 @@ import (
func init() { func init() {
simple.ModuleRegister(ModuleName, "match", &simple.Scaffold{ 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"), T: types.NewType("func(pattern str, s str) bool"),
F: Match, F: Match,
}) })

View File

@@ -39,6 +39,12 @@ import (
func init() { func init() {
simple.ModuleRegister(ModuleName, "join_nonempty", &simple.Scaffold{ 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"), T: types.NewType("func(s []str, sep str) str"),
F: JoinNonempty, F: JoinNonempty,
}) })

View File

@@ -39,10 +39,22 @@ import (
func init() { func init() {
simple.ModuleRegister(ModuleName, "left_pad", &simple.Scaffold{ 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"), T: types.NewType("func(s str, pad str, len int) str"),
F: LeftPad, F: LeftPad,
}) })
simple.ModuleRegister(ModuleName, "right_pad", &simple.Scaffold{ 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"), T: types.NewType("func(s str, pad str, len int) str"),
F: RightPad, F: RightPad,
}) })

View File

@@ -39,6 +39,12 @@ import (
func init() { func init() {
simple.ModuleRegister(ModuleName, "split", &simple.Scaffold{ 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"), T: types.NewType("func(a str, b str) []str"),
F: Split, F: Split,
}) })

View File

@@ -39,6 +39,12 @@ import (
func init() { func init() {
simple.ModuleRegister(ModuleName, "substr", &simple.Scaffold{ 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"), T: types.NewType("func(s str, low int, high int) str"),
F: SubStr, F: SubStr,
}) })

View File

@@ -39,6 +39,12 @@ import (
func init() { func init() {
simple.ModuleRegister(ModuleName, "to_lower", &simple.Scaffold{ 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"), T: types.NewType("func(a str) str"),
F: ToLower, F: ToLower,
}) })

View File

@@ -269,7 +269,9 @@ func (obj *StructLookupFunc) Info() *interfaces.Info {
} }
return &interfaces.Info{ return &interfaces.Info{
Pure: true, Pure: true,
Memo: false, Memo: true,
Fast: true,
Spec: true,
Sig: sig, Sig: sig,
Err: obj.Validate(), Err: obj.Validate(),
} }
@@ -339,6 +341,9 @@ func (obj *StructLookupFunc) Stream(ctx context.Context) error {
// Call returns the result of this function. // Call returns the result of this function.
func (obj *StructLookupFunc) Call(ctx context.Context, args []types.Value) (types.Value, error) { 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) st := args[0].(*types.StructValue)
field := args[1].Str() field := args[1].Str()

View File

@@ -266,7 +266,9 @@ func (obj *StructLookupOptionalFunc) Info() *interfaces.Info {
} }
return &interfaces.Info{ return &interfaces.Info{
Pure: true, Pure: true,
Memo: false, Memo: true,
Fast: true,
Spec: true,
Sig: sig, Sig: sig,
Err: obj.Validate(), Err: obj.Validate(),
} }
@@ -345,6 +347,9 @@ func (obj *StructLookupOptionalFunc) Stream(ctx context.Context) error {
// Call returns the result of this function. // Call returns the result of this function.
func (obj *StructLookupOptionalFunc) Call(ctx context.Context, args []types.Value) (types.Value, error) { 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) st := args[0].(*types.StructValue)
field := args[1].Str() field := args[1].Str()
optional := args[2] optional := args[2]

View File

@@ -76,6 +76,8 @@ func (obj *CPUCountFact) String() string {
// Info returns static typing info about what the fact returns. // Info returns static typing info about what the fact returns.
func (obj *CPUCountFact) Info() *facts.Info { func (obj *CPUCountFact) Info() *facts.Info {
return &facts.Info{ return &facts.Info{
Pure: false,
Memo: false,
Output: types.NewType("int"), Output: types.NewType("int"),
} }
} }

View File

@@ -40,18 +40,42 @@ import (
func init() { func init() {
simple.ModuleRegister(ModuleName, "getenv", &simple.Scaffold{ simple.ModuleRegister(ModuleName, "getenv", &simple.Scaffold{
I: &simple.Info{
Pure: false,
Memo: false,
Fast: true,
Spec: false,
},
T: types.NewType("func(str) str"), T: types.NewType("func(str) str"),
F: GetEnv, F: GetEnv,
}) })
simple.ModuleRegister(ModuleName, "defaultenv", &simple.Scaffold{ simple.ModuleRegister(ModuleName, "defaultenv", &simple.Scaffold{
I: &simple.Info{
Pure: false,
Memo: false,
Fast: true,
Spec: false,
},
T: types.NewType("func(str, str) str"), T: types.NewType("func(str, str) str"),
F: DefaultEnv, F: DefaultEnv,
}) })
simple.ModuleRegister(ModuleName, "hasenv", &simple.Scaffold{ simple.ModuleRegister(ModuleName, "hasenv", &simple.Scaffold{
I: &simple.Info{
Pure: false,
Memo: false,
Fast: true,
Spec: false,
},
T: types.NewType("func(str) bool"), T: types.NewType("func(str) bool"),
F: HasEnv, F: HasEnv,
}) })
simple.ModuleRegister(ModuleName, "env", &simple.Scaffold{ simple.ModuleRegister(ModuleName, "env", &simple.Scaffold{
I: &simple.Info{
Pure: false,
Memo: false,
Fast: true,
Spec: false,
},
T: types.NewType("func() map{str: str}"), T: types.NewType("func() map{str: str}"),
F: Env, F: Env,
}) })

View File

@@ -70,6 +70,8 @@ func (obj *LoadFact) String() string {
// Info returns some static info about itself. // Info returns some static info about itself.
func (obj *LoadFact) Info() *facts.Info { func (obj *LoadFact) Info() *facts.Info {
return &facts.Info{ return &facts.Info{
Pure: false,
Memo: false,
Output: types.NewType(loadSignature), Output: types.NewType(loadSignature),
} }
} }

View File

@@ -62,6 +62,8 @@ func (obj *UptimeFact) String() string {
// Info returns some static info about itself. // Info returns some static info about itself.
func (obj *UptimeFact) Info() *facts.Info { func (obj *UptimeFact) Info() *facts.Info {
return &facts.Info{ return &facts.Info{
Pure: false,
Memo: false,
Output: types.TypeInt, Output: types.TypeInt,
} }
} }

View File

@@ -71,6 +71,8 @@ func (obj *FastCountFact) String() string {
// Info returns some static info about itself. // Info returns some static info about itself.
func (obj *FastCountFact) Info() *facts.Info { func (obj *FastCountFact) Info() *facts.Info {
return &facts.Info{ return &facts.Info{
Pure: false,
Memo: false,
Output: types.NewType("int"), 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. // 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) { 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 obj.mutex.Lock() // TODO: could be a read lock
count := obj.count count := obj.count
obj.mutex.Unlock() obj.mutex.Unlock()

View File

@@ -122,6 +122,12 @@ func init() {
}) })
simple.ModuleRegister(ModuleName, OneInstanceBFuncName, &simple.Scaffold{ 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"), T: types.NewType("func() str"),
F: func(context.Context, []types.Value) (types.Value, error) { F: func(context.Context, []types.Value) (types.Value, error) {
oneInstanceBMutex.Lock() oneInstanceBMutex.Lock()
@@ -135,6 +141,12 @@ func init() {
D: &OneInstanceFact{}, D: &OneInstanceFact{},
}) })
simple.ModuleRegister(ModuleName, OneInstanceDFuncName, &simple.Scaffold{ 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"), T: types.NewType("func() str"),
F: func(context.Context, []types.Value) (types.Value, error) { F: func(context.Context, []types.Value) (types.Value, error) {
oneInstanceDMutex.Lock() oneInstanceDMutex.Lock()
@@ -148,6 +160,12 @@ func init() {
D: &OneInstanceFact{}, D: &OneInstanceFact{},
}) })
simple.ModuleRegister(ModuleName, OneInstanceFFuncName, &simple.Scaffold{ 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"), T: types.NewType("func() str"),
F: func(context.Context, []types.Value) (types.Value, error) { F: func(context.Context, []types.Value) (types.Value, error) {
oneInstanceFMutex.Lock() oneInstanceFMutex.Lock()
@@ -161,6 +179,12 @@ func init() {
D: &OneInstanceFact{}, D: &OneInstanceFact{},
}) })
simple.ModuleRegister(ModuleName, OneInstanceHFuncName, &simple.Scaffold{ 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"), T: types.NewType("func() str"),
F: func(context.Context, []types.Value) (types.Value, error) { F: func(context.Context, []types.Value) (types.Value, error) {
oneInstanceHMutex.Lock() oneInstanceHMutex.Lock()

View File

@@ -41,6 +41,12 @@ import (
func init() { func init() {
simple.ModuleRegister(ModuleName, "hostname_mapper", &simple.Scaffold{ 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"), T: types.NewType("func(map{str:str}) str"),
F: HostnameMapper, F: HostnameMapper,
}) })

View File

@@ -206,6 +206,8 @@ func (obj *GetFunc) Info() *interfaces.Info {
return &interfaces.Info{ return &interfaces.Info{
Pure: false, // definitely false Pure: false, // definitely false
Memo: false, Memo: false,
Fast: false,
Spec: false,
Sig: sig, Sig: sig,
Err: obj.Validate(), 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 // to do so at this time. This was previously getValue which gets the value
// we're looking for. // we're looking for.
func (obj *GetFunc) Call(ctx context.Context, args []types.Value) (types.Value, error) { 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() key := args[0].Str()
typ, exists := obj.Info().Sig.Out.Map[getFieldNameValue] // type of value field 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... // step that might be needed if the value started out empty...
// TODO: We could even add a stored: bool field in the returned struct! // TODO: We could even add a stored: bool field in the returned struct!
isReady := true // assume true isReady := true // assume true
if obj.init == nil {
return nil, funcs.ErrCantSpeculate
}
val, err := obj.init.Local.ValueGet(ctx, key) val, err := obj.init.Local.ValueGet(ctx, key)
if err != nil { if err != nil {
return nil, errwrap.Wrapf(err, "channel read failed on `%s`", key) return nil, errwrap.Wrapf(err, "channel read failed on `%s`", key)

View File

@@ -118,6 +118,8 @@ func (obj *ResFunc) Info() *interfaces.Info {
return &interfaces.Info{ return &interfaces.Info{
Pure: false, Pure: false,
Memo: false, Memo: false,
Fast: false,
Spec: false,
Sig: obj.sig(), Sig: obj.sig(),
Err: obj.Validate(), 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 // to do so at this time. This was previously getValue which gets the value
// we're looking for. // we're looking for.
func (obj *ResFunc) Call(ctx context.Context, args []types.Value) (types.Value, error) { 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() kind := args[0].Str()
if kind == "" { if kind == "" {
return nil, fmt.Errorf("resource kind is empty") 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) filters = append(filters, filter)
if obj.init == nil {
return nil, funcs.ErrCantSpeculate
}
resOutput, err := obj.init.World.ResCollect(ctx, filters) resOutput, err := obj.init.World.ResCollect(ctx, filters)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@@ -91,6 +91,8 @@ func (obj *ExchangeFunc) Info() *interfaces.Info {
return &interfaces.Info{ return &interfaces.Info{
Pure: false, // definitely false Pure: false, // definitely false
Memo: false, Memo: false,
Fast: false,
Spec: false,
// TODO: do we want to allow this to be statically polymorphic, // TODO: do we want to allow this to be statically polymorphic,
// and have value be any type we might want? // and have value be any type we might want?
// output is map of: hostname => value // output is map of: hostname => value

View File

@@ -97,6 +97,8 @@ func (obj *GetValFunc) Info() *interfaces.Info {
return &interfaces.Info{ return &interfaces.Info{
Pure: false, // definitely false Pure: false, // definitely false
Memo: false, Memo: false,
Fast: false,
Spec: false,
// output is a struct with two fields: // output is a struct with two fields:
// value is the zero value if not exists. A bool for that in other field. // 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)), 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 // to do so at this time. This was previously getValue which gets the value
// we're looking for. // we're looking for.
func (obj *GetValFunc) Call(ctx context.Context, args []types.Value) (types.Value, error) { 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() key := args[0].Str()
exists := true // assume true exists := true // assume true
if obj.init == nil {
return nil, funcs.ErrCantSpeculate
}
val, err := obj.init.World.StrGet(ctx, key) val, err := obj.init.World.StrGet(ctx, key)
if err != nil && obj.init.World.StrIsNotExist(err) { if err != nil && obj.init.World.StrIsNotExist(err) {
exists = false // val doesn't exist exists = false // val doesn't exist

View File

@@ -93,6 +93,8 @@ func (obj *KVLookupFunc) Info() *interfaces.Info {
return &interfaces.Info{ return &interfaces.Info{
Pure: false, // definitely false Pure: false, // definitely false
Memo: false, Memo: false,
Fast: false,
Spec: false,
// output is map of: hostname => value // output is map of: hostname => value
Sig: types.NewType(fmt.Sprintf("func(%s str) map{str: str}", kvLookupArgNameNamespace)), Sig: types.NewType(fmt.Sprintf("func(%s str) map{str: str}", kvLookupArgNameNamespace)),
Err: obj.Validate(), 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 // to do so at this time. This was previously buildMap, which builds the result
// map which we'll need. It uses struct variables. // map which we'll need. It uses struct variables.
func (obj *KVLookupFunc) Call(ctx context.Context, args []types.Value) (types.Value, error) { 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() namespace := args[0].Str()
if obj.init == nil {
return nil, funcs.ErrCantSpeculate
}
keyMap, err := obj.init.World.StrMapGet(ctx, namespace) keyMap, err := obj.init.World.StrMapGet(ctx, namespace)
if err != nil { if err != nil {
return nil, errwrap.Wrapf(err, "channel read failed on `%s`", namespace) return nil, errwrap.Wrapf(err, "channel read failed on `%s`", namespace)

View File

@@ -283,6 +283,8 @@ func (obj *ScheduleFunc) Info() *interfaces.Info {
return &interfaces.Info{ return &interfaces.Info{
Pure: false, // definitely false Pure: false, // definitely false
Memo: false, Memo: false,
Fast: false,
Spec: false,
// output is list of hostnames chosen // output is list of hostnames chosen
Sig: sig, // func kind Sig: sig, // func kind
Err: obj.Validate(), Err: obj.Validate(),

View File

@@ -40,6 +40,17 @@ import (
"github.com/purpleidea/mgmt/lang/types" "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 // 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. // should never touch this map directly. Use methods like Register instead.
var RegisteredFacts = make(map[string]func() Fact) // must initialize 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 // used for static analysis and type checking. If you break this contract, you
// might cause a panic. // might cause a panic.
type Info struct { 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!) Output *types.Type // output value type (must not change over time!)
Err error // did this fact validate? Err error // did this fact validate?
} }

View File

@@ -64,8 +64,10 @@ func (obj *FactFunc) Validate() error {
// Info returns some static info about itself. // Info returns some static info about itself.
func (obj *FactFunc) Info() *interfaces.Info { func (obj *FactFunc) Info() *interfaces.Info {
return &interfaces.Info{ return &interfaces.Info{
Pure: false, Pure: obj.Fact.Info().Pure,
Memo: false, Memo: obj.Fact.Info().Memo,
Fast: obj.Fact.Info().Fast,
Spec: obj.Fact.Info().Spec,
Sig: &types.Type{ Sig: &types.Type{
Kind: types.KindFunc, Kind: types.KindFunc,
// if Ord or Map are nil, this will panic things! // if Ord or Map are nil, this will panic things!

View File

@@ -40,26 +40,68 @@ import (
func init() { func init() {
simple.ModuleRegister("golang/testpkg", "all_kind", &simple.Scaffold{ 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"), T: types.NewType("func(x int, y str) float"),
F: TestpkgAllKind, F: TestpkgAllKind,
}) })
simple.ModuleRegister("golang/testpkg", "to_upper", &simple.Scaffold{ 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"), T: types.NewType("func(s str) str"),
F: TestpkgToUpper, F: TestpkgToUpper,
}) })
simple.ModuleRegister("golang/testpkg", "max", &simple.Scaffold{ 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"), T: types.NewType("func(x float, y float) float"),
F: TestpkgMax, F: TestpkgMax,
}) })
simple.ModuleRegister("golang/testpkg", "with_error", &simple.Scaffold{ 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"), T: types.NewType("func(s str) str"),
F: TestpkgWithError, F: TestpkgWithError,
}) })
simple.ModuleRegister("golang/testpkg", "with_int", &simple.Scaffold{ 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"), T: types.NewType("func(s float, i int, x int, j int, k int, b bool, t str) str"),
F: TestpkgWithInt, F: TestpkgWithInt,
}) })
simple.ModuleRegister("golang/testpkg", "super_byte", &simple.Scaffold{ 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"), T: types.NewType("func(s str, t str) str"),
F: TestpkgSuperByte, F: TestpkgSuperByte,
}) })

View File

@@ -40,6 +40,13 @@ import (
func init() { func init() {
{{ range $i, $func := .Functions }} simple.ModuleRegister("{{$func.MgmtPackage}}", "{{$func.MclName}}", &simple.Scaffold{ {{ 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}}"), T: types.NewType("{{$func.Signature}}"),
F: {{$func.InternalName}}, F: {{$func.InternalName}},
}) })

View File

@@ -38,6 +38,7 @@ import (
docsUtil "github.com/purpleidea/mgmt/docs/util" docsUtil "github.com/purpleidea/mgmt/docs/util"
"github.com/purpleidea/mgmt/lang/interfaces" "github.com/purpleidea/mgmt/lang/interfaces"
"github.com/purpleidea/mgmt/util"
) )
const ( const (
@@ -113,6 +114,15 @@ const (
// CollectFuncOutType is the expected return type, the data field is an // CollectFuncOutType is the expected return type, the data field is an
// encoded resource blob. // encoded resource blob.
CollectFuncOutType = "[]" + CollectFuncOutStruct 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 // registeredFuncs is a global map of all possible funcs which can be used. You

View File

@@ -45,9 +45,20 @@ import (
// RegisteredFuncs maps a function name to the corresponding function scaffold. // RegisteredFuncs maps a function name to the corresponding function scaffold.
var RegisteredFuncs = make(map[string]*Scaffold) // must initialize 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 // Scaffold holds the necessary data to build a (possibly polymorphic) function
// with this API. // with this API.
type Scaffold struct { 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. // 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 // At a minimum, this must be a `func(?1) ?2` as a naked `?1` is not
// allowed. (TODO: Because of ArgGen.) // 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)) 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 // register a copy in the main function database
funcs.Register(name, func() interfaces.Func { funcs.Register(name, func() interfaces.Func {
return &Func{ return &Func{
Metadata: metadata, Metadata: metadata,
WrappedFunc: &wrapped.Func{ WrappedFunc: &wrapped.Func{
Name: name, Name: name,
FuncInfo: funcInfo,
// NOTE: It might be more correct to Copy here, // NOTE: It might be more correct to Copy here,
// but we do the copy inside of ExprFunc.Copy() // but we do the copy inside of ExprFunc.Copy()
// instead, so that the same type can be unified // instead, so that the same type can be unified

View File

@@ -56,7 +56,14 @@ const (
) )
func init() { func init() {
info := &simple.Info{
Pure: true,
Memo: true,
Fast: true,
Spec: true,
}
RegisterOperator("+", &simple.Scaffold{ RegisterOperator("+", &simple.Scaffold{
I: info,
T: types.NewType("func(?1, ?1) ?1"), T: types.NewType("func(?1, ?1) ?1"),
C: simple.TypeMatch([]string{ C: simple.TypeMatch([]string{
"func(str, str) str", // concatenation "func(str, str) str", // concatenation
@@ -91,6 +98,7 @@ func init() {
}) })
RegisterOperator("-", &simple.Scaffold{ RegisterOperator("-", &simple.Scaffold{
I: info,
T: types.NewType("func(?1, ?1) ?1"), T: types.NewType("func(?1, ?1) ?1"),
C: simple.TypeMatch([]string{ C: simple.TypeMatch([]string{
"func(int, int) int", // subtraction "func(int, int) int", // subtraction
@@ -115,6 +123,7 @@ func init() {
}) })
RegisterOperator("*", &simple.Scaffold{ RegisterOperator("*", &simple.Scaffold{
I: info,
T: types.NewType("func(?1, ?1) ?1"), T: types.NewType("func(?1, ?1) ?1"),
C: simple.TypeMatch([]string{ C: simple.TypeMatch([]string{
"func(int, int) int", // multiplication "func(int, int) int", // multiplication
@@ -141,6 +150,7 @@ func init() {
// don't add: `func(int, float) float` or: `func(float, int) float` // don't add: `func(int, float) float` or: `func(float, int) float`
RegisterOperator("/", &simple.Scaffold{ RegisterOperator("/", &simple.Scaffold{
I: info,
T: types.NewType("func(?1, ?1) float"), T: types.NewType("func(?1, ?1) float"),
C: simple.TypeMatch([]string{ C: simple.TypeMatch([]string{
"func(int, int) float", // division "func(int, int) float", // division
@@ -173,6 +183,7 @@ func init() {
}) })
RegisterOperator("==", &simple.Scaffold{ RegisterOperator("==", &simple.Scaffold{
I: info,
T: types.NewType("func(?1, ?1) bool"), T: types.NewType("func(?1, ?1) bool"),
C: func(typ *types.Type) error { C: func(typ *types.Type) error {
//if typ == nil { // happens within iter //if typ == nil { // happens within iter
@@ -218,6 +229,7 @@ func init() {
}) })
RegisterOperator("!=", &simple.Scaffold{ RegisterOperator("!=", &simple.Scaffold{
I: info,
T: types.NewType("func(?1, ?1) bool"), T: types.NewType("func(?1, ?1) bool"),
C: func(typ *types.Type) error { C: func(typ *types.Type) error {
//if typ == nil { // happens within iter //if typ == nil { // happens within iter
@@ -263,6 +275,7 @@ func init() {
}) })
RegisterOperator("<", &simple.Scaffold{ RegisterOperator("<", &simple.Scaffold{
I: info,
T: types.NewType("func(?1, ?1) bool"), T: types.NewType("func(?1, ?1) bool"),
C: simple.TypeMatch([]string{ C: simple.TypeMatch([]string{
"func(int, int) bool", // less-than "func(int, int) bool", // less-than
@@ -288,6 +301,7 @@ func init() {
}) })
RegisterOperator(">", &simple.Scaffold{ RegisterOperator(">", &simple.Scaffold{
I: info,
T: types.NewType("func(?1, ?1) bool"), T: types.NewType("func(?1, ?1) bool"),
C: simple.TypeMatch([]string{ C: simple.TypeMatch([]string{
"func(int, int) bool", // greater-than "func(int, int) bool", // greater-than
@@ -313,6 +327,7 @@ func init() {
}) })
RegisterOperator("<=", &simple.Scaffold{ RegisterOperator("<=", &simple.Scaffold{
I: info,
T: types.NewType("func(?1, ?1) bool"), T: types.NewType("func(?1, ?1) bool"),
C: simple.TypeMatch([]string{ C: simple.TypeMatch([]string{
"func(int, int) bool", // less-than-equal "func(int, int) bool", // less-than-equal
@@ -338,6 +353,7 @@ func init() {
}) })
RegisterOperator(">=", &simple.Scaffold{ RegisterOperator(">=", &simple.Scaffold{
I: info,
T: types.NewType("func(?1, ?1) bool"), T: types.NewType("func(?1, ?1) bool"),
C: simple.TypeMatch([]string{ C: simple.TypeMatch([]string{
"func(int, int) bool", // greater-than-equal "func(int, int) bool", // greater-than-equal
@@ -366,6 +382,7 @@ func init() {
// TODO: is there a way for the engine to have // TODO: is there a way for the engine to have
// short-circuit operators, and does it matter? // short-circuit operators, and does it matter?
RegisterOperator("and", &simple.Scaffold{ RegisterOperator("and", &simple.Scaffold{
I: info,
T: types.NewType("func(bool, bool) bool"), T: types.NewType("func(bool, bool) bool"),
F: func(ctx context.Context, input []types.Value) (types.Value, error) { F: func(ctx context.Context, input []types.Value) (types.Value, error) {
return &types.BoolValue{ return &types.BoolValue{
@@ -376,6 +393,7 @@ func init() {
// logical or // logical or
RegisterOperator("or", &simple.Scaffold{ RegisterOperator("or", &simple.Scaffold{
I: info,
T: types.NewType("func(bool, bool) bool"), T: types.NewType("func(bool, bool) bool"),
F: func(ctx context.Context, input []types.Value) (types.Value, error) { F: func(ctx context.Context, input []types.Value) (types.Value, error) {
return &types.BoolValue{ return &types.BoolValue{
@@ -386,6 +404,7 @@ func init() {
// logical not (unary operator) // logical not (unary operator)
RegisterOperator("not", &simple.Scaffold{ RegisterOperator("not", &simple.Scaffold{
I: info,
T: types.NewType("func(bool) bool"), T: types.NewType("func(bool) bool"),
F: func(ctx context.Context, input []types.Value) (types.Value, error) { F: func(ctx context.Context, input []types.Value) (types.Value, error) {
return &types.BoolValue{ return &types.BoolValue{
@@ -396,6 +415,7 @@ func init() {
// pi operator (this is an easter egg to demo a zero arg operator) // pi operator (this is an easter egg to demo a zero arg operator)
RegisterOperator("π", &simple.Scaffold{ RegisterOperator("π", &simple.Scaffold{
I: info,
T: types.NewType("func() float"), T: types.NewType("func() float"),
F: func(ctx context.Context, input []types.Value) (types.Value, error) { F: func(ctx context.Context, input []types.Value) (types.Value, error) {
return &types.FloatValue{ return &types.FloatValue{
@@ -645,8 +665,11 @@ func (obj *OperatorFunc) Info() *interfaces.Info {
// avoid an accidental return of unification variables when we should be // avoid an accidental return of unification variables when we should be
// getting them from FuncInfer, and not from here. (During unification!) // getting them from FuncInfer, and not from here. (During unification!)
return &interfaces.Info{ return &interfaces.Info{
// XXX: get these from the scaffold
Pure: true, Pure: true,
Memo: false, Memo: true,
Fast: true,
Spec: true,
Sig: obj.Type, // func kind, which includes operator arg as input Sig: obj.Type, // func kind, which includes operator arg as input
Err: obj.Validate(), Err: obj.Validate(),
} }

View File

@@ -54,9 +54,20 @@ const (
// RegisteredFuncs maps a function name to the corresponding function scaffold. // RegisteredFuncs maps a function name to the corresponding function scaffold.
var RegisteredFuncs = make(map[string]*Scaffold) // must initialize 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 // Scaffold holds the necessary data to build a (possibly polymorphic) function
// with this API. // with this API.
type Scaffold struct { 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. // 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 // At a minimum, this must be a `func(?1) ?2` as a naked `?1` is not
// allowed. (TODO: Because of ArgGen.) // 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)) 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 // register a copy in the main function database
funcs.Register(name, func() interfaces.Func { funcs.Register(name, func() interfaces.Func {
return &Func{ return &Func{
Metadata: metadata, Metadata: metadata,
WrappedFunc: &wrapped.Func{ WrappedFunc: &wrapped.Func{
Name: name, Name: name,
FuncInfo: funcInfo,
// NOTE: It might be more correct to Copy here, // NOTE: It might be more correct to Copy here,
// but we do the copy inside of ExprFunc.Copy() // but we do the copy inside of ExprFunc.Copy()
// instead, so that the same type can be unified // instead, so that the same type can be unified

View File

@@ -223,7 +223,7 @@ func (obj *CallFunc) replaceSubGraph(newFuncValue *full.FuncValue) error {
// methods called on it. Nothing else. It will _not_ call Commit or // methods called on it. Nothing else. It will _not_ call Commit or
// Reverse. It adds to the graph, and our Commit and Reverse operations // Reverse. It adds to the graph, and our Commit and Reverse operations
// are the ones that actually make the change. // 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 { if err != nil {
return errwrap.Wrapf(err, "could not call newFuncValue.Call()") return errwrap.Wrapf(err, "could not call newFuncValue.Call()")
} }

View File

@@ -74,6 +74,7 @@ func SimpleFnToFuncValue(name string, fv *types.FuncValue) *full.FuncValue {
} }
return wrappedFunc, nil return wrappedFunc, nil
}, },
F: nil, // unused
T: fv.T, 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 { func SimpleFnToConstFunc(name string, fv *types.FuncValue) interfaces.Func {
return FuncValueToConstFunc(SimpleFnToFuncValue(name, fv)) 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,
}
}

View File

@@ -39,6 +39,14 @@ import (
var _ interfaces.Func = &Func{} // ensure it meets this expectation 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 // 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. // 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. // 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 is a unique string name for the function.
Name string Name string
// Info is some general info about the function.
FuncInfo *Info
// Type is the type of the function. It can include unification // Type is the type of the function. It can include unification
// variables when this struct is wrapped in one that can build this out. // variables when this struct is wrapped in one that can build this out.
Type *types.Type Type *types.Type
@@ -64,7 +75,16 @@ type Func struct {
// String returns a simple name for this function. This is needed so this struct // String returns a simple name for this function. This is needed so this struct
// can satisfy the pgraph.Vertex interface. // can satisfy the pgraph.Vertex interface.
func (obj *Func) String() string { 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 "<wrapped>"
}
return obj.Name
} }
// ArgGen returns the Nth arg name for this function. // ArgGen returns the Nth arg name for this function.
@@ -104,12 +124,21 @@ func (obj *Func) Info() *interfaces.Info {
typ = obj.Fn.Type() typ = obj.Fn.Type()
} }
return &interfaces.Info{ info := &interfaces.Info{
Pure: true, Pure: false,
Memo: false, // TODO: should this be something we specify here? Memo: false,
Fast: false,
Spec: false,
Sig: typ, Sig: typ,
Err: obj.Validate(), 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. // 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 // Call this function with the input args and return the value if it is possible
// to do so at this time. // to do so at this time.
func (obj *Func) Call(ctx context.Context, args []types.Value) (types.Value, error) { 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) return obj.Fn.Call(ctx, args) // (Value, error)
} }

Some files were not shown because too many files have changed in this diff Show More