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:
@@ -33,6 +33,7 @@ package ast
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
@@ -45,7 +46,9 @@ import (
|
||||
"github.com/purpleidea/mgmt/lang/core"
|
||||
"github.com/purpleidea/mgmt/lang/embedded"
|
||||
"github.com/purpleidea/mgmt/lang/funcs"
|
||||
"github.com/purpleidea/mgmt/lang/funcs/ref"
|
||||
"github.com/purpleidea/mgmt/lang/funcs/structs"
|
||||
"github.com/purpleidea/mgmt/lang/funcs/txn"
|
||||
"github.com/purpleidea/mgmt/lang/inputs"
|
||||
"github.com/purpleidea/mgmt/lang/interfaces"
|
||||
"github.com/purpleidea/mgmt/lang/types"
|
||||
@@ -9474,7 +9477,6 @@ func (obj *ExprFunc) Copy() (interfaces.Expr, error) {
|
||||
|
||||
// Ordering returns a graph of the scope ordering that represents the data flow.
|
||||
// This can be used in SetScope so that it knows the correct order to run it in.
|
||||
// XXX: do we need to add ordering around named args, eg: obj.Args Name strings?
|
||||
func (obj *ExprFunc) Ordering(produces map[string]interfaces.Node) (*pgraph.Graph, map[interfaces.Node]string, error) {
|
||||
graph, err := pgraph.NewGraph("ordering")
|
||||
if err != nil {
|
||||
@@ -9502,7 +9504,6 @@ func (obj *ExprFunc) Ordering(produces map[string]interfaces.Node) (*pgraph.Grap
|
||||
|
||||
cons := make(map[interfaces.Node]string)
|
||||
|
||||
// XXX: do we need ordering for other aspects of ExprFunc ?
|
||||
if obj.Body != nil {
|
||||
g, c, err := obj.Body.Ordering(newProduces)
|
||||
if err != nil {
|
||||
@@ -9851,6 +9852,11 @@ func (obj *ExprFunc) Graph(env *interfaces.Env) (*pgraph.Graph, interfaces.Func,
|
||||
|
||||
var funcValueFunc interfaces.Func
|
||||
if obj.Body != nil {
|
||||
f := func(ctx context.Context, args []types.Value) (types.Value, error) {
|
||||
// XXX: Find a way to exercise this function if possible.
|
||||
//return nil, funcs.ErrCantSpeculate
|
||||
return nil, fmt.Errorf("not implemented")
|
||||
}
|
||||
funcValueFunc = structs.FuncValueToConstFunc(&full.FuncValue{
|
||||
V: func(innerTxn interfaces.Txn, args []interfaces.Func) (interfaces.Func, error) {
|
||||
// Extend the environment with the arguments.
|
||||
@@ -9888,13 +9894,27 @@ func (obj *ExprFunc) Graph(env *interfaces.Env) (*pgraph.Graph, interfaces.Func,
|
||||
|
||||
return bodyFunc, nil
|
||||
},
|
||||
F: f,
|
||||
T: obj.typ,
|
||||
})
|
||||
} else if obj.Function != nil {
|
||||
// Build this "callable" version in case it's available and we
|
||||
// can use that directly. We don't need to copy it because we
|
||||
// expect anything that is Callable to be stateless, and so it
|
||||
// can use the same function call for every instantiation of it.
|
||||
var fn interfaces.FuncSig
|
||||
callableFunc, ok := obj.function.(interfaces.CallableFunc)
|
||||
if ok {
|
||||
// XXX: this might be dead code, how do we exercise it?
|
||||
// If the function is callable then the surrounding
|
||||
// ExprCall will produce a graph containing this func
|
||||
// instead of calling ExprFunc.Graph().
|
||||
fn = callableFunc.Call
|
||||
}
|
||||
|
||||
// obj.function is a node which transforms input values into
|
||||
// an output value, but we need to construct a node which takes no
|
||||
// inputs and produces a FuncValue, so we need to wrap it.
|
||||
|
||||
funcValueFunc = structs.FuncValueToConstFunc(&full.FuncValue{
|
||||
V: func(txn interfaces.Txn, args []interfaces.Func) (interfaces.Func, error) {
|
||||
// Copy obj.function so that the underlying ExprFunc.function gets
|
||||
@@ -9919,6 +9939,7 @@ func (obj *ExprFunc) Graph(env *interfaces.Env) (*pgraph.Graph, interfaces.Func,
|
||||
}
|
||||
return valueTransformingFunc, nil
|
||||
},
|
||||
F: fn,
|
||||
T: obj.typ,
|
||||
})
|
||||
} else /* len(obj.Values) > 0 */ {
|
||||
@@ -9967,13 +9988,122 @@ func (obj *ExprFunc) SetValue(value types.Value) error {
|
||||
// This particular value is always known since it is a constant.
|
||||
func (obj *ExprFunc) Value() (types.Value, error) {
|
||||
// Don't panic because we call Value speculatively for partial values!
|
||||
// XXX: Not implemented
|
||||
return nil, fmt.Errorf("error: ExprFunc does not store its latest value because resources don't yet have function fields")
|
||||
//// TODO: implement speculative value lookup (if not already sufficient)
|
||||
//return &full.FuncValue{
|
||||
// V: obj.V,
|
||||
// T: obj.typ,
|
||||
//}, nil
|
||||
//return nil, fmt.Errorf("error: ExprFunc does not store its latest value because resources don't yet have function fields")
|
||||
|
||||
if obj.Body != nil {
|
||||
// We can only return a Value if we know the value of all the
|
||||
// ExprParams. We don't have an environment, so this is only
|
||||
// possible if there are no ExprParams at all.
|
||||
// XXX: If we add in EnvValue as an arg, can we change this up?
|
||||
if err := checkParamScope(obj, make(map[interfaces.Expr]struct{})); err != nil {
|
||||
// return the sentinel value
|
||||
return nil, funcs.ErrCantSpeculate
|
||||
}
|
||||
|
||||
f := func(ctx context.Context, args []types.Value) (types.Value, error) {
|
||||
// TODO: make TestAstFunc1/shape8.txtar better...
|
||||
//extendedValueEnv := interfaces.EmptyValueEnv() // TODO: add me?
|
||||
//for _, x := range obj.Args {
|
||||
// extendedValueEnv[???] = ???
|
||||
//}
|
||||
|
||||
// XXX: Find a way to exercise this function if possible.
|
||||
// chained-returned-funcs.txtar will error if we use:
|
||||
//return nil, fmt.Errorf("not implemented")
|
||||
return nil, funcs.ErrCantSpeculate
|
||||
}
|
||||
|
||||
return &full.FuncValue{
|
||||
V: func(innerTxn interfaces.Txn, args []interfaces.Func) (interfaces.Func, error) {
|
||||
// There are no ExprParams, so we start with the empty environment.
|
||||
// Extend that environment with the arguments.
|
||||
extendedEnv := interfaces.EmptyEnv()
|
||||
//extendedEnv := make(map[string]interfaces.Func)
|
||||
for i := range obj.Args {
|
||||
if args[i] == nil {
|
||||
// XXX: speculation error?
|
||||
return nil, fmt.Errorf("programming error?")
|
||||
}
|
||||
if len(obj.params) <= i {
|
||||
// XXX: speculation error?
|
||||
return nil, fmt.Errorf("programming error?")
|
||||
}
|
||||
param := obj.params[i]
|
||||
if param == nil || param.envKey == nil {
|
||||
// XXX: speculation error?
|
||||
return nil, fmt.Errorf("programming error?")
|
||||
}
|
||||
|
||||
extendedEnv.Variables[param.envKey] = &interfaces.FuncSingleton{
|
||||
MakeFunc: func() (*pgraph.Graph, interfaces.Func, error) {
|
||||
f := args[i]
|
||||
g, err := pgraph.NewGraph("g")
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
g.AddVertex(f)
|
||||
return g, f, nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Create a subgraph from the lambda's body, instantiating the
|
||||
// lambda's parameters with the args and the other variables
|
||||
// with the nodes in the captured environment.
|
||||
subgraph, bodyFunc, err := obj.Body.Graph(extendedEnv)
|
||||
if err != nil {
|
||||
return nil, errwrap.Wrapf(err, "could not create the lambda body's subgraph")
|
||||
}
|
||||
|
||||
innerTxn.AddGraph(subgraph)
|
||||
|
||||
return bodyFunc, nil
|
||||
},
|
||||
F: f,
|
||||
T: obj.typ,
|
||||
}, nil
|
||||
|
||||
} else if obj.Function != nil {
|
||||
copyFunc := func() interfaces.Func {
|
||||
copyableFunc, isCopyableFunc := obj.function.(interfaces.CopyableFunc)
|
||||
if obj.function == nil || !isCopyableFunc {
|
||||
return obj.Function() // force re-build a new pointer here!
|
||||
}
|
||||
|
||||
// is copyable!
|
||||
return copyableFunc.Copy()
|
||||
}
|
||||
|
||||
// Instead of passing in the obj.function, we instead pass in a
|
||||
// builder function so that this can use that inside of the
|
||||
// *full.FuncValue implementation to make new functions when it
|
||||
// gets called. We'll need more than one so they're not the same
|
||||
// pointer!
|
||||
return structs.FuncToFullFuncValue(copyFunc, obj.typ), nil
|
||||
}
|
||||
// else if /* len(obj.Values) > 0 */
|
||||
|
||||
// XXX: It's unclear if the below code in this function is correct or
|
||||
// even tested.
|
||||
|
||||
// polymorphic case: figure out which one has the correct type and wrap
|
||||
// it in a full.FuncValue.
|
||||
|
||||
index, err := langUtil.FnMatch(obj.typ, obj.Values)
|
||||
if err != nil {
|
||||
// programming error
|
||||
// since type checking succeeded at this point, there should only be one match
|
||||
return nil, errwrap.Wrapf(err, "multiple matches found")
|
||||
}
|
||||
|
||||
simpleFn := obj.Values[index] // *types.FuncValue
|
||||
simpleFn.T = obj.typ // ensure the precise type is set/known
|
||||
|
||||
return &full.FuncValue{
|
||||
V: nil, // XXX: do we need to implement this too?
|
||||
F: simpleFn.V, // XXX: is this correct?
|
||||
T: obj.typ,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ExprCall is a representation of a function call. This does not represent the
|
||||
@@ -10737,13 +10867,6 @@ func (obj *ExprCall) Graph(env *interfaces.Env) (*pgraph.Graph, interfaces.Func,
|
||||
return nil, nil, errwrap.Wrapf(err, "could not get the type of the function")
|
||||
}
|
||||
|
||||
// Find the vertex which produces the FuncValue.
|
||||
g, funcValueFunc, err := obj.funcValueFunc(env)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
graph.AddGraph(g)
|
||||
|
||||
// Loop over the arguments, add them to the graph, but do _not_ connect them
|
||||
// to the function vertex. Instead, each time the call vertex (which we
|
||||
// create below) receives a FuncValue from the function node, it creates the
|
||||
@@ -10758,6 +10881,50 @@ func (obj *ExprCall) Graph(env *interfaces.Env) (*pgraph.Graph, interfaces.Func,
|
||||
argFuncs = append(argFuncs, argFunc)
|
||||
}
|
||||
|
||||
// Speculate early, in an attempt to get a simpler graph shape.
|
||||
//exprFunc, ok := obj.expr.(*ExprFunc)
|
||||
// XXX: Does this need to be .Pure for it to be allowed?
|
||||
//canSpeculate := !ok || exprFunc.function == nil || (exprFunc.function.Info().Fast && exprFunc.function.Info().Spec)
|
||||
canSpeculate := true // XXX: use the magic Info fields?
|
||||
exprValue, err := obj.expr.Value()
|
||||
exprFuncValue, ok := exprValue.(*full.FuncValue)
|
||||
if err == nil && ok && canSpeculate {
|
||||
txn := (&txn.GraphTxn{
|
||||
GraphAPI: (&txn.Graph{
|
||||
Debug: obj.data.Debug,
|
||||
Logf: func(format string, v ...interface{}) {
|
||||
obj.data.Logf(format, v...)
|
||||
},
|
||||
}).Init(),
|
||||
Lock: func() {},
|
||||
Unlock: func() {},
|
||||
RefCount: (&ref.Count{}).Init(),
|
||||
}).Init()
|
||||
txn.AddGraph(graph) // add all of the graphs so far...
|
||||
|
||||
outputFunc, err := exprFuncValue.CallWithFuncs(txn, argFuncs)
|
||||
if err != nil {
|
||||
return nil, nil, errwrap.Wrapf(err, "could not construct the static graph for a function call")
|
||||
}
|
||||
txn.AddVertex(outputFunc)
|
||||
|
||||
if err := txn.Commit(); err != nil { // Must Commit after txn.AddGraph(...)
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return txn.Graph(), outputFunc, nil
|
||||
} else if err != nil && ok && canSpeculate && err != funcs.ErrCantSpeculate {
|
||||
// This is a permanent error, not a temporary speculation error.
|
||||
//return nil, nil, err // XXX: Consider adding this...
|
||||
}
|
||||
|
||||
// Find the vertex which produces the FuncValue.
|
||||
g, funcValueFunc, err := obj.funcValueFunc(env)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
graph.AddGraph(g)
|
||||
|
||||
// Add a vertex for the call itself.
|
||||
edgeName := structs.CallFuncArgNameFunction
|
||||
callFunc := &structs.CallFunc{
|
||||
@@ -10867,7 +11034,7 @@ func (obj *ExprCall) SetValue(value types.Value) error {
|
||||
if err := obj.typ.Cmp(value.Type()); err != nil {
|
||||
return err
|
||||
}
|
||||
obj.V = value
|
||||
obj.V = value // XXX: is this useful or a good idea?
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -10875,13 +11042,46 @@ func (obj *ExprCall) SetValue(value types.Value) error {
|
||||
// usually only be valid once the engine has run and values have been produced.
|
||||
// This might get called speculatively (early) during unification to learn more.
|
||||
// It is often unlikely that this kind of speculative execution finds something.
|
||||
// This particular implementation of the function returns the previously stored
|
||||
// and cached value as received by SetValue.
|
||||
// This particular implementation will run a function if all of the needed
|
||||
// values are known. This is necessary for getting the efficient graph shape of
|
||||
// ExprCall.
|
||||
func (obj *ExprCall) Value() (types.Value, error) {
|
||||
if obj.V == nil {
|
||||
if obj.V != nil { // XXX: is this useful or a good idea?
|
||||
return obj.V, nil
|
||||
}
|
||||
|
||||
if obj.expr == nil {
|
||||
return nil, fmt.Errorf("func value does not yet exist")
|
||||
}
|
||||
return obj.V, nil
|
||||
|
||||
// Speculatively call Value() on obj.expr and each arg.
|
||||
// XXX: Should we check obj.expr.(*ExprFunc).Info.Pure here ?
|
||||
value, err := obj.expr.Value() // speculative
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
funcValue, ok := value.(*full.FuncValue)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("not a func value")
|
||||
}
|
||||
|
||||
args := []types.Value{}
|
||||
for _, arg := range obj.Args { // []interfaces.Expr
|
||||
a, err := arg.Value() // speculative
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
args = append(args, a)
|
||||
}
|
||||
|
||||
// We now have a *full.FuncValue and a []types.Value. We can't call the
|
||||
// existing:
|
||||
// Call(..., []interfaces.Func) interfaces.Func` method on the
|
||||
// FuncValue, we need a speculative:
|
||||
// Call(..., []types.Value) types.Value
|
||||
// method.
|
||||
return funcValue.CallWithValues(context.TODO(), args)
|
||||
}
|
||||
|
||||
// ExprVar is a representation of a variable lookup. It returns the expression
|
||||
@@ -11370,6 +11570,11 @@ func (obj *ExprParam) SetValue(value types.Value) error {
|
||||
// usually only be valid once the engine has run and values have been produced.
|
||||
// This might get called speculatively (early) during unification to learn more.
|
||||
func (obj *ExprParam) Value() (types.Value, error) {
|
||||
// XXX: if value env is an arg in Expr.Value(...)
|
||||
//value, exists := valueEnv[obj]
|
||||
//if exists {
|
||||
// return value, nil
|
||||
//}
|
||||
return nil, fmt.Errorf("no value for ExprParam")
|
||||
}
|
||||
|
||||
|
||||
123
lang/ast/util.go
123
lang/ast/util.go
@@ -321,6 +321,11 @@ func getScope(node interfaces.Expr) (*interfaces.Scope, error) {
|
||||
return expr.scope, nil
|
||||
case *ExprVar:
|
||||
return expr.scope, nil
|
||||
//case *ExprParam:
|
||||
//case *ExprIterated:
|
||||
//case *ExprPoly:
|
||||
//case *ExprTopLevel:
|
||||
//case *ExprSingleton:
|
||||
case *ExprIf:
|
||||
return expr.scope, nil
|
||||
|
||||
@@ -329,6 +334,124 @@ func getScope(node interfaces.Expr) (*interfaces.Scope, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// CheckParamScope ensures that only the specified ExprParams are free in the
|
||||
// expression. It is used for graph shape function speculation. This could have
|
||||
// been an addition to the interfaces.Expr interface, but since it's mostly
|
||||
// iteration, it felt cleaner like this.
|
||||
// TODO: Can we replace this with a call to Apply instead.
|
||||
func checkParamScope(node interfaces.Expr, freeVars map[interfaces.Expr]struct{}) error {
|
||||
switch obj := node.(type) {
|
||||
|
||||
case *ExprBool:
|
||||
return nil
|
||||
|
||||
case *ExprStr:
|
||||
return nil
|
||||
|
||||
case *ExprInt:
|
||||
return nil
|
||||
|
||||
case *ExprFloat:
|
||||
return nil
|
||||
|
||||
case *ExprList:
|
||||
for _, x := range obj.Elements {
|
||||
if err := checkParamScope(x, freeVars); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
case *ExprMap:
|
||||
for _, x := range obj.KVs {
|
||||
if err := checkParamScope(x.Key, freeVars); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := checkParamScope(x.Val, freeVars); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
case *ExprStruct:
|
||||
for _, x := range obj.Fields {
|
||||
if err := checkParamScope(x.Value, freeVars); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
case *ExprFunc:
|
||||
if obj.Body != nil {
|
||||
newFreeVars := make(map[interfaces.Expr]struct{})
|
||||
for k, v := range freeVars {
|
||||
newFreeVars[k] = v
|
||||
}
|
||||
for _, param := range obj.params {
|
||||
newFreeVars[param] = struct{}{}
|
||||
}
|
||||
|
||||
if err := checkParamScope(obj.Body, newFreeVars); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// XXX: Do we need to do anything for obj.Function ?
|
||||
// XXX: Do we need to do anything for obj.Values ?
|
||||
return nil
|
||||
|
||||
case *ExprCall:
|
||||
if obj.expr != nil {
|
||||
if err := checkParamScope(obj.expr, freeVars); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, x := range obj.Args {
|
||||
if err := checkParamScope(x, freeVars); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
case *ExprVar:
|
||||
// XXX: is this still correct?
|
||||
target := obj.scope.Variables[obj.Name]
|
||||
return checkParamScope(target, freeVars)
|
||||
|
||||
case *ExprParam:
|
||||
if _, exists := freeVars[obj]; !exists {
|
||||
return fmt.Errorf("the body uses parameter $%s", obj.Name)
|
||||
}
|
||||
return nil
|
||||
|
||||
case *ExprIterated:
|
||||
return checkParamScope(obj.Definition, freeVars) // XXX: is this what we want?
|
||||
|
||||
case *ExprPoly:
|
||||
panic("checkParamScope(ExprPoly): should not happen, ExprVar should replace ExprPoly with a copy of its definition before calling checkParamScope")
|
||||
|
||||
case *ExprTopLevel:
|
||||
return checkParamScope(obj.Definition, freeVars)
|
||||
|
||||
case *ExprSingleton:
|
||||
return checkParamScope(obj.Definition, freeVars)
|
||||
|
||||
case *ExprIf:
|
||||
if err := checkParamScope(obj.Condition, freeVars); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := checkParamScope(obj.ThenBranch, freeVars); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := checkParamScope(obj.ElseBranch, freeVars); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
||||
default:
|
||||
return fmt.Errorf("unexpected: %+v", node)
|
||||
}
|
||||
}
|
||||
|
||||
// trueCallee is a helper function because ExprTopLevel and ExprSingleton are
|
||||
// sometimes added around builtins. This makes it difficult for the type checker
|
||||
// to check if a particular builtin is the callee or not. This function removes
|
||||
|
||||
@@ -282,6 +282,8 @@ func (obj *CollectFunc) Info() *interfaces.Info {
|
||||
return &interfaces.Info{
|
||||
Pure: false,
|
||||
Memo: false,
|
||||
Fast: false,
|
||||
Spec: false,
|
||||
Sig: sig,
|
||||
Err: obj.Validate(),
|
||||
}
|
||||
@@ -389,6 +391,9 @@ func (obj *CollectFunc) Stream(ctx context.Context) error {
|
||||
// to do so at this time. This was previously getValue which gets the value
|
||||
// we're looking for.
|
||||
func (obj *CollectFunc) Call(ctx context.Context, args []types.Value) (types.Value, error) {
|
||||
if len(args) < 2 {
|
||||
return nil, fmt.Errorf("not enough args")
|
||||
}
|
||||
kind := args[0].Str()
|
||||
if kind == "" {
|
||||
return nil, fmt.Errorf("resource kind is empty")
|
||||
@@ -448,6 +453,9 @@ func (obj *CollectFunc) Call(ctx context.Context, args []types.Value) (types.Val
|
||||
}
|
||||
}
|
||||
|
||||
if obj.init == nil {
|
||||
return nil, funcs.ErrCantSpeculate
|
||||
}
|
||||
resOutput, err := obj.init.World.ResCollect(ctx, filters)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -44,6 +44,12 @@ const (
|
||||
|
||||
func init() {
|
||||
simple.Register(ConcatFuncName, &simple.Scaffold{
|
||||
I: &simple.Info{
|
||||
Pure: true,
|
||||
Memo: true,
|
||||
Fast: true,
|
||||
Spec: true,
|
||||
},
|
||||
T: types.NewType("func(a str, b str) str"),
|
||||
F: Concat,
|
||||
})
|
||||
|
||||
@@ -44,6 +44,12 @@ const (
|
||||
|
||||
func init() {
|
||||
simple.Register(ContainsFuncName, &simple.Scaffold{
|
||||
I: &simple.Info{
|
||||
Pure: true,
|
||||
Memo: true,
|
||||
Fast: true,
|
||||
Spec: true,
|
||||
},
|
||||
T: types.NewType("func(needle ?1, haystack []?1) bool"),
|
||||
F: Contains,
|
||||
})
|
||||
|
||||
@@ -39,6 +39,12 @@ import (
|
||||
|
||||
func init() {
|
||||
simple.ModuleRegister(ModuleName, "format_bool", &simple.Scaffold{
|
||||
I: &simple.Info{
|
||||
Pure: true,
|
||||
Memo: true,
|
||||
Fast: true,
|
||||
Spec: true,
|
||||
},
|
||||
T: types.NewType("func(a bool) str"),
|
||||
F: FormatBool,
|
||||
})
|
||||
|
||||
@@ -40,6 +40,12 @@ import (
|
||||
|
||||
func init() {
|
||||
simple.ModuleRegister(ModuleName, "parse_bool", &simple.Scaffold{
|
||||
I: &simple.Info{
|
||||
Pure: true,
|
||||
Memo: true,
|
||||
Fast: true,
|
||||
Spec: true,
|
||||
},
|
||||
T: types.NewType("func(a str) bool"),
|
||||
F: ParseBool,
|
||||
})
|
||||
|
||||
@@ -38,6 +38,12 @@ import (
|
||||
|
||||
func init() {
|
||||
simple.ModuleRegister(ModuleName, "to_float", &simple.Scaffold{
|
||||
I: &simple.Info{
|
||||
Pure: true,
|
||||
Memo: true,
|
||||
Fast: true,
|
||||
Spec: true,
|
||||
},
|
||||
T: types.NewType("func(a int) float"),
|
||||
F: ToFloat,
|
||||
})
|
||||
|
||||
@@ -38,6 +38,12 @@ import (
|
||||
|
||||
func init() {
|
||||
simple.ModuleRegister(ModuleName, "to_int", &simple.Scaffold{
|
||||
I: &simple.Info{
|
||||
Pure: true,
|
||||
Memo: true,
|
||||
Fast: true,
|
||||
Spec: true,
|
||||
},
|
||||
T: types.NewType("func(a float) int"),
|
||||
F: ToInt,
|
||||
})
|
||||
|
||||
@@ -39,6 +39,12 @@ import (
|
||||
|
||||
func init() {
|
||||
simple.ModuleRegister(ModuleName, "int_to_str", &simple.Scaffold{
|
||||
I: &simple.Info{
|
||||
Pure: true,
|
||||
Memo: true,
|
||||
Fast: true,
|
||||
Spec: true,
|
||||
},
|
||||
T: types.NewType("func(a int) str"),
|
||||
F: IntToStr,
|
||||
})
|
||||
|
||||
@@ -62,8 +62,8 @@ func PureFuncExec(handle interfaces.Func, args []types.Value) (types.Value, erro
|
||||
return nil, fmt.Errorf("func is not pure")
|
||||
}
|
||||
// if function is expensive to run, we won't run it provisionally
|
||||
if info.Slow {
|
||||
return nil, fmt.Errorf("func is slow")
|
||||
if !info.Fast {
|
||||
return nil, fmt.Errorf("func is not fast")
|
||||
}
|
||||
|
||||
sig := handle.Info().Sig
|
||||
|
||||
@@ -40,6 +40,12 @@ import (
|
||||
|
||||
func init() {
|
||||
simple.ModuleRegister(ModuleName, "format", &simple.Scaffold{
|
||||
I: &simple.Info{
|
||||
Pure: true,
|
||||
Memo: true,
|
||||
Fast: true,
|
||||
Spec: true,
|
||||
},
|
||||
T: types.NewType("func(a int, b str) str"),
|
||||
F: Format,
|
||||
})
|
||||
|
||||
@@ -40,6 +40,12 @@ import (
|
||||
|
||||
func init() {
|
||||
simple.ModuleRegister(ModuleName, "hour", &simple.Scaffold{
|
||||
I: &simple.Info{
|
||||
Pure: true,
|
||||
Memo: true,
|
||||
Fast: true,
|
||||
Spec: true,
|
||||
},
|
||||
T: types.NewType("func(a int) int"),
|
||||
F: Hour,
|
||||
})
|
||||
|
||||
@@ -67,6 +67,8 @@ func (obj *DateTimeFact) String() string {
|
||||
// Info returns some static info about itself.
|
||||
func (obj *DateTimeFact) Info() *facts.Info {
|
||||
return &facts.Info{
|
||||
Pure: false,
|
||||
Memo: false,
|
||||
Output: types.NewType("int"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,6 +41,12 @@ import (
|
||||
func init() {
|
||||
// FIXME: consider renaming this to printf, and add in a format string?
|
||||
simple.ModuleRegister(ModuleName, "print", &simple.Scaffold{
|
||||
I: &simple.Info{
|
||||
Pure: true,
|
||||
Memo: true,
|
||||
Fast: true,
|
||||
Spec: true,
|
||||
},
|
||||
T: types.NewType("func(a int) str"),
|
||||
F: Print,
|
||||
})
|
||||
|
||||
@@ -41,6 +41,12 @@ import (
|
||||
|
||||
func init() {
|
||||
simple.ModuleRegister(ModuleName, "weekday", &simple.Scaffold{
|
||||
I: &simple.Info{
|
||||
Pure: true,
|
||||
Memo: true,
|
||||
Fast: true,
|
||||
Spec: true,
|
||||
},
|
||||
T: types.NewType("func(a int) str"),
|
||||
F: Weekday,
|
||||
})
|
||||
|
||||
@@ -51,6 +51,8 @@ func init() {
|
||||
funcs.ModuleRegister(ModuleName, AbsPathFuncName, func() interfaces.Func { return &AbsPathFunc{} }) // must register the func and name
|
||||
}
|
||||
|
||||
var _ interfaces.DataFunc = &AbsPathFunc{}
|
||||
|
||||
// AbsPathFunc is a function that returns the absolute, full path in the deploy
|
||||
// from an input path that is relative to the calling file. If you pass it an
|
||||
// empty string, you'll just get the absolute deploy directory path that you're
|
||||
@@ -96,6 +98,8 @@ func (obj *AbsPathFunc) Info() *interfaces.Info {
|
||||
return &interfaces.Info{
|
||||
Pure: false, // maybe false because the file contents can change
|
||||
Memo: false,
|
||||
Fast: false,
|
||||
Spec: false,
|
||||
Sig: types.NewType(fmt.Sprintf("func(%s str) str", absPathArgNamePath)),
|
||||
}
|
||||
}
|
||||
@@ -166,11 +170,26 @@ func (obj *AbsPathFunc) Stream(ctx context.Context) error {
|
||||
}
|
||||
}
|
||||
|
||||
// Copy is implemented so that the obj.built value is not lost if we copy this
|
||||
// function.
|
||||
func (obj *AbsPathFunc) Copy() interfaces.Func {
|
||||
return &AbsPathFunc{
|
||||
init: obj.init, // likely gets overwritten anyways
|
||||
data: obj.data, // needed because we don't call SetData twice
|
||||
}
|
||||
}
|
||||
|
||||
// Call this function with the input args and return the value if it is possible
|
||||
// to do so at this time.
|
||||
func (obj *AbsPathFunc) Call(ctx context.Context, args []types.Value) (types.Value, error) {
|
||||
if len(args) < 1 {
|
||||
return nil, fmt.Errorf("not enough args")
|
||||
}
|
||||
path := args[0].Str()
|
||||
|
||||
if obj.data == nil {
|
||||
return nil, funcs.ErrCantSpeculate
|
||||
}
|
||||
p := strings.TrimSuffix(obj.data.Base, "/")
|
||||
if p == obj.data.Base { // didn't trim, so we fail
|
||||
// programming error
|
||||
|
||||
@@ -39,6 +39,12 @@ import (
|
||||
|
||||
func init() {
|
||||
simple.ModuleRegister(ModuleName, "binary_path", &simple.Scaffold{
|
||||
I: &simple.Info{
|
||||
Pure: true,
|
||||
Memo: false,
|
||||
Fast: true,
|
||||
Spec: false,
|
||||
},
|
||||
T: types.NewType("func() str"),
|
||||
F: BinaryPath,
|
||||
})
|
||||
|
||||
@@ -40,6 +40,12 @@ import (
|
||||
|
||||
func init() {
|
||||
simple.ModuleRegister(ModuleName, "bootstrap_packages", &simple.Scaffold{
|
||||
I: &simple.Info{
|
||||
Pure: true,
|
||||
Memo: true,
|
||||
Fast: true,
|
||||
Spec: true,
|
||||
},
|
||||
T: types.NewType("func(str) []str"),
|
||||
F: BootstrapPackages,
|
||||
})
|
||||
|
||||
@@ -52,6 +52,8 @@ func init() {
|
||||
funcs.ModuleRegister(ModuleName, ReadFileFuncName, func() interfaces.Func { return &ReadFileFunc{} }) // must register the func and name
|
||||
}
|
||||
|
||||
var _ interfaces.DataFunc = &ReadFileFunc{}
|
||||
|
||||
// ReadFileFunc is a function that reads the full contents from a file in our
|
||||
// deploy. The file contents can only change with a new deploy, so this is
|
||||
// static. Please note that this is different from the readfile function in the
|
||||
@@ -97,6 +99,8 @@ func (obj *ReadFileFunc) Info() *interfaces.Info {
|
||||
return &interfaces.Info{
|
||||
Pure: false, // maybe false because the file contents can change
|
||||
Memo: false,
|
||||
Fast: false,
|
||||
Spec: false,
|
||||
Sig: types.NewType(fmt.Sprintf("func(%s str) str", readFileArgNameFilename)),
|
||||
}
|
||||
}
|
||||
@@ -168,11 +172,26 @@ func (obj *ReadFileFunc) Stream(ctx context.Context) error {
|
||||
}
|
||||
}
|
||||
|
||||
// Copy is implemented so that the obj.built value is not lost if we copy this
|
||||
// function.
|
||||
func (obj *ReadFileFunc) Copy() interfaces.Func {
|
||||
return &ReadFileFunc{
|
||||
init: obj.init, // likely gets overwritten anyways
|
||||
data: obj.data, // needed because we don't call SetData twice
|
||||
}
|
||||
}
|
||||
|
||||
// Call this function with the input args and return the value if it is possible
|
||||
// to do so at this time.
|
||||
func (obj *ReadFileFunc) Call(ctx context.Context, args []types.Value) (types.Value, error) {
|
||||
if len(args) < 1 {
|
||||
return nil, fmt.Errorf("not enough args")
|
||||
}
|
||||
filename := args[0].Str()
|
||||
|
||||
if obj.data == nil {
|
||||
return nil, funcs.ErrCantSpeculate
|
||||
}
|
||||
p := strings.TrimSuffix(obj.data.Base, "/")
|
||||
if p == obj.data.Base { // didn't trim, so we fail
|
||||
// programming error
|
||||
@@ -186,6 +205,9 @@ func (obj *ReadFileFunc) Call(ctx context.Context, args []types.Value) (types.Va
|
||||
}
|
||||
path += filename
|
||||
|
||||
if obj.init == nil || obj.data == nil {
|
||||
return nil, funcs.ErrCantSpeculate
|
||||
}
|
||||
fs, err := obj.init.World.Fs(obj.data.FsURI) // open the remote file system
|
||||
if err != nil {
|
||||
return nil, errwrap.Wrapf(err, "can't load code from file system `%s`", obj.data.FsURI)
|
||||
|
||||
@@ -51,6 +51,8 @@ func init() {
|
||||
funcs.ModuleRegister(ModuleName, ReadFileAbsFuncName, func() interfaces.Func { return &ReadFileAbsFunc{} }) // must register the func and name
|
||||
}
|
||||
|
||||
var _ interfaces.DataFunc = &ReadFileAbsFunc{}
|
||||
|
||||
// ReadFileAbsFunc is a function that reads the full contents from a file in our
|
||||
// deploy. The file contents can only change with a new deploy, so this is
|
||||
// static. In particular, this takes an absolute path relative to the root
|
||||
@@ -97,6 +99,8 @@ func (obj *ReadFileAbsFunc) Info() *interfaces.Info {
|
||||
return &interfaces.Info{
|
||||
Pure: false, // maybe false because the file contents can change
|
||||
Memo: false,
|
||||
Fast: false,
|
||||
Spec: false,
|
||||
Sig: types.NewType(fmt.Sprintf("func(%s str) str", readfileArgNameFilename)),
|
||||
}
|
||||
}
|
||||
@@ -168,11 +172,26 @@ func (obj *ReadFileAbsFunc) Stream(ctx context.Context) error {
|
||||
}
|
||||
}
|
||||
|
||||
// Copy is implemented so that the obj.built value is not lost if we copy this
|
||||
// function.
|
||||
func (obj *ReadFileAbsFunc) Copy() interfaces.Func {
|
||||
return &ReadFileAbsFunc{
|
||||
init: obj.init, // likely gets overwritten anyways
|
||||
data: obj.data, // needed because we don't call SetData twice
|
||||
}
|
||||
}
|
||||
|
||||
// Call this function with the input args and return the value if it is possible
|
||||
// to do so at this time.
|
||||
func (obj *ReadFileAbsFunc) Call(ctx context.Context, args []types.Value) (types.Value, error) {
|
||||
if len(args) < 1 {
|
||||
return nil, fmt.Errorf("not enough args")
|
||||
}
|
||||
filename := args[0].Str()
|
||||
|
||||
if obj.init == nil || obj.data == nil {
|
||||
return nil, funcs.ErrCantSpeculate
|
||||
}
|
||||
fs, err := obj.init.World.Fs(obj.data.FsURI) // open the remote file system
|
||||
if err != nil {
|
||||
return nil, errwrap.Wrapf(err, "can't load code from file system `%s`", obj.data.FsURI)
|
||||
|
||||
@@ -524,6 +524,12 @@ func (obj *provisioner) Register(moduleName string) error {
|
||||
|
||||
// Build a few separately...
|
||||
simple.ModuleRegister(moduleName, "cli_password", &simple.Scaffold{
|
||||
I: &simple.Info{
|
||||
Pure: false,
|
||||
Memo: false,
|
||||
Fast: false,
|
||||
Spec: false,
|
||||
},
|
||||
T: types.NewType("func() str"),
|
||||
F: func(ctx context.Context, input []types.Value) (types.Value, error) {
|
||||
if obj.localArgs == nil {
|
||||
|
||||
@@ -41,6 +41,12 @@ const Answer = 42
|
||||
|
||||
func init() {
|
||||
simple.ModuleRegister(ModuleName, "answer", &simple.Scaffold{
|
||||
I: &simple.Info{
|
||||
Pure: true,
|
||||
Memo: true,
|
||||
Fast: true,
|
||||
Spec: true,
|
||||
},
|
||||
T: types.NewType("func() int"),
|
||||
F: TheAnswerToLifeTheUniverseAndEverything,
|
||||
})
|
||||
|
||||
@@ -39,6 +39,12 @@ import (
|
||||
|
||||
func init() {
|
||||
simple.ModuleRegister(ModuleName, "errorbool", &simple.Scaffold{
|
||||
I: &simple.Info{
|
||||
Pure: false, // XXX: because it errors do we need to be be safer here?
|
||||
Memo: false,
|
||||
Fast: true,
|
||||
Spec: false,
|
||||
},
|
||||
T: types.NewType("func(a bool) str"),
|
||||
F: ErrorBool,
|
||||
})
|
||||
|
||||
@@ -123,6 +123,9 @@ func (obj *FlipFlopFact) Stream(ctx context.Context) error {
|
||||
|
||||
// Call this fact and return the value if it is possible to do so at this time.
|
||||
func (obj *FlipFlopFact) Call(ctx context.Context) (types.Value, error) {
|
||||
if obj.mutex == nil {
|
||||
return nil, facts.ErrCantSpeculate
|
||||
}
|
||||
obj.mutex.Lock() // TODO: could be a read lock
|
||||
value := obj.value
|
||||
obj.mutex.Unlock()
|
||||
|
||||
@@ -39,6 +39,12 @@ import (
|
||||
|
||||
func init() {
|
||||
simple.ModuleRegister(ModuleName, "int2str", &simple.Scaffold{
|
||||
I: &simple.Info{
|
||||
Pure: true,
|
||||
Memo: true,
|
||||
Fast: true,
|
||||
Spec: true,
|
||||
},
|
||||
T: types.NewType("func(a int) str"),
|
||||
F: Int2Str,
|
||||
})
|
||||
|
||||
@@ -39,6 +39,12 @@ import (
|
||||
|
||||
func init() {
|
||||
simple.ModuleRegister(coreexample.ModuleName+"/"+ModuleName, "hello", &simple.Scaffold{
|
||||
I: &simple.Info{
|
||||
Pure: true,
|
||||
Memo: true,
|
||||
Fast: true,
|
||||
Spec: true,
|
||||
},
|
||||
T: types.NewType("func() str"),
|
||||
F: Hello,
|
||||
})
|
||||
|
||||
@@ -38,6 +38,12 @@ import (
|
||||
|
||||
func init() {
|
||||
simple.ModuleRegister(ModuleName, "plus", &simple.Scaffold{
|
||||
I: &simple.Info{
|
||||
Pure: true,
|
||||
Memo: true,
|
||||
Fast: true,
|
||||
Spec: true,
|
||||
},
|
||||
T: types.NewType("func(y str, z str) str"),
|
||||
F: Plus,
|
||||
})
|
||||
|
||||
@@ -39,6 +39,12 @@ import (
|
||||
|
||||
func init() {
|
||||
simple.ModuleRegister(ModuleName, "str2int", &simple.Scaffold{
|
||||
I: &simple.Info{
|
||||
Pure: true,
|
||||
Memo: true,
|
||||
Fast: true,
|
||||
Spec: true,
|
||||
},
|
||||
T: types.NewType("func(a str) int"),
|
||||
F: Str2Int,
|
||||
})
|
||||
|
||||
@@ -131,8 +131,10 @@ func (obj *VUMeterFunc) Validate() error {
|
||||
// Info returns some static info about itself.
|
||||
func (obj *VUMeterFunc) Info() *interfaces.Info {
|
||||
return &interfaces.Info{
|
||||
Pure: true,
|
||||
Pure: false,
|
||||
Memo: false,
|
||||
Fast: false,
|
||||
Spec: false,
|
||||
Sig: types.NewType(fmt.Sprintf("func(%s str, %s int, %s float) str", vuMeterArgNameSymbol, vuMeterArgNameMultiplier, vuMeterArgNamePeak)),
|
||||
}
|
||||
}
|
||||
@@ -212,6 +214,9 @@ func (obj *VUMeterFunc) Stream(ctx context.Context) error {
|
||||
// Call this function with the input args and return the value if it is possible
|
||||
// to do so at this time.
|
||||
func (obj *VUMeterFunc) Call(ctx context.Context, args []types.Value) (types.Value, error) {
|
||||
if len(args) < 3 {
|
||||
return nil, fmt.Errorf("not enough args")
|
||||
}
|
||||
symbol := args[0].Str()
|
||||
multiplier := args[1].Int()
|
||||
peak := args[2].Float()
|
||||
|
||||
@@ -98,7 +98,10 @@ type PrintfFunc struct {
|
||||
// String returns a simple name for this function. This is needed so this struct
|
||||
// can satisfy the pgraph.Vertex interface.
|
||||
func (obj *PrintfFunc) String() string {
|
||||
return fmt.Sprintf("%s@%p", PrintfFuncName, obj) // be more unique!
|
||||
if obj.Type != nil {
|
||||
return fmt.Sprintf("%s: %s", PrintfFuncName, obj.Type)
|
||||
}
|
||||
return PrintfFuncName
|
||||
}
|
||||
|
||||
// ArgGen returns the Nth arg name for this function.
|
||||
@@ -295,7 +298,9 @@ func (obj *PrintfFunc) Info() *interfaces.Info {
|
||||
// getting them from FuncInfer, and not from here. (During unification!)
|
||||
return &interfaces.Info{
|
||||
Pure: true,
|
||||
Memo: false,
|
||||
Memo: true,
|
||||
Fast: true,
|
||||
Spec: true,
|
||||
Sig: obj.Type,
|
||||
Err: obj.Validate(),
|
||||
}
|
||||
@@ -367,6 +372,9 @@ func (obj *PrintfFunc) Copy() interfaces.Func {
|
||||
// Call this function with the input args and return the value if it is possible
|
||||
// to do so at this time.
|
||||
func (obj *PrintfFunc) Call(ctx context.Context, args []types.Value) (types.Value, error) {
|
||||
if len(args) < 1 {
|
||||
return nil, fmt.Errorf("not enough args")
|
||||
}
|
||||
format := args[0].Str()
|
||||
|
||||
values := []types.Value{}
|
||||
|
||||
@@ -202,8 +202,10 @@ func (obj *TemplateFunc) Info() *interfaces.Info {
|
||||
sig = obj.sig() // helper
|
||||
}
|
||||
return &interfaces.Info{
|
||||
Pure: true,
|
||||
Pure: false, // contents of a template might not be pure
|
||||
Memo: false,
|
||||
Fast: false,
|
||||
Spec: false,
|
||||
Sig: sig,
|
||||
Err: obj.Validate(),
|
||||
}
|
||||
@@ -407,6 +409,9 @@ func (obj *TemplateFunc) Copy() interfaces.Func {
|
||||
// Call this function with the input args and return the value if it is possible
|
||||
// to do so at this time.
|
||||
func (obj *TemplateFunc) Call(ctx context.Context, args []types.Value) (types.Value, error) {
|
||||
if len(args) < 1 {
|
||||
return nil, fmt.Errorf("not enough args")
|
||||
}
|
||||
tmpl := args[0].Str()
|
||||
|
||||
var vars types.Value // nil
|
||||
@@ -414,6 +419,9 @@ func (obj *TemplateFunc) Call(ctx context.Context, args []types.Value) (types.Va
|
||||
vars = args[1]
|
||||
}
|
||||
|
||||
if obj.init == nil {
|
||||
return nil, funcs.ErrCantSpeculate
|
||||
}
|
||||
result, err := obj.run(ctx, tmpl, vars)
|
||||
if err != nil {
|
||||
return nil, err // no errwrap needed b/c helper func
|
||||
|
||||
@@ -158,6 +158,8 @@ func (obj *HistoryFunc) Info() *interfaces.Info {
|
||||
return &interfaces.Info{
|
||||
Pure: false, // definitely false
|
||||
Memo: false,
|
||||
Fast: false,
|
||||
Spec: false,
|
||||
Sig: obj.sig(), // helper
|
||||
Err: obj.Validate(),
|
||||
}
|
||||
|
||||
@@ -142,8 +142,10 @@ func (obj *FilterFunc) Validate() error {
|
||||
// will return correct data.
|
||||
func (obj *FilterFunc) Info() *interfaces.Info {
|
||||
return &interfaces.Info{
|
||||
Pure: false, // TODO: what if the input function isn't pure?
|
||||
Pure: false, // XXX: what if the input function isn't pure?
|
||||
Memo: false,
|
||||
Fast: false,
|
||||
Spec: false,
|
||||
Sig: obj.sig(), // helper
|
||||
Err: obj.Validate(),
|
||||
}
|
||||
@@ -412,9 +414,9 @@ func (obj *FilterFunc) replaceSubGraph(subgraphInput interfaces.Func) error {
|
||||
)
|
||||
obj.init.Txn.AddVertex(inputElemFunc)
|
||||
|
||||
outputElemFunc, err := obj.lastFuncValue.Call(obj.init.Txn, []interfaces.Func{inputElemFunc})
|
||||
outputElemFunc, err := obj.lastFuncValue.CallWithFuncs(obj.init.Txn, []interfaces.Func{inputElemFunc})
|
||||
if err != nil {
|
||||
return errwrap.Wrapf(err, "could not call obj.lastFuncValue.Call()")
|
||||
return errwrap.Wrapf(err, "could not call obj.lastFuncValue.CallWithFuncs()")
|
||||
}
|
||||
|
||||
obj.init.Txn.AddEdge(subgraphInput, inputElemFunc, &interfaces.FuncEdge{
|
||||
|
||||
@@ -204,8 +204,10 @@ func (obj *MapFunc) Validate() error {
|
||||
// will return correct data.
|
||||
func (obj *MapFunc) Info() *interfaces.Info {
|
||||
return &interfaces.Info{
|
||||
Pure: false, // TODO: what if the input function isn't pure?
|
||||
Pure: false, // XXX: what if the input function isn't pure?
|
||||
Memo: false,
|
||||
Fast: false,
|
||||
Spec: false,
|
||||
Sig: obj.sig(), // helper
|
||||
Err: obj.Validate(),
|
||||
}
|
||||
@@ -439,9 +441,9 @@ func (obj *MapFunc) replaceSubGraph(subgraphInput interfaces.Func) error {
|
||||
)
|
||||
obj.init.Txn.AddVertex(inputElemFunc)
|
||||
|
||||
outputElemFunc, err := obj.lastFuncValue.Call(obj.init.Txn, []interfaces.Func{inputElemFunc})
|
||||
outputElemFunc, err := obj.lastFuncValue.CallWithFuncs(obj.init.Txn, []interfaces.Func{inputElemFunc})
|
||||
if err != nil {
|
||||
return errwrap.Wrapf(err, "could not call obj.lastFuncValue.Call()")
|
||||
return errwrap.Wrapf(err, "could not call obj.lastFuncValue.CallWithFuncs()")
|
||||
}
|
||||
|
||||
obj.init.Txn.AddEdge(subgraphInput, inputElemFunc, &interfaces.FuncEdge{
|
||||
|
||||
@@ -161,7 +161,9 @@ func (obj *RangeFunc) Validate() error {
|
||||
func (obj *RangeFunc) Info() *interfaces.Info {
|
||||
return &interfaces.Info{
|
||||
Pure: true,
|
||||
Memo: false,
|
||||
Memo: true,
|
||||
Fast: true,
|
||||
Spec: true,
|
||||
Sig: obj.Type,
|
||||
Err: obj.Validate(),
|
||||
}
|
||||
|
||||
@@ -39,6 +39,12 @@ import (
|
||||
|
||||
func init() {
|
||||
simple.Register("len", &simple.Scaffold{
|
||||
I: &simple.Info{
|
||||
Pure: true,
|
||||
Memo: true,
|
||||
Fast: true,
|
||||
Spec: true,
|
||||
},
|
||||
T: types.NewType("func(?1) int"),
|
||||
// TODO: should we add support for struct or func lengths?
|
||||
C: simple.TypeMatch([]string{
|
||||
|
||||
@@ -158,6 +158,13 @@ func (obj *ListConcatFunc) Build(typ *types.Type) (*types.Type, error) {
|
||||
|
||||
obj.Func = &wrapped.Func{
|
||||
Name: obj.String(),
|
||||
FuncInfo: &wrapped.Info{
|
||||
// TODO: dedup with below Info data
|
||||
Pure: true,
|
||||
Memo: true,
|
||||
Fast: true,
|
||||
Spec: true,
|
||||
},
|
||||
Type: typ, // .Copy(),
|
||||
}
|
||||
|
||||
@@ -188,6 +195,7 @@ func (obj *ListConcatFunc) Copy() interfaces.Func {
|
||||
func (obj *ListConcatFunc) Call(ctx context.Context, args []types.Value) (types.Value, error) {
|
||||
values := []types.Value{}
|
||||
|
||||
// TODO: Could speculation pass in non-lists here and cause a panic?
|
||||
for _, x := range args {
|
||||
values = append(values, x.List()...)
|
||||
}
|
||||
@@ -217,7 +225,9 @@ func (obj *ListConcatFunc) Validate() error {
|
||||
func (obj *ListConcatFunc) Info() *interfaces.Info {
|
||||
return &interfaces.Info{
|
||||
Pure: true,
|
||||
Memo: false,
|
||||
Memo: true,
|
||||
Fast: true,
|
||||
Spec: true,
|
||||
Sig: obj.sig(), // helper
|
||||
Err: obj.Validate(),
|
||||
}
|
||||
|
||||
@@ -202,6 +202,13 @@ func (obj *ListLookupFunc) Build(typ *types.Type) (*types.Type, error) {
|
||||
|
||||
obj.Func = &wrapped.Func{
|
||||
Name: obj.String(),
|
||||
FuncInfo: &wrapped.Info{
|
||||
// TODO: dedup with below Info data
|
||||
Pure: true,
|
||||
Memo: true,
|
||||
Fast: true,
|
||||
Spec: true,
|
||||
},
|
||||
Type: typ, // .Copy(),
|
||||
}
|
||||
|
||||
@@ -230,6 +237,10 @@ func (obj *ListLookupFunc) Copy() interfaces.Func {
|
||||
// Call is the actual implementation here. This is used in lieu of the Stream
|
||||
// function which we'd have these contents within.
|
||||
func (obj *ListLookupFunc) Call(ctx context.Context, args []types.Value) (types.Value, error) {
|
||||
if len(args) < 2 {
|
||||
return nil, fmt.Errorf("not enough args")
|
||||
}
|
||||
// TODO: Could speculation pass in a non-list here and cause a panic?
|
||||
l := (args[0]).(*types.ListValue)
|
||||
index := args[1].Int()
|
||||
//zero := l.Type().Val.New() // the zero value
|
||||
@@ -273,7 +284,9 @@ func (obj *ListLookupFunc) Validate() error {
|
||||
func (obj *ListLookupFunc) Info() *interfaces.Info {
|
||||
return &interfaces.Info{
|
||||
Pure: true,
|
||||
Memo: false,
|
||||
Memo: true,
|
||||
Fast: true,
|
||||
Spec: true,
|
||||
Sig: obj.sig(), // helper
|
||||
Err: obj.Validate(),
|
||||
}
|
||||
|
||||
@@ -45,6 +45,12 @@ const (
|
||||
|
||||
func init() {
|
||||
simple.Register(ListLookupFuncName, &simple.Scaffold{
|
||||
I: &simple.Info{
|
||||
Pure: true,
|
||||
Memo: true,
|
||||
Fast: true,
|
||||
Spec: true,
|
||||
},
|
||||
T: types.NewType("func(list []?1, index int) ?1"),
|
||||
F: ListLookup,
|
||||
})
|
||||
|
||||
@@ -45,6 +45,12 @@ const (
|
||||
|
||||
func init() {
|
||||
simple.Register(ListLookupDefaultFuncName, &simple.Scaffold{
|
||||
I: &simple.Info{
|
||||
Pure: true,
|
||||
Memo: true,
|
||||
Fast: true,
|
||||
Spec: true,
|
||||
},
|
||||
T: types.NewType("func(list []?1, index int, default ?1) ?1"),
|
||||
F: ListLookupDefault,
|
||||
})
|
||||
|
||||
@@ -54,6 +54,8 @@ func init() {
|
||||
funcs.ModuleRegister(ModuleName, PoolFuncName, func() interfaces.Func { return &PoolFunc{} }) // must register the func and name
|
||||
}
|
||||
|
||||
var _ interfaces.DataFunc = &PoolFunc{}
|
||||
|
||||
// PoolFunc is a function that returns a unique integer from a pool of numbers.
|
||||
// Within a given namespace, it returns the same integer for a given name. It is
|
||||
// a simple mechanism to allocate numbers to different inputs when we don't have
|
||||
@@ -93,8 +95,10 @@ func (obj *PoolFunc) Validate() error {
|
||||
// Info returns some static info about itself.
|
||||
func (obj *PoolFunc) Info() *interfaces.Info {
|
||||
return &interfaces.Info{
|
||||
Pure: true,
|
||||
Pure: false, // depends on local API
|
||||
Memo: false,
|
||||
Fast: false,
|
||||
Spec: false,
|
||||
Sig: types.NewType(fmt.Sprintf("func(%s str, %s str) int", absPathArgNameNamespace, absPathArgNameUID)),
|
||||
// TODO: add an optional config arg
|
||||
//Sig: types.NewType(fmt.Sprintf("func(%s str, %s str, %s struct{}) int", absPathArgNameNamespace, absPathArgNameUID, absPathArgNameConfig)),
|
||||
@@ -153,15 +157,30 @@ func (obj *PoolFunc) Stream(ctx context.Context) error {
|
||||
}
|
||||
}
|
||||
|
||||
// Copy is implemented so that the obj.built value is not lost if we copy this
|
||||
// function.
|
||||
func (obj *PoolFunc) Copy() interfaces.Func {
|
||||
return &PoolFunc{
|
||||
init: obj.init, // likely gets overwritten anyways
|
||||
data: obj.data, // needed because we don't call SetData twice
|
||||
}
|
||||
}
|
||||
|
||||
// Call this function with the input args and return the value if it is possible
|
||||
// to do so at this time.
|
||||
func (obj *PoolFunc) Call(ctx context.Context, args []types.Value) (types.Value, error) {
|
||||
if len(args) < 2 {
|
||||
return nil, fmt.Errorf("not enough args")
|
||||
}
|
||||
// Validation of these inputs happens in the Local API which does it.
|
||||
namespace := args[0].Str()
|
||||
uid := args[1].Str()
|
||||
// TODO: pass in config
|
||||
//config := args[2].???()
|
||||
|
||||
if obj.init == nil {
|
||||
return nil, funcs.ErrCantSpeculate
|
||||
}
|
||||
result, err := obj.init.Local.Pool(ctx, namespace, uid, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -61,6 +61,8 @@ func init() {
|
||||
funcs.ModuleRegister(ModuleName, VarDirFuncName, func() interfaces.Func { return &VarDirFunc{} }) // must register the func and name
|
||||
}
|
||||
|
||||
var _ interfaces.DataFunc = &VarDirFunc{}
|
||||
|
||||
// VarDirFunc is a function that returns the absolute, full path in the deploy
|
||||
// from an input path that is relative to the calling file. If you pass it an
|
||||
// empty string, you'll just get the absolute deploy directory path that you're
|
||||
@@ -102,8 +104,10 @@ func (obj *VarDirFunc) Validate() error {
|
||||
// Info returns some static info about itself.
|
||||
func (obj *VarDirFunc) Info() *interfaces.Info {
|
||||
return &interfaces.Info{
|
||||
Pure: true,
|
||||
Pure: false, // TODO: depends on runtime dir path
|
||||
Memo: false,
|
||||
Fast: false,
|
||||
Spec: false,
|
||||
Sig: types.NewType(fmt.Sprintf("func(%s str) str", absPathArgNamePath)),
|
||||
}
|
||||
}
|
||||
@@ -160,9 +164,21 @@ func (obj *VarDirFunc) Stream(ctx context.Context) error {
|
||||
}
|
||||
}
|
||||
|
||||
// Copy is implemented so that the obj.built value is not lost if we copy this
|
||||
// function.
|
||||
func (obj *VarDirFunc) Copy() interfaces.Func {
|
||||
return &VarDirFunc{
|
||||
init: obj.init, // likely gets overwritten anyways
|
||||
data: obj.data, // needed because we don't call SetData twice
|
||||
}
|
||||
}
|
||||
|
||||
// Call this function with the input args and return the value if it is possible
|
||||
// to do so at this time.
|
||||
func (obj *VarDirFunc) Call(ctx context.Context, args []types.Value) (types.Value, error) {
|
||||
if len(args) < 1 {
|
||||
return nil, fmt.Errorf("not enough args")
|
||||
}
|
||||
reldir := args[0].Str()
|
||||
if strings.HasPrefix(reldir, "/") {
|
||||
return nil, fmt.Errorf("path must be relative")
|
||||
@@ -174,6 +190,9 @@ func (obj *VarDirFunc) Call(ctx context.Context, args []types.Value) (types.Valu
|
||||
|
||||
p := fmt.Sprintf("%s/", path.Join(VarDirFunctionsPrefix, reldir))
|
||||
|
||||
if obj.init == nil {
|
||||
return nil, funcs.ErrCantSpeculate
|
||||
}
|
||||
result, err := obj.init.Local.VarDir(ctx, p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -148,6 +148,9 @@ func (obj *LookupFunc) FuncInfer(partialType *types.Type, partialValues []types.
|
||||
// runs.
|
||||
func (obj *LookupFunc) Build(typ *types.Type) (*types.Type, error) {
|
||||
// typ is the KindFunc signature we're trying to build...
|
||||
if typ == nil {
|
||||
return nil, fmt.Errorf("nil type") // happens b/c of Copy()
|
||||
}
|
||||
if typ.Kind != types.KindFunc {
|
||||
return nil, fmt.Errorf("input type must be of kind func")
|
||||
}
|
||||
@@ -226,7 +229,9 @@ func (obj *LookupFunc) Info() *interfaces.Info {
|
||||
if obj.fn == nil {
|
||||
return &interfaces.Info{
|
||||
Pure: true,
|
||||
Memo: false,
|
||||
Memo: true,
|
||||
Fast: true,
|
||||
Spec: true,
|
||||
Sig: types.NewType("func(?1, ?2) ?3"), // func kind
|
||||
Err: obj.Validate(),
|
||||
}
|
||||
@@ -253,6 +258,9 @@ func (obj *LookupFunc) Stream(ctx context.Context) error {
|
||||
|
||||
// Call returns the result of this function.
|
||||
func (obj *LookupFunc) Call(ctx context.Context, args []types.Value) (types.Value, error) {
|
||||
if obj.fn == nil {
|
||||
return nil, funcs.ErrCantSpeculate
|
||||
}
|
||||
cf, ok := obj.fn.(interfaces.CallableFunc)
|
||||
if !ok {
|
||||
// programming error
|
||||
|
||||
@@ -93,6 +93,9 @@ func (obj *LookupDefaultFunc) ArgGen(index int) (string, error) {
|
||||
// runs.
|
||||
func (obj *LookupDefaultFunc) Build(typ *types.Type) (*types.Type, error) {
|
||||
// typ is the KindFunc signature we're trying to build...
|
||||
if typ == nil {
|
||||
return nil, fmt.Errorf("nil type") // happens b/c of Copy()
|
||||
}
|
||||
if typ.Kind != types.KindFunc {
|
||||
return nil, fmt.Errorf("input type must be of kind func")
|
||||
}
|
||||
@@ -172,7 +175,9 @@ func (obj *LookupDefaultFunc) Info() *interfaces.Info {
|
||||
if obj.fn == nil {
|
||||
return &interfaces.Info{
|
||||
Pure: true,
|
||||
Memo: false,
|
||||
Memo: true,
|
||||
Fast: true,
|
||||
Spec: true,
|
||||
Sig: types.NewType("func(?1, ?2, ?3) ?3"), // func kind
|
||||
Err: obj.Validate(),
|
||||
}
|
||||
@@ -199,6 +204,9 @@ func (obj *LookupDefaultFunc) Stream(ctx context.Context) error {
|
||||
|
||||
// Call returns the result of this function.
|
||||
func (obj *LookupDefaultFunc) Call(ctx context.Context, args []types.Value) (types.Value, error) {
|
||||
if obj.fn == nil {
|
||||
return nil, funcs.ErrCantSpeculate
|
||||
}
|
||||
cf, ok := obj.fn.(interfaces.CallableFunc)
|
||||
if !ok {
|
||||
// programming error
|
||||
|
||||
@@ -39,6 +39,12 @@ import (
|
||||
|
||||
func init() {
|
||||
simple.ModuleRegister(ModuleName, "keys", &simple.Scaffold{
|
||||
I: &simple.Info{
|
||||
Pure: true,
|
||||
Memo: true,
|
||||
Fast: true,
|
||||
Spec: true,
|
||||
},
|
||||
// TODO: Maybe saying ?0 could mean we don't care about that type?
|
||||
T: types.NewType("func(x map{?1: ?2}) []?1"),
|
||||
F: MapKeys,
|
||||
|
||||
@@ -204,6 +204,13 @@ func (obj *MapLookupFunc) Build(typ *types.Type) (*types.Type, error) {
|
||||
|
||||
obj.Func = &wrapped.Func{
|
||||
Name: obj.String(),
|
||||
FuncInfo: &wrapped.Info{
|
||||
// TODO: dedup with below Info data
|
||||
Pure: true,
|
||||
Memo: true,
|
||||
Fast: true,
|
||||
Spec: true,
|
||||
},
|
||||
Type: typ, // .Copy(),
|
||||
}
|
||||
|
||||
@@ -232,6 +239,9 @@ func (obj *MapLookupFunc) Copy() interfaces.Func {
|
||||
// Call is the actual implementation here. This is used in lieu of the Stream
|
||||
// function which we'd have these contents within.
|
||||
func (obj *MapLookupFunc) Call(ctx context.Context, args []types.Value) (types.Value, error) {
|
||||
if len(args) < 2 {
|
||||
return nil, fmt.Errorf("not enough args")
|
||||
}
|
||||
m := (args[0]).(*types.MapValue)
|
||||
key := args[1]
|
||||
//zero := m.Type().Val.New() // the zero value
|
||||
@@ -266,7 +276,9 @@ func (obj *MapLookupFunc) Validate() error {
|
||||
func (obj *MapLookupFunc) Info() *interfaces.Info {
|
||||
return &interfaces.Info{
|
||||
Pure: true,
|
||||
Memo: false,
|
||||
Memo: true,
|
||||
Fast: true,
|
||||
Spec: true,
|
||||
Sig: obj.sig(), // helper
|
||||
Err: obj.Validate(),
|
||||
}
|
||||
|
||||
@@ -39,6 +39,12 @@ import (
|
||||
|
||||
func init() {
|
||||
simple.ModuleRegister(ModuleName, "values", &simple.Scaffold{
|
||||
I: &simple.Info{
|
||||
Pure: true,
|
||||
Memo: true,
|
||||
Fast: true,
|
||||
Spec: true,
|
||||
},
|
||||
// TODO: Maybe saying ?0 could mean we don't care about that type?
|
||||
T: types.NewType("func(x map{?1: ?2}) []?2"),
|
||||
F: MapValues,
|
||||
|
||||
@@ -43,6 +43,12 @@ const (
|
||||
|
||||
func init() {
|
||||
simple.Register(MapLookupFuncName, &simple.Scaffold{
|
||||
I: &simple.Info{
|
||||
Pure: true,
|
||||
Memo: true,
|
||||
Fast: true,
|
||||
Spec: true,
|
||||
},
|
||||
T: types.NewType("func(map map{?1: ?2}, key ?1) ?2"),
|
||||
F: MapLookup,
|
||||
})
|
||||
|
||||
@@ -43,6 +43,12 @@ const (
|
||||
|
||||
func init() {
|
||||
simple.Register(MapLookupDefaultFuncName, &simple.Scaffold{
|
||||
I: &simple.Info{
|
||||
Pure: true,
|
||||
Memo: true,
|
||||
Fast: true,
|
||||
Spec: true,
|
||||
},
|
||||
T: types.NewType("func(map map{?1: ?2}, key ?1, default ?2) ?2"),
|
||||
F: MapLookupDefault,
|
||||
})
|
||||
|
||||
@@ -42,6 +42,12 @@ func init() {
|
||||
typInt := types.NewType("func() int")
|
||||
typFloat := types.NewType("func() float")
|
||||
multi.ModuleRegister(ModuleName, "fortytwo", &multi.Scaffold{
|
||||
I: &multi.Info{
|
||||
Pure: true,
|
||||
Memo: true,
|
||||
Fast: true,
|
||||
Spec: true,
|
||||
},
|
||||
T: types.NewType("func() ?1"),
|
||||
M: multi.TypeMatch(map[string]interfaces.FuncSig{
|
||||
"func() int": fortyTwo(typInt),
|
||||
|
||||
@@ -38,6 +38,12 @@ import (
|
||||
|
||||
func init() {
|
||||
simple.ModuleRegister(ModuleName, "minus1", &simple.Scaffold{
|
||||
I: &simple.Info{
|
||||
Pure: true,
|
||||
Memo: true,
|
||||
Fast: true,
|
||||
Spec: true,
|
||||
},
|
||||
T: types.NewType("func(x int) int"),
|
||||
F: Minus1,
|
||||
})
|
||||
|
||||
@@ -40,6 +40,12 @@ import (
|
||||
|
||||
func init() {
|
||||
simple.ModuleRegister(ModuleName, "mod", &simple.Scaffold{
|
||||
I: &simple.Info{
|
||||
Pure: true,
|
||||
Memo: true,
|
||||
Fast: true,
|
||||
Spec: true,
|
||||
},
|
||||
T: types.NewType("func(?1, ?1) ?1"), // all int or float
|
||||
C: simple.TypeMatch([]string{
|
||||
"func(int, int) int",
|
||||
|
||||
@@ -40,6 +40,12 @@ import (
|
||||
|
||||
func init() {
|
||||
simple.ModuleRegister(ModuleName, "pow", &simple.Scaffold{
|
||||
I: &simple.Info{
|
||||
Pure: true,
|
||||
Memo: true,
|
||||
Fast: true,
|
||||
Spec: true,
|
||||
},
|
||||
T: types.NewType("func(x float, y float) float"),
|
||||
F: Pow,
|
||||
})
|
||||
|
||||
@@ -40,6 +40,12 @@ import (
|
||||
|
||||
func init() {
|
||||
simple.ModuleRegister(ModuleName, "sqrt", &simple.Scaffold{
|
||||
I: &simple.Info{
|
||||
Pure: true,
|
||||
Memo: true,
|
||||
Fast: true,
|
||||
Spec: true,
|
||||
},
|
||||
T: types.NewType("func(x float) float"),
|
||||
F: Sqrt,
|
||||
})
|
||||
|
||||
@@ -44,22 +44,52 @@ import (
|
||||
|
||||
func init() {
|
||||
simple.ModuleRegister(ModuleName, "cidr_to_ip", &simple.Scaffold{
|
||||
I: &simple.Info{
|
||||
Pure: true,
|
||||
Memo: true,
|
||||
Fast: true,
|
||||
Spec: true,
|
||||
},
|
||||
T: types.NewType("func(a str) str"),
|
||||
F: CidrToIP,
|
||||
})
|
||||
simple.ModuleRegister(ModuleName, "cidr_to_prefix", &simple.Scaffold{
|
||||
I: &simple.Info{
|
||||
Pure: true,
|
||||
Memo: true,
|
||||
Fast: true,
|
||||
Spec: true,
|
||||
},
|
||||
T: types.NewType("func(a str) str"),
|
||||
F: CidrToPrefix,
|
||||
})
|
||||
simple.ModuleRegister(ModuleName, "cidr_to_mask", &simple.Scaffold{
|
||||
I: &simple.Info{
|
||||
Pure: true,
|
||||
Memo: true,
|
||||
Fast: true,
|
||||
Spec: true,
|
||||
},
|
||||
T: types.NewType("func(a str) str"),
|
||||
F: CidrToMask,
|
||||
})
|
||||
simple.ModuleRegister(ModuleName, "cidr_to_first", &simple.Scaffold{
|
||||
I: &simple.Info{
|
||||
Pure: true,
|
||||
Memo: true,
|
||||
Fast: true,
|
||||
Spec: true,
|
||||
},
|
||||
T: types.NewType("func(a str) str"),
|
||||
F: CidrToFirst,
|
||||
})
|
||||
simple.ModuleRegister(ModuleName, "cidr_to_last", &simple.Scaffold{
|
||||
I: &simple.Info{
|
||||
Pure: true,
|
||||
Memo: true,
|
||||
Fast: true,
|
||||
Spec: true,
|
||||
},
|
||||
T: types.NewType("func(a str) str"),
|
||||
F: CidrToLast,
|
||||
})
|
||||
|
||||
@@ -41,14 +41,32 @@ import (
|
||||
|
||||
func init() {
|
||||
simple.ModuleRegister(ModuleName, "is_mac", &simple.Scaffold{
|
||||
I: &simple.Info{
|
||||
Pure: true,
|
||||
Memo: true,
|
||||
Fast: true,
|
||||
Spec: true,
|
||||
},
|
||||
T: types.NewType("func(a str) bool"),
|
||||
F: IsMac,
|
||||
})
|
||||
simple.ModuleRegister(ModuleName, "macfmt", &simple.Scaffold{
|
||||
I: &simple.Info{
|
||||
Pure: true,
|
||||
Memo: true,
|
||||
Fast: true,
|
||||
Spec: true,
|
||||
},
|
||||
T: types.NewType("func(a str) str"),
|
||||
F: MacFmt,
|
||||
})
|
||||
simple.ModuleRegister(ModuleName, "oldmacfmt", &simple.Scaffold{
|
||||
I: &simple.Info{
|
||||
Pure: true,
|
||||
Memo: true,
|
||||
Fast: true,
|
||||
Spec: true,
|
||||
},
|
||||
T: types.NewType("func(a str) str"),
|
||||
F: OldMacFmt,
|
||||
})
|
||||
|
||||
@@ -39,6 +39,12 @@ import (
|
||||
|
||||
func init() {
|
||||
simple.ModuleRegister(ModuleName, "macs", &simple.Scaffold{
|
||||
I: &simple.Info{
|
||||
Pure: false,
|
||||
Memo: false,
|
||||
Fast: false,
|
||||
Spec: false, // might be different at real runtime
|
||||
},
|
||||
T: types.NewType("func() []str"),
|
||||
F: Macs,
|
||||
})
|
||||
|
||||
@@ -56,6 +56,12 @@ var urlParserReturnType = fmt.Sprintf(
|
||||
|
||||
func init() {
|
||||
simple.ModuleRegister(ModuleName, "url_parser", &simple.Scaffold{
|
||||
I: &simple.Info{
|
||||
Pure: true,
|
||||
Memo: true,
|
||||
Fast: true,
|
||||
Spec: true,
|
||||
},
|
||||
T: types.NewType(fmt.Sprintf("func(str) %s", urlParserReturnType)),
|
||||
F: URLParser,
|
||||
})
|
||||
|
||||
@@ -39,6 +39,13 @@ import (
|
||||
|
||||
func init() {
|
||||
simple.ModuleRegister(ModuleName, "args", &simple.Scaffold{
|
||||
I: &simple.Info{
|
||||
// XXX: make sure nobody changes it at runtime
|
||||
Pure: true,
|
||||
Memo: true,
|
||||
Fast: true,
|
||||
Spec: true,
|
||||
},
|
||||
T: types.NewType("func() []str"),
|
||||
F: Args,
|
||||
})
|
||||
|
||||
@@ -44,6 +44,12 @@ var typeParseDistroUID = types.NewType(fmt.Sprintf("func(str) %s", structDistroU
|
||||
|
||||
func init() {
|
||||
simple.ModuleRegister(ModuleName, "parse_distro_uid", &simple.Scaffold{
|
||||
I: &simple.Info{
|
||||
Pure: true,
|
||||
Memo: true,
|
||||
Fast: true,
|
||||
Spec: true,
|
||||
},
|
||||
T: typeParseDistroUID,
|
||||
F: ParseDistroUID,
|
||||
})
|
||||
|
||||
@@ -39,6 +39,13 @@ import (
|
||||
|
||||
func init() {
|
||||
simple.ModuleRegister(ModuleName, "expand_home", &simple.Scaffold{
|
||||
// These might change depending on user database...
|
||||
I: &simple.Info{
|
||||
Pure: false,
|
||||
Memo: false,
|
||||
Fast: true,
|
||||
Spec: false,
|
||||
},
|
||||
T: types.NewType("func(str) str"),
|
||||
F: ExpandHome,
|
||||
})
|
||||
|
||||
@@ -40,6 +40,13 @@ import (
|
||||
|
||||
func init() {
|
||||
simple.ModuleRegister(ModuleName, "family", &simple.Scaffold{
|
||||
I: &simple.Info{
|
||||
// In theory these are dependent on runtime.
|
||||
Pure: false,
|
||||
Memo: false,
|
||||
Fast: true,
|
||||
Spec: false,
|
||||
},
|
||||
T: types.NewType("func() str"),
|
||||
F: Family,
|
||||
})
|
||||
@@ -62,14 +69,35 @@ func init() {
|
||||
|
||||
// TODO: Create a family method that will return a giant struct.
|
||||
simple.ModuleRegister(ModuleName, "is_family_redhat", &simple.Scaffold{
|
||||
I: &simple.Info{
|
||||
// In theory these are dependent on runtime.
|
||||
Pure: false,
|
||||
Memo: false,
|
||||
Fast: true,
|
||||
Spec: false,
|
||||
},
|
||||
T: types.NewType("func() bool"),
|
||||
F: IsFamilyRedHat,
|
||||
})
|
||||
simple.ModuleRegister(ModuleName, "is_family_debian", &simple.Scaffold{
|
||||
I: &simple.Info{
|
||||
// In theory these are dependent on runtime.
|
||||
Pure: false,
|
||||
Memo: false,
|
||||
Fast: true,
|
||||
Spec: false,
|
||||
},
|
||||
T: types.NewType("func() bool"),
|
||||
F: IsFamilyDebian,
|
||||
})
|
||||
simple.ModuleRegister(ModuleName, "is_family_archlinux", &simple.Scaffold{
|
||||
I: &simple.Info{
|
||||
// In theory these are dependent on runtime.
|
||||
Pure: false,
|
||||
Memo: false,
|
||||
Fast: true,
|
||||
Spec: false,
|
||||
},
|
||||
T: types.NewType("func() bool"),
|
||||
F: IsFamilyArchLinux,
|
||||
})
|
||||
|
||||
@@ -77,6 +77,13 @@ var virtualizationVendorSlice = []string{
|
||||
|
||||
func init() {
|
||||
simple.ModuleRegister(ModuleName, "is_virtual", &simple.Scaffold{
|
||||
I: &simple.Info{
|
||||
// In theory these are dependent on runtime.
|
||||
Pure: false,
|
||||
Memo: false,
|
||||
Fast: false, // network requests to API's?
|
||||
Spec: false,
|
||||
},
|
||||
T: types.NewType("func() bool"),
|
||||
F: IsVirtual,
|
||||
})
|
||||
|
||||
@@ -97,6 +97,8 @@ func (obj *ReadFileFunc) Info() *interfaces.Info {
|
||||
return &interfaces.Info{
|
||||
Pure: false, // maybe false because the file contents can change
|
||||
Memo: false,
|
||||
Fast: false,
|
||||
Spec: false,
|
||||
Sig: types.NewType(fmt.Sprintf("func(%s str) str", readFileArgNameFilename)),
|
||||
}
|
||||
}
|
||||
@@ -249,6 +251,9 @@ func (obj *ReadFileFunc) Stream(ctx context.Context) error {
|
||||
// Call this function with the input args and return the value if it is possible
|
||||
// to do so at this time.
|
||||
func (obj *ReadFileFunc) Call(ctx context.Context, args []types.Value) (types.Value, error) {
|
||||
if len(args) < 1 {
|
||||
return nil, fmt.Errorf("not enough args")
|
||||
}
|
||||
filename := args[0].Str()
|
||||
|
||||
// read file...
|
||||
|
||||
@@ -91,6 +91,8 @@ func (obj *SystemFunc) Info() *interfaces.Info {
|
||||
return &interfaces.Info{
|
||||
Pure: false, // definitely false
|
||||
Memo: false,
|
||||
Fast: false,
|
||||
Spec: false,
|
||||
Sig: types.NewType(fmt.Sprintf("func(%s str) str", systemArgNameCmd)),
|
||||
Err: obj.Validate(),
|
||||
}
|
||||
|
||||
@@ -39,6 +39,12 @@ import (
|
||||
|
||||
func init() {
|
||||
simple.Register("panic", &simple.Scaffold{
|
||||
I: &simple.Info{
|
||||
Pure: false, // n/a
|
||||
Memo: false,
|
||||
Fast: true,
|
||||
Spec: false, // important!
|
||||
},
|
||||
T: types.NewType("func(x ?1) bool"), // ?1 is bool or str
|
||||
C: simple.TypeMatch([]string{
|
||||
"func(x bool) bool",
|
||||
|
||||
@@ -96,6 +96,9 @@ func (obj *Random1Func) Validate() error {
|
||||
func (obj *Random1Func) Info() *interfaces.Info {
|
||||
return &interfaces.Info{
|
||||
Pure: false,
|
||||
Memo: false,
|
||||
Fast: false,
|
||||
Spec: false,
|
||||
Sig: types.NewType(fmt.Sprintf("func(%s int) str", random1ArgNameLength)),
|
||||
Err: obj.Validate(),
|
||||
}
|
||||
|
||||
@@ -40,6 +40,12 @@ import (
|
||||
|
||||
func init() {
|
||||
simple.ModuleRegister(ModuleName, "match", &simple.Scaffold{
|
||||
I: &simple.Info{
|
||||
Pure: true,
|
||||
Memo: true,
|
||||
Fast: false, // TODO: should we consider this fast?
|
||||
Spec: true,
|
||||
},
|
||||
T: types.NewType("func(pattern str, s str) bool"),
|
||||
F: Match,
|
||||
})
|
||||
|
||||
@@ -39,6 +39,12 @@ import (
|
||||
|
||||
func init() {
|
||||
simple.ModuleRegister(ModuleName, "join_nonempty", &simple.Scaffold{
|
||||
I: &simple.Info{
|
||||
Pure: true,
|
||||
Memo: true,
|
||||
Fast: true,
|
||||
Spec: true,
|
||||
},
|
||||
T: types.NewType("func(s []str, sep str) str"),
|
||||
F: JoinNonempty,
|
||||
})
|
||||
|
||||
@@ -39,10 +39,22 @@ import (
|
||||
|
||||
func init() {
|
||||
simple.ModuleRegister(ModuleName, "left_pad", &simple.Scaffold{
|
||||
I: &simple.Info{
|
||||
Pure: true,
|
||||
Memo: true,
|
||||
Fast: true,
|
||||
Spec: true,
|
||||
},
|
||||
T: types.NewType("func(s str, pad str, len int) str"),
|
||||
F: LeftPad,
|
||||
})
|
||||
simple.ModuleRegister(ModuleName, "right_pad", &simple.Scaffold{
|
||||
I: &simple.Info{
|
||||
Pure: true,
|
||||
Memo: true,
|
||||
Fast: true,
|
||||
Spec: true,
|
||||
},
|
||||
T: types.NewType("func(s str, pad str, len int) str"),
|
||||
F: RightPad,
|
||||
})
|
||||
|
||||
@@ -39,6 +39,12 @@ import (
|
||||
|
||||
func init() {
|
||||
simple.ModuleRegister(ModuleName, "split", &simple.Scaffold{
|
||||
I: &simple.Info{
|
||||
Pure: true,
|
||||
Memo: true,
|
||||
Fast: true,
|
||||
Spec: true,
|
||||
},
|
||||
T: types.NewType("func(a str, b str) []str"),
|
||||
F: Split,
|
||||
})
|
||||
|
||||
@@ -39,6 +39,12 @@ import (
|
||||
|
||||
func init() {
|
||||
simple.ModuleRegister(ModuleName, "substr", &simple.Scaffold{
|
||||
I: &simple.Info{
|
||||
Pure: true,
|
||||
Memo: true,
|
||||
Fast: true,
|
||||
Spec: true,
|
||||
},
|
||||
T: types.NewType("func(s str, low int, high int) str"),
|
||||
F: SubStr,
|
||||
})
|
||||
|
||||
@@ -39,6 +39,12 @@ import (
|
||||
|
||||
func init() {
|
||||
simple.ModuleRegister(ModuleName, "to_lower", &simple.Scaffold{
|
||||
I: &simple.Info{
|
||||
Pure: true,
|
||||
Memo: true,
|
||||
Fast: true,
|
||||
Spec: true,
|
||||
},
|
||||
T: types.NewType("func(a str) str"),
|
||||
F: ToLower,
|
||||
})
|
||||
|
||||
@@ -269,7 +269,9 @@ func (obj *StructLookupFunc) Info() *interfaces.Info {
|
||||
}
|
||||
return &interfaces.Info{
|
||||
Pure: true,
|
||||
Memo: false,
|
||||
Memo: true,
|
||||
Fast: true,
|
||||
Spec: true,
|
||||
Sig: sig,
|
||||
Err: obj.Validate(),
|
||||
}
|
||||
@@ -339,6 +341,9 @@ func (obj *StructLookupFunc) Stream(ctx context.Context) error {
|
||||
|
||||
// Call returns the result of this function.
|
||||
func (obj *StructLookupFunc) Call(ctx context.Context, args []types.Value) (types.Value, error) {
|
||||
if len(args) < 2 {
|
||||
return nil, fmt.Errorf("not enough args")
|
||||
}
|
||||
st := args[0].(*types.StructValue)
|
||||
field := args[1].Str()
|
||||
|
||||
|
||||
@@ -266,7 +266,9 @@ func (obj *StructLookupOptionalFunc) Info() *interfaces.Info {
|
||||
}
|
||||
return &interfaces.Info{
|
||||
Pure: true,
|
||||
Memo: false,
|
||||
Memo: true,
|
||||
Fast: true,
|
||||
Spec: true,
|
||||
Sig: sig,
|
||||
Err: obj.Validate(),
|
||||
}
|
||||
@@ -345,6 +347,9 @@ func (obj *StructLookupOptionalFunc) Stream(ctx context.Context) error {
|
||||
|
||||
// Call returns the result of this function.
|
||||
func (obj *StructLookupOptionalFunc) Call(ctx context.Context, args []types.Value) (types.Value, error) {
|
||||
if len(args) < 3 {
|
||||
return nil, fmt.Errorf("not enough args")
|
||||
}
|
||||
st := args[0].(*types.StructValue)
|
||||
field := args[1].Str()
|
||||
optional := args[2]
|
||||
|
||||
@@ -76,6 +76,8 @@ func (obj *CPUCountFact) String() string {
|
||||
// Info returns static typing info about what the fact returns.
|
||||
func (obj *CPUCountFact) Info() *facts.Info {
|
||||
return &facts.Info{
|
||||
Pure: false,
|
||||
Memo: false,
|
||||
Output: types.NewType("int"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,18 +40,42 @@ import (
|
||||
|
||||
func init() {
|
||||
simple.ModuleRegister(ModuleName, "getenv", &simple.Scaffold{
|
||||
I: &simple.Info{
|
||||
Pure: false,
|
||||
Memo: false,
|
||||
Fast: true,
|
||||
Spec: false,
|
||||
},
|
||||
T: types.NewType("func(str) str"),
|
||||
F: GetEnv,
|
||||
})
|
||||
simple.ModuleRegister(ModuleName, "defaultenv", &simple.Scaffold{
|
||||
I: &simple.Info{
|
||||
Pure: false,
|
||||
Memo: false,
|
||||
Fast: true,
|
||||
Spec: false,
|
||||
},
|
||||
T: types.NewType("func(str, str) str"),
|
||||
F: DefaultEnv,
|
||||
})
|
||||
simple.ModuleRegister(ModuleName, "hasenv", &simple.Scaffold{
|
||||
I: &simple.Info{
|
||||
Pure: false,
|
||||
Memo: false,
|
||||
Fast: true,
|
||||
Spec: false,
|
||||
},
|
||||
T: types.NewType("func(str) bool"),
|
||||
F: HasEnv,
|
||||
})
|
||||
simple.ModuleRegister(ModuleName, "env", &simple.Scaffold{
|
||||
I: &simple.Info{
|
||||
Pure: false,
|
||||
Memo: false,
|
||||
Fast: true,
|
||||
Spec: false,
|
||||
},
|
||||
T: types.NewType("func() map{str: str}"),
|
||||
F: Env,
|
||||
})
|
||||
|
||||
@@ -70,6 +70,8 @@ func (obj *LoadFact) String() string {
|
||||
// Info returns some static info about itself.
|
||||
func (obj *LoadFact) Info() *facts.Info {
|
||||
return &facts.Info{
|
||||
Pure: false,
|
||||
Memo: false,
|
||||
Output: types.NewType(loadSignature),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,6 +62,8 @@ func (obj *UptimeFact) String() string {
|
||||
// Info returns some static info about itself.
|
||||
func (obj *UptimeFact) Info() *facts.Info {
|
||||
return &facts.Info{
|
||||
Pure: false,
|
||||
Memo: false,
|
||||
Output: types.TypeInt,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,6 +71,8 @@ func (obj *FastCountFact) String() string {
|
||||
// Info returns some static info about itself.
|
||||
func (obj *FastCountFact) Info() *facts.Info {
|
||||
return &facts.Info{
|
||||
Pure: false,
|
||||
Memo: false,
|
||||
Output: types.NewType("int"),
|
||||
}
|
||||
}
|
||||
@@ -108,6 +110,9 @@ func (obj *FastCountFact) Stream(ctx context.Context) error {
|
||||
|
||||
// Call this fact and return the value if it is possible to do so at this time.
|
||||
func (obj *FastCountFact) Call(ctx context.Context) (types.Value, error) {
|
||||
if obj.mutex == nil {
|
||||
return nil, facts.ErrCantSpeculate
|
||||
}
|
||||
obj.mutex.Lock() // TODO: could be a read lock
|
||||
count := obj.count
|
||||
obj.mutex.Unlock()
|
||||
|
||||
@@ -122,6 +122,12 @@ func init() {
|
||||
})
|
||||
|
||||
simple.ModuleRegister(ModuleName, OneInstanceBFuncName, &simple.Scaffold{
|
||||
I: &simple.Info{
|
||||
Pure: true,
|
||||
Memo: true,
|
||||
Fast: true,
|
||||
Spec: false, // don't break tests
|
||||
},
|
||||
T: types.NewType("func() str"),
|
||||
F: func(context.Context, []types.Value) (types.Value, error) {
|
||||
oneInstanceBMutex.Lock()
|
||||
@@ -135,6 +141,12 @@ func init() {
|
||||
D: &OneInstanceFact{},
|
||||
})
|
||||
simple.ModuleRegister(ModuleName, OneInstanceDFuncName, &simple.Scaffold{
|
||||
I: &simple.Info{
|
||||
Pure: true,
|
||||
Memo: true,
|
||||
Fast: true,
|
||||
Spec: false, // don't break tests
|
||||
},
|
||||
T: types.NewType("func() str"),
|
||||
F: func(context.Context, []types.Value) (types.Value, error) {
|
||||
oneInstanceDMutex.Lock()
|
||||
@@ -148,6 +160,12 @@ func init() {
|
||||
D: &OneInstanceFact{},
|
||||
})
|
||||
simple.ModuleRegister(ModuleName, OneInstanceFFuncName, &simple.Scaffold{
|
||||
I: &simple.Info{
|
||||
Pure: true,
|
||||
Memo: true,
|
||||
Fast: true,
|
||||
Spec: false, // don't break tests
|
||||
},
|
||||
T: types.NewType("func() str"),
|
||||
F: func(context.Context, []types.Value) (types.Value, error) {
|
||||
oneInstanceFMutex.Lock()
|
||||
@@ -161,6 +179,12 @@ func init() {
|
||||
D: &OneInstanceFact{},
|
||||
})
|
||||
simple.ModuleRegister(ModuleName, OneInstanceHFuncName, &simple.Scaffold{
|
||||
I: &simple.Info{
|
||||
Pure: true,
|
||||
Memo: true,
|
||||
Fast: true,
|
||||
Spec: false, // don't break tests
|
||||
},
|
||||
T: types.NewType("func() str"),
|
||||
F: func(context.Context, []types.Value) (types.Value, error) {
|
||||
oneInstanceHMutex.Lock()
|
||||
|
||||
@@ -41,6 +41,12 @@ import (
|
||||
|
||||
func init() {
|
||||
simple.ModuleRegister(ModuleName, "hostname_mapper", &simple.Scaffold{
|
||||
I: &simple.Info{
|
||||
Pure: false,
|
||||
Memo: false,
|
||||
Fast: false,
|
||||
Spec: false,
|
||||
},
|
||||
T: types.NewType("func(map{str:str}) str"),
|
||||
F: HostnameMapper,
|
||||
})
|
||||
|
||||
@@ -206,6 +206,8 @@ func (obj *GetFunc) Info() *interfaces.Info {
|
||||
return &interfaces.Info{
|
||||
Pure: false, // definitely false
|
||||
Memo: false,
|
||||
Fast: false,
|
||||
Spec: false,
|
||||
Sig: sig,
|
||||
Err: obj.Validate(),
|
||||
}
|
||||
@@ -310,6 +312,9 @@ func (obj *GetFunc) Stream(ctx context.Context) error {
|
||||
// to do so at this time. This was previously getValue which gets the value
|
||||
// we're looking for.
|
||||
func (obj *GetFunc) Call(ctx context.Context, args []types.Value) (types.Value, error) {
|
||||
if len(args) < 1 {
|
||||
return nil, fmt.Errorf("not enough args")
|
||||
}
|
||||
key := args[0].Str()
|
||||
|
||||
typ, exists := obj.Info().Sig.Out.Map[getFieldNameValue] // type of value field
|
||||
@@ -326,6 +331,9 @@ func (obj *GetFunc) Call(ctx context.Context, args []types.Value) (types.Value,
|
||||
// step that might be needed if the value started out empty...
|
||||
// TODO: We could even add a stored: bool field in the returned struct!
|
||||
isReady := true // assume true
|
||||
if obj.init == nil {
|
||||
return nil, funcs.ErrCantSpeculate
|
||||
}
|
||||
val, err := obj.init.Local.ValueGet(ctx, key)
|
||||
if err != nil {
|
||||
return nil, errwrap.Wrapf(err, "channel read failed on `%s`", key)
|
||||
|
||||
@@ -118,6 +118,8 @@ func (obj *ResFunc) Info() *interfaces.Info {
|
||||
return &interfaces.Info{
|
||||
Pure: false,
|
||||
Memo: false,
|
||||
Fast: false,
|
||||
Spec: false,
|
||||
Sig: obj.sig(),
|
||||
Err: obj.Validate(),
|
||||
}
|
||||
@@ -225,6 +227,9 @@ func (obj *ResFunc) Stream(ctx context.Context) error {
|
||||
// to do so at this time. This was previously getValue which gets the value
|
||||
// we're looking for.
|
||||
func (obj *ResFunc) Call(ctx context.Context, args []types.Value) (types.Value, error) {
|
||||
if len(args) < 1 {
|
||||
return nil, fmt.Errorf("not enough args")
|
||||
}
|
||||
kind := args[0].Str()
|
||||
if kind == "" {
|
||||
return nil, fmt.Errorf("resource kind is empty")
|
||||
@@ -241,6 +246,9 @@ func (obj *ResFunc) Call(ctx context.Context, args []types.Value) (types.Value,
|
||||
}
|
||||
filters = append(filters, filter)
|
||||
|
||||
if obj.init == nil {
|
||||
return nil, funcs.ErrCantSpeculate
|
||||
}
|
||||
resOutput, err := obj.init.World.ResCollect(ctx, filters)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -91,6 +91,8 @@ func (obj *ExchangeFunc) Info() *interfaces.Info {
|
||||
return &interfaces.Info{
|
||||
Pure: false, // definitely false
|
||||
Memo: false,
|
||||
Fast: false,
|
||||
Spec: false,
|
||||
// TODO: do we want to allow this to be statically polymorphic,
|
||||
// and have value be any type we might want?
|
||||
// output is map of: hostname => value
|
||||
|
||||
@@ -97,6 +97,8 @@ func (obj *GetValFunc) Info() *interfaces.Info {
|
||||
return &interfaces.Info{
|
||||
Pure: false, // definitely false
|
||||
Memo: false,
|
||||
Fast: false,
|
||||
Spec: false,
|
||||
// output is a struct with two fields:
|
||||
// value is the zero value if not exists. A bool for that in other field.
|
||||
Sig: types.NewType(fmt.Sprintf("func(%s str) struct{%s str; %s bool}", getValArgNameKey, getValFieldNameValue, getValFieldNameExists)),
|
||||
@@ -206,8 +208,14 @@ func (obj *GetValFunc) Stream(ctx context.Context) error {
|
||||
// to do so at this time. This was previously getValue which gets the value
|
||||
// we're looking for.
|
||||
func (obj *GetValFunc) Call(ctx context.Context, args []types.Value) (types.Value, error) {
|
||||
if len(args) < 1 {
|
||||
return nil, fmt.Errorf("not enough args")
|
||||
}
|
||||
key := args[0].Str()
|
||||
exists := true // assume true
|
||||
if obj.init == nil {
|
||||
return nil, funcs.ErrCantSpeculate
|
||||
}
|
||||
val, err := obj.init.World.StrGet(ctx, key)
|
||||
if err != nil && obj.init.World.StrIsNotExist(err) {
|
||||
exists = false // val doesn't exist
|
||||
|
||||
@@ -93,6 +93,8 @@ func (obj *KVLookupFunc) Info() *interfaces.Info {
|
||||
return &interfaces.Info{
|
||||
Pure: false, // definitely false
|
||||
Memo: false,
|
||||
Fast: false,
|
||||
Spec: false,
|
||||
// output is map of: hostname => value
|
||||
Sig: types.NewType(fmt.Sprintf("func(%s str) map{str: str}", kvLookupArgNameNamespace)),
|
||||
Err: obj.Validate(),
|
||||
@@ -211,7 +213,13 @@ func (obj *KVLookupFunc) Stream(ctx context.Context) error {
|
||||
// to do so at this time. This was previously buildMap, which builds the result
|
||||
// map which we'll need. It uses struct variables.
|
||||
func (obj *KVLookupFunc) Call(ctx context.Context, args []types.Value) (types.Value, error) {
|
||||
if len(args) < 1 {
|
||||
return nil, fmt.Errorf("not enough args")
|
||||
}
|
||||
namespace := args[0].Str()
|
||||
if obj.init == nil {
|
||||
return nil, funcs.ErrCantSpeculate
|
||||
}
|
||||
keyMap, err := obj.init.World.StrMapGet(ctx, namespace)
|
||||
if err != nil {
|
||||
return nil, errwrap.Wrapf(err, "channel read failed on `%s`", namespace)
|
||||
|
||||
@@ -283,6 +283,8 @@ func (obj *ScheduleFunc) Info() *interfaces.Info {
|
||||
return &interfaces.Info{
|
||||
Pure: false, // definitely false
|
||||
Memo: false,
|
||||
Fast: false,
|
||||
Spec: false,
|
||||
// output is list of hostnames chosen
|
||||
Sig: sig, // func kind
|
||||
Err: obj.Validate(),
|
||||
|
||||
@@ -40,6 +40,17 @@ import (
|
||||
"github.com/purpleidea/mgmt/lang/types"
|
||||
)
|
||||
|
||||
const (
|
||||
// ErrCantSpeculate is an error that explains that we can't speculate
|
||||
// when trying to Call a function. This often gets called by the Value()
|
||||
// method of the Expr. This can be useful if we want to distinguish
|
||||
// between "something is broken" and "I just can't produce a value at
|
||||
// this time", which can be identified and skipped over. If it's the
|
||||
// former, then it's okay to error early and shut everything down since
|
||||
// we know this function is never going to work the way it's called.
|
||||
ErrCantSpeculate = funcs.ErrCantSpeculate
|
||||
)
|
||||
|
||||
// RegisteredFacts is a global map of all possible facts which can be used. You
|
||||
// should never touch this map directly. Use methods like Register instead.
|
||||
var RegisteredFacts = make(map[string]func() Fact) // must initialize
|
||||
@@ -79,6 +90,10 @@ func ModuleRegister(module, name string, fn func() Fact) {
|
||||
// used for static analysis and type checking. If you break this contract, you
|
||||
// might cause a panic.
|
||||
type Info struct {
|
||||
Pure bool // is the function pure? (can it be memoized?)
|
||||
Memo bool // should the function be memoized? (false if too much output)
|
||||
Fast bool // is the function fast? (avoid speculative execution)
|
||||
Spec bool // can we speculatively execute it? (true for most)
|
||||
Output *types.Type // output value type (must not change over time!)
|
||||
Err error // did this fact validate?
|
||||
}
|
||||
|
||||
@@ -64,8 +64,10 @@ func (obj *FactFunc) Validate() error {
|
||||
// Info returns some static info about itself.
|
||||
func (obj *FactFunc) Info() *interfaces.Info {
|
||||
return &interfaces.Info{
|
||||
Pure: false,
|
||||
Memo: false,
|
||||
Pure: obj.Fact.Info().Pure,
|
||||
Memo: obj.Fact.Info().Memo,
|
||||
Fast: obj.Fact.Info().Fast,
|
||||
Spec: obj.Fact.Info().Spec,
|
||||
Sig: &types.Type{
|
||||
Kind: types.KindFunc,
|
||||
// if Ord or Map are nil, this will panic things!
|
||||
|
||||
@@ -40,26 +40,68 @@ import (
|
||||
|
||||
func init() {
|
||||
simple.ModuleRegister("golang/testpkg", "all_kind", &simple.Scaffold{
|
||||
// XXX: pull these from a database, remove the impure functions
|
||||
I: &simple.Info{
|
||||
Pure: true,
|
||||
Memo: true,
|
||||
Fast: true,
|
||||
Spec: true,
|
||||
},
|
||||
T: types.NewType("func(x int, y str) float"),
|
||||
F: TestpkgAllKind,
|
||||
})
|
||||
simple.ModuleRegister("golang/testpkg", "to_upper", &simple.Scaffold{
|
||||
// XXX: pull these from a database, remove the impure functions
|
||||
I: &simple.Info{
|
||||
Pure: true,
|
||||
Memo: true,
|
||||
Fast: true,
|
||||
Spec: true,
|
||||
},
|
||||
T: types.NewType("func(s str) str"),
|
||||
F: TestpkgToUpper,
|
||||
})
|
||||
simple.ModuleRegister("golang/testpkg", "max", &simple.Scaffold{
|
||||
// XXX: pull these from a database, remove the impure functions
|
||||
I: &simple.Info{
|
||||
Pure: true,
|
||||
Memo: true,
|
||||
Fast: true,
|
||||
Spec: true,
|
||||
},
|
||||
T: types.NewType("func(x float, y float) float"),
|
||||
F: TestpkgMax,
|
||||
})
|
||||
simple.ModuleRegister("golang/testpkg", "with_error", &simple.Scaffold{
|
||||
// XXX: pull these from a database, remove the impure functions
|
||||
I: &simple.Info{
|
||||
Pure: true,
|
||||
Memo: true,
|
||||
Fast: true,
|
||||
Spec: true,
|
||||
},
|
||||
T: types.NewType("func(s str) str"),
|
||||
F: TestpkgWithError,
|
||||
})
|
||||
simple.ModuleRegister("golang/testpkg", "with_int", &simple.Scaffold{
|
||||
// XXX: pull these from a database, remove the impure functions
|
||||
I: &simple.Info{
|
||||
Pure: true,
|
||||
Memo: true,
|
||||
Fast: true,
|
||||
Spec: true,
|
||||
},
|
||||
T: types.NewType("func(s float, i int, x int, j int, k int, b bool, t str) str"),
|
||||
F: TestpkgWithInt,
|
||||
})
|
||||
simple.ModuleRegister("golang/testpkg", "super_byte", &simple.Scaffold{
|
||||
// XXX: pull these from a database, remove the impure functions
|
||||
I: &simple.Info{
|
||||
Pure: true,
|
||||
Memo: true,
|
||||
Fast: true,
|
||||
Spec: true,
|
||||
},
|
||||
T: types.NewType("func(s str, t str) str"),
|
||||
F: TestpkgSuperByte,
|
||||
})
|
||||
|
||||
@@ -40,6 +40,13 @@ import (
|
||||
|
||||
func init() {
|
||||
{{ range $i, $func := .Functions }} simple.ModuleRegister("{{$func.MgmtPackage}}", "{{$func.MclName}}", &simple.Scaffold{
|
||||
// XXX: pull these from a database, remove the impure functions
|
||||
I: &simple.Info{
|
||||
Pure: true,
|
||||
Memo: true,
|
||||
Fast: true,
|
||||
Spec: true,
|
||||
},
|
||||
T: types.NewType("{{$func.Signature}}"),
|
||||
F: {{$func.InternalName}},
|
||||
})
|
||||
|
||||
@@ -38,6 +38,7 @@ import (
|
||||
|
||||
docsUtil "github.com/purpleidea/mgmt/docs/util"
|
||||
"github.com/purpleidea/mgmt/lang/interfaces"
|
||||
"github.com/purpleidea/mgmt/util"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -113,6 +114,15 @@ const (
|
||||
// CollectFuncOutType is the expected return type, the data field is an
|
||||
// encoded resource blob.
|
||||
CollectFuncOutType = "[]" + CollectFuncOutStruct
|
||||
|
||||
// ErrCantSpeculate is an error that explains that we can't speculate
|
||||
// when trying to Call a function. This often gets called by the Value()
|
||||
// method of the Expr. This can be useful if we want to distinguish
|
||||
// between "something is broken" and "I just can't produce a value at
|
||||
// this time", which can be identified and skipped over. If it's the
|
||||
// former, then it's okay to error early and shut everything down since
|
||||
// we know this function is never going to work the way it's called.
|
||||
ErrCantSpeculate = util.Error("can't speculate")
|
||||
)
|
||||
|
||||
// registeredFuncs is a global map of all possible funcs which can be used. You
|
||||
|
||||
@@ -45,9 +45,20 @@ import (
|
||||
// RegisteredFuncs maps a function name to the corresponding function scaffold.
|
||||
var RegisteredFuncs = make(map[string]*Scaffold) // must initialize
|
||||
|
||||
// Info holds some information about this function.
|
||||
type Info struct {
|
||||
Pure bool // is the function pure? (can it be memoized?)
|
||||
Memo bool // should the function be memoized? (false if too much output)
|
||||
Fast bool // is the function slow? (avoid speculative execution)
|
||||
Spec bool // can we speculatively execute it? (true for most)
|
||||
}
|
||||
|
||||
// Scaffold holds the necessary data to build a (possibly polymorphic) function
|
||||
// with this API.
|
||||
type Scaffold struct {
|
||||
// I is some general info about the function.
|
||||
I *Info
|
||||
|
||||
// T is the type of the function. It can include unification variables.
|
||||
// At a minimum, this must be a `func(?1) ?2` as a naked `?1` is not
|
||||
// allowed. (TODO: Because of ArgGen.)
|
||||
@@ -102,12 +113,23 @@ func Register(name string, scaffold *Scaffold) {
|
||||
panic(fmt.Sprintf("could not locate function filename for %s", name))
|
||||
}
|
||||
|
||||
funcInfo := &wrapped.Info{}
|
||||
if scaffold.I != nil {
|
||||
funcInfo = &wrapped.Info{
|
||||
Pure: scaffold.I.Pure,
|
||||
Memo: scaffold.I.Memo,
|
||||
Fast: scaffold.I.Fast,
|
||||
Spec: scaffold.I.Spec,
|
||||
}
|
||||
}
|
||||
|
||||
// register a copy in the main function database
|
||||
funcs.Register(name, func() interfaces.Func {
|
||||
return &Func{
|
||||
Metadata: metadata,
|
||||
WrappedFunc: &wrapped.Func{
|
||||
Name: name,
|
||||
Name: name,
|
||||
FuncInfo: funcInfo,
|
||||
// NOTE: It might be more correct to Copy here,
|
||||
// but we do the copy inside of ExprFunc.Copy()
|
||||
// instead, so that the same type can be unified
|
||||
|
||||
@@ -56,7 +56,14 @@ const (
|
||||
)
|
||||
|
||||
func init() {
|
||||
info := &simple.Info{
|
||||
Pure: true,
|
||||
Memo: true,
|
||||
Fast: true,
|
||||
Spec: true,
|
||||
}
|
||||
RegisterOperator("+", &simple.Scaffold{
|
||||
I: info,
|
||||
T: types.NewType("func(?1, ?1) ?1"),
|
||||
C: simple.TypeMatch([]string{
|
||||
"func(str, str) str", // concatenation
|
||||
@@ -91,6 +98,7 @@ func init() {
|
||||
})
|
||||
|
||||
RegisterOperator("-", &simple.Scaffold{
|
||||
I: info,
|
||||
T: types.NewType("func(?1, ?1) ?1"),
|
||||
C: simple.TypeMatch([]string{
|
||||
"func(int, int) int", // subtraction
|
||||
@@ -115,6 +123,7 @@ func init() {
|
||||
})
|
||||
|
||||
RegisterOperator("*", &simple.Scaffold{
|
||||
I: info,
|
||||
T: types.NewType("func(?1, ?1) ?1"),
|
||||
C: simple.TypeMatch([]string{
|
||||
"func(int, int) int", // multiplication
|
||||
@@ -141,6 +150,7 @@ func init() {
|
||||
|
||||
// don't add: `func(int, float) float` or: `func(float, int) float`
|
||||
RegisterOperator("/", &simple.Scaffold{
|
||||
I: info,
|
||||
T: types.NewType("func(?1, ?1) float"),
|
||||
C: simple.TypeMatch([]string{
|
||||
"func(int, int) float", // division
|
||||
@@ -173,6 +183,7 @@ func init() {
|
||||
})
|
||||
|
||||
RegisterOperator("==", &simple.Scaffold{
|
||||
I: info,
|
||||
T: types.NewType("func(?1, ?1) bool"),
|
||||
C: func(typ *types.Type) error {
|
||||
//if typ == nil { // happens within iter
|
||||
@@ -218,6 +229,7 @@ func init() {
|
||||
})
|
||||
|
||||
RegisterOperator("!=", &simple.Scaffold{
|
||||
I: info,
|
||||
T: types.NewType("func(?1, ?1) bool"),
|
||||
C: func(typ *types.Type) error {
|
||||
//if typ == nil { // happens within iter
|
||||
@@ -263,6 +275,7 @@ func init() {
|
||||
})
|
||||
|
||||
RegisterOperator("<", &simple.Scaffold{
|
||||
I: info,
|
||||
T: types.NewType("func(?1, ?1) bool"),
|
||||
C: simple.TypeMatch([]string{
|
||||
"func(int, int) bool", // less-than
|
||||
@@ -288,6 +301,7 @@ func init() {
|
||||
})
|
||||
|
||||
RegisterOperator(">", &simple.Scaffold{
|
||||
I: info,
|
||||
T: types.NewType("func(?1, ?1) bool"),
|
||||
C: simple.TypeMatch([]string{
|
||||
"func(int, int) bool", // greater-than
|
||||
@@ -313,6 +327,7 @@ func init() {
|
||||
})
|
||||
|
||||
RegisterOperator("<=", &simple.Scaffold{
|
||||
I: info,
|
||||
T: types.NewType("func(?1, ?1) bool"),
|
||||
C: simple.TypeMatch([]string{
|
||||
"func(int, int) bool", // less-than-equal
|
||||
@@ -338,6 +353,7 @@ func init() {
|
||||
})
|
||||
|
||||
RegisterOperator(">=", &simple.Scaffold{
|
||||
I: info,
|
||||
T: types.NewType("func(?1, ?1) bool"),
|
||||
C: simple.TypeMatch([]string{
|
||||
"func(int, int) bool", // greater-than-equal
|
||||
@@ -366,6 +382,7 @@ func init() {
|
||||
// TODO: is there a way for the engine to have
|
||||
// short-circuit operators, and does it matter?
|
||||
RegisterOperator("and", &simple.Scaffold{
|
||||
I: info,
|
||||
T: types.NewType("func(bool, bool) bool"),
|
||||
F: func(ctx context.Context, input []types.Value) (types.Value, error) {
|
||||
return &types.BoolValue{
|
||||
@@ -376,6 +393,7 @@ func init() {
|
||||
|
||||
// logical or
|
||||
RegisterOperator("or", &simple.Scaffold{
|
||||
I: info,
|
||||
T: types.NewType("func(bool, bool) bool"),
|
||||
F: func(ctx context.Context, input []types.Value) (types.Value, error) {
|
||||
return &types.BoolValue{
|
||||
@@ -386,6 +404,7 @@ func init() {
|
||||
|
||||
// logical not (unary operator)
|
||||
RegisterOperator("not", &simple.Scaffold{
|
||||
I: info,
|
||||
T: types.NewType("func(bool) bool"),
|
||||
F: func(ctx context.Context, input []types.Value) (types.Value, error) {
|
||||
return &types.BoolValue{
|
||||
@@ -396,6 +415,7 @@ func init() {
|
||||
|
||||
// pi operator (this is an easter egg to demo a zero arg operator)
|
||||
RegisterOperator("π", &simple.Scaffold{
|
||||
I: info,
|
||||
T: types.NewType("func() float"),
|
||||
F: func(ctx context.Context, input []types.Value) (types.Value, error) {
|
||||
return &types.FloatValue{
|
||||
@@ -645,8 +665,11 @@ func (obj *OperatorFunc) Info() *interfaces.Info {
|
||||
// avoid an accidental return of unification variables when we should be
|
||||
// getting them from FuncInfer, and not from here. (During unification!)
|
||||
return &interfaces.Info{
|
||||
// XXX: get these from the scaffold
|
||||
Pure: true,
|
||||
Memo: false,
|
||||
Memo: true,
|
||||
Fast: true,
|
||||
Spec: true,
|
||||
Sig: obj.Type, // func kind, which includes operator arg as input
|
||||
Err: obj.Validate(),
|
||||
}
|
||||
|
||||
@@ -54,9 +54,20 @@ const (
|
||||
// RegisteredFuncs maps a function name to the corresponding function scaffold.
|
||||
var RegisteredFuncs = make(map[string]*Scaffold) // must initialize
|
||||
|
||||
// Info holds some information about this function.
|
||||
type Info struct {
|
||||
Pure bool // is the function pure? (can it be memoized?)
|
||||
Memo bool // should the function be memoized? (false if too much output)
|
||||
Fast bool // is the function slow? (avoid speculative execution)
|
||||
Spec bool // can we speculatively execute it? (true for most)
|
||||
}
|
||||
|
||||
// Scaffold holds the necessary data to build a (possibly polymorphic) function
|
||||
// with this API.
|
||||
type Scaffold struct {
|
||||
// I is some general info about the function.
|
||||
I *Info
|
||||
|
||||
// T is the type of the function. It can include unification variables.
|
||||
// At a minimum, this must be a `func(?1) ?2` as a naked `?1` is not
|
||||
// allowed. (TODO: Because of ArgGen.)
|
||||
@@ -124,12 +135,23 @@ func Register(name string, scaffold *Scaffold) {
|
||||
panic(fmt.Sprintf("could not locate function filename for %s", name))
|
||||
}
|
||||
|
||||
funcInfo := &wrapped.Info{}
|
||||
if scaffold.I != nil {
|
||||
funcInfo = &wrapped.Info{
|
||||
Pure: scaffold.I.Pure,
|
||||
Memo: scaffold.I.Memo,
|
||||
Fast: scaffold.I.Fast,
|
||||
Spec: scaffold.I.Spec,
|
||||
}
|
||||
}
|
||||
|
||||
// register a copy in the main function database
|
||||
funcs.Register(name, func() interfaces.Func {
|
||||
return &Func{
|
||||
Metadata: metadata,
|
||||
WrappedFunc: &wrapped.Func{
|
||||
Name: name,
|
||||
Name: name,
|
||||
FuncInfo: funcInfo,
|
||||
// NOTE: It might be more correct to Copy here,
|
||||
// but we do the copy inside of ExprFunc.Copy()
|
||||
// instead, so that the same type can be unified
|
||||
|
||||
@@ -223,7 +223,7 @@ func (obj *CallFunc) replaceSubGraph(newFuncValue *full.FuncValue) error {
|
||||
// methods called on it. Nothing else. It will _not_ call Commit or
|
||||
// Reverse. It adds to the graph, and our Commit and Reverse operations
|
||||
// are the ones that actually make the change.
|
||||
outputFunc, err := newFuncValue.Call(obj.init.Txn, obj.ArgVertices)
|
||||
outputFunc, err := newFuncValue.CallWithFuncs(obj.init.Txn, obj.ArgVertices)
|
||||
if err != nil {
|
||||
return errwrap.Wrapf(err, "could not call newFuncValue.Call()")
|
||||
}
|
||||
|
||||
@@ -74,6 +74,7 @@ func SimpleFnToFuncValue(name string, fv *types.FuncValue) *full.FuncValue {
|
||||
}
|
||||
return wrappedFunc, nil
|
||||
},
|
||||
F: nil, // unused
|
||||
T: fv.T,
|
||||
}
|
||||
}
|
||||
@@ -84,3 +85,42 @@ func SimpleFnToFuncValue(name string, fv *types.FuncValue) *full.FuncValue {
|
||||
func SimpleFnToConstFunc(name string, fv *types.FuncValue) interfaces.Func {
|
||||
return FuncValueToConstFunc(SimpleFnToFuncValue(name, fv))
|
||||
}
|
||||
|
||||
// FuncToFullFuncValue creates a *full.FuncValue which adds the given
|
||||
// interfaces.Func to the graph. Note that this means the *full.FuncValue can
|
||||
// only be called once.
|
||||
func FuncToFullFuncValue(makeFunc func() interfaces.Func, typ *types.Type) *full.FuncValue {
|
||||
|
||||
v := func(txn interfaces.Txn, args []interfaces.Func) (interfaces.Func, error) {
|
||||
valueTransformingFunc := makeFunc() // do this once here
|
||||
buildableFunc, ok := valueTransformingFunc.(interfaces.BuildableFunc)
|
||||
if ok {
|
||||
// Set the type in case it's not already done.
|
||||
if _, err := buildableFunc.Build(typ); err != nil {
|
||||
// programming error?
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
for i, arg := range args {
|
||||
argName := typ.Ord[i]
|
||||
txn.AddEdge(arg, valueTransformingFunc, &interfaces.FuncEdge{
|
||||
Args: []string{argName},
|
||||
})
|
||||
}
|
||||
return valueTransformingFunc, nil
|
||||
}
|
||||
|
||||
var f interfaces.FuncSig
|
||||
callableFunc, ok := makeFunc().(interfaces.CallableFunc)
|
||||
if ok {
|
||||
f = callableFunc.Call
|
||||
}
|
||||
|
||||
// This has the "V" implementation and the simpler "F" implementation
|
||||
// which can occasionally be used if the interfaces.Func supports that!
|
||||
return &full.FuncValue{
|
||||
V: v,
|
||||
F: f,
|
||||
T: typ,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,6 +39,14 @@ import (
|
||||
|
||||
var _ interfaces.Func = &Func{} // ensure it meets this expectation
|
||||
|
||||
// Info holds some information about this function.
|
||||
type Info struct {
|
||||
Pure bool // is the function pure? (can it be memoized?)
|
||||
Memo bool // should the function be memoized? (false if too much output)
|
||||
Fast bool // is the function slow? (avoid speculative execution)
|
||||
Spec bool // can we speculatively execute it? (true for most)
|
||||
}
|
||||
|
||||
// Func is a wrapped scaffolding function struct which fulfills the boiler-plate
|
||||
// for the function API, but that can run a very simple, static, pure, function.
|
||||
// It can be wrapped by other structs that support polymorphism in various ways.
|
||||
@@ -48,6 +56,9 @@ type Func struct {
|
||||
// Name is a unique string name for the function.
|
||||
Name string
|
||||
|
||||
// Info is some general info about the function.
|
||||
FuncInfo *Info
|
||||
|
||||
// Type is the type of the function. It can include unification
|
||||
// variables when this struct is wrapped in one that can build this out.
|
||||
Type *types.Type
|
||||
@@ -64,7 +75,16 @@ type Func struct {
|
||||
// String returns a simple name for this function. This is needed so this struct
|
||||
// can satisfy the pgraph.Vertex interface.
|
||||
func (obj *Func) String() string {
|
||||
return fmt.Sprintf("%s @ %p", obj.Name, obj) // be more unique!
|
||||
//if obj.Fn != nil { // TODO: would this work and be useful?
|
||||
// return fmt.Sprintf("%s: %s", obj.Name, obj.Fn)
|
||||
//}
|
||||
//if obj.Type != nil { // TODO: would this work and be useful?
|
||||
// return fmt.Sprintf("%s: %s", obj.Name, obj.Type)
|
||||
//}
|
||||
if obj.Name == "" {
|
||||
return "<wrapped>"
|
||||
}
|
||||
return obj.Name
|
||||
}
|
||||
|
||||
// ArgGen returns the Nth arg name for this function.
|
||||
@@ -104,12 +124,21 @@ func (obj *Func) Info() *interfaces.Info {
|
||||
typ = obj.Fn.Type()
|
||||
}
|
||||
|
||||
return &interfaces.Info{
|
||||
Pure: true,
|
||||
Memo: false, // TODO: should this be something we specify here?
|
||||
info := &interfaces.Info{
|
||||
Pure: false,
|
||||
Memo: false,
|
||||
Fast: false,
|
||||
Spec: false,
|
||||
Sig: typ,
|
||||
Err: obj.Validate(),
|
||||
}
|
||||
if fi := obj.FuncInfo; fi != nil {
|
||||
info.Pure = fi.Pure
|
||||
info.Memo = fi.Memo
|
||||
info.Fast = fi.Fast
|
||||
info.Spec = fi.Spec
|
||||
}
|
||||
return info
|
||||
}
|
||||
|
||||
// Init runs some startup code for this function.
|
||||
@@ -184,5 +213,9 @@ func (obj *Func) Stream(ctx context.Context) error {
|
||||
// Call this function with the input args and return the value if it is possible
|
||||
// to do so at this time.
|
||||
func (obj *Func) Call(ctx context.Context, args []types.Value) (types.Value, error) {
|
||||
if obj.Fn == nil {
|
||||
// happens with speculative graph shape code paths
|
||||
return nil, fmt.Errorf("nil function")
|
||||
}
|
||||
return obj.Fn.Call(ctx, args) // (Value, error)
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user