diff --git a/examples/longpoll/redirect-client.go b/examples/longpoll/redirect-client.go
deleted file mode 100644
index 112347d38a..0000000000
--- a/examples/longpoll/redirect-client.go
+++ /dev/null
@@ -1,53 +0,0 @@
-// This is an example longpoll client. The connection to the corresponding
-// server initiates a request on a "Watch". It then waits until a redirect is
-// received from the server which indicates that the watch is ready. To signal
-// than an event on this watch has occurred, the server sends a final message.
-package main
-
-import (
- "bytes"
- "fmt"
- "io/ioutil"
- "log"
- "math/rand"
- "net/http"
- "time"
-)
-
-const (
- timeout = 15
-)
-
-func main() {
- log.Printf("Starting...")
-
- checkRedirectFunc := func(req *http.Request, via []*http.Request) error {
- log.Printf("Watch is ready!")
- return nil
- }
-
- client := &http.Client{
- Timeout: time.Duration(timeout) * time.Second,
- CheckRedirect: checkRedirectFunc,
- }
-
- id := rand.Intn(2 ^ 32 - 1)
- body := bytes.NewBufferString("hello")
- url := fmt.Sprintf("http://127.0.0.1:12345/watch?id=%d", id)
- req, err := http.NewRequest("GET", url, body)
- if err != nil {
- log.Printf("err: %+v", err)
- return
- }
- result, err := client.Do(req)
- if err != nil {
- log.Printf("err: %+v", err)
- return
- }
- log.Printf("Event received: %+v", result)
-
- s, err := ioutil.ReadAll(result.Body) // TODO: apparently we can stream
- result.Body.Close()
- log.Printf("Response: %+v", string(s))
- log.Printf("Error: %+v", err)
-}
diff --git a/examples/longpoll/redirect-server.go b/examples/longpoll/redirect-server.go
deleted file mode 100644
index 0b9d811262..0000000000
--- a/examples/longpoll/redirect-server.go
+++ /dev/null
@@ -1,56 +0,0 @@
-// This is an example longpoll server. On client connection it starts a "Watch",
-// and notifies the client with a redirect when that watch is ready. This is
-// important to avoid a possible race between when the client believes the watch
-// is actually ready, and when the server actually is watching.
-package main
-
-import (
- "fmt"
- "io"
- "log"
- "math/rand"
- "net/http"
- "time"
-)
-
-// you can use `wget http://127.0.0.1:12345/hello -O /dev/null` or you can run
-// `go run client.go`
-const (
- addr = ":12345"
-)
-
-// WatchStart kicks off the initial watch and then redirects the client to
-// notify them that we're ready. The watch operation here is simulated.
-func WatchStart(w http.ResponseWriter, req *http.Request) {
- log.Printf("Start received...")
- time.Sleep(time.Duration(5) * time.Second) // 5 seconds to get ready and start *our* watch ;)
- //started := time.Now().UnixNano() // time since watch is "started"
- log.Printf("URL: %+v", req.URL)
-
- token := fmt.Sprintf("%d", rand.Intn(2^32-1))
- http.Redirect(w, req, fmt.Sprintf("/ready?token=%s", token), http.StatusSeeOther) // TODO: which code should we use ?
- log.Printf("Redirect sent!")
-}
-
-// WatchReady receives the client connection when it has been notified that the
-// watch has started, and it returns to signal that an event on the watch
-// occurred. The event operation here is simulated.
-func WatchReady(w http.ResponseWriter, req *http.Request) {
- log.Printf("Ready received")
- log.Printf("URL: %+v", req.URL)
-
- //time.Sleep(time.Duration(10) * time.Second)
- time.Sleep(time.Duration(rand.Intn(10)) * time.Second) // wait until an "event" happens
-
- io.WriteString(w, "Event happened!\n")
- log.Printf("Event sent")
-}
-
-func main() {
- log.Printf("Starting...")
- //rand.Seed(time.Now().UTC().UnixNano())
- http.HandleFunc("/watch", WatchStart)
- http.HandleFunc("/ready", WatchReady)
- log.Printf("Listening on %s", addr)
- log.Fatal(http.ListenAndServe(addr, nil))
-}
diff --git a/lang/ast/structs.go b/lang/ast/structs.go
index b4e8b8c09f..852ff02557 100644
--- a/lang/ast/structs.go
+++ b/lang/ast/structs.go
@@ -29,9 +29,10 @@ import (
"github.com/purpleidea/mgmt/engine"
engineUtil "github.com/purpleidea/mgmt/engine/util"
+ "github.com/purpleidea/mgmt/lang/fancyfunc"
"github.com/purpleidea/mgmt/lang/funcs"
"github.com/purpleidea/mgmt/lang/funcs/core"
- "github.com/purpleidea/mgmt/lang/funcs/structs"
+ "github.com/purpleidea/mgmt/lang/funcs/simple"
"github.com/purpleidea/mgmt/lang/inputs"
"github.com/purpleidea/mgmt/lang/interfaces"
"github.com/purpleidea/mgmt/lang/types"
@@ -268,6 +269,11 @@ func (obj *StmtBind) Graph(map[string]interfaces.Func) (*pgraph.Graph, error) {
return pgraph.NewGraph("stmtbind") // empty graph
}
+// MergedGraph returns the graph and func together in one call.
+func (obj *StmtBind) MergedGraph(env map[string]interfaces.Func) (*pgraph.Graph, error) {
+ return obj.Graph() // does nothing
+}
+
// Output for the bind statement produces no output. Any values of interest come
// from the use of the var which this binds the expression to.
func (obj *StmtBind) Output(map[interfaces.Func]types.Value) (*interfaces.Output, error) {
@@ -620,6 +626,64 @@ func (obj *StmtRes) Graph(env map[string]interfaces.Func) (*pgraph.Graph, error)
return graph, nil
}
+// MergedGraph returns the graph and func together in one call.
+func (obj *StmtRes) MergedGraph(env map[string]interfaces.Func) (*pgraph.Graph, error) {
+ metaNames := make(map[string]struct{})
+ for _, x := range obj.Contents {
+ line, ok := x.(*StmtResMeta)
+ if !ok {
+ continue
+ }
+
+ properties := []string{line.Property} // "noop" or "Meta" or...
+ if line.Property == MetaField {
+ // If this is the generic MetaField struct field, then
+ // we lookup the type signature to see which fields are
+ // defined. You're allowed to have more than one Meta
+ // field, but they can't contain the same field twice.
+
+ typ, err := line.MetaExpr.Type() // must be known now
+ if err != nil {
+ // programming error in type unification
+ return nil, errwrap.Wrapf(err, "unknown resource meta type")
+ }
+ if t := typ.Kind; t != types.KindStruct {
+ return nil, fmt.Errorf("unexpected resource meta kind of: %s", t)
+ }
+ properties = typ.Ord // list of field names in this struct
+ }
+
+ for _, property := range properties {
+ // Was the meta entry already seen in this resource?
+ if _, exists := metaNames[property]; exists {
+ return nil, fmt.Errorf("resource has duplicate meta entry of: %s", property)
+ }
+ metaNames[property] = struct{}{}
+ }
+ }
+
+ graph, err := pgraph.NewGraph("res")
+ if err != nil {
+ return nil, errwrap.Wrapf(err, "could not create graph")
+ }
+
+ g, _, err := obj.Name.MergedGraph(env)
+ if err != nil {
+ return nil, err
+ }
+ graph.AddGraph(g)
+
+ for _, x := range obj.Contents {
+ g, err := x.MergedGraph(env)
+ if err != nil {
+ return nil, err
+ }
+ graph.AddGraph(g)
+ }
+
+ return graph, nil
+}
+
// Output returns the output that this "program" produces. This output is what
// is used to build the output graph. This only exists for statements. The
// analogous function for expressions is Value. Those Value functions might get
@@ -1359,6 +1423,30 @@ func (obj *StmtResField) Graph(env map[string]interfaces.Func) (*pgraph.Graph, e
return graph, nil
}
+// MergedGraph returns the graph and func together in one call.
+func (obj *StmtResField) MergedGraph(env map[string]interfaces.Func) (*pgraph.Graph, error) {
+ graph, err := pgraph.NewGraph("resfield")
+ if err != nil {
+ return nil, errwrap.Wrapf(err, "could not create graph")
+ }
+
+ g, _, err := obj.Value.MergedGraph(env)
+ if err != nil {
+ return nil, err
+ }
+ graph.AddGraph(g)
+
+ if obj.Condition != nil {
+ g, _, err := obj.Condition.MergedGraph(env)
+ if err != nil {
+ return nil, err
+ }
+ graph.AddGraph(g)
+ }
+
+ return graph, nil
+}
+
// StmtResEdge represents a single edge property in the parsed resource
// representation. This does not satisfy the Stmt interface.
type StmtResEdge struct {
@@ -1594,6 +1682,30 @@ func (obj *StmtResEdge) Graph(env map[string]interfaces.Func) (*pgraph.Graph, er
return graph, nil
}
+// MergedGraph returns the graph and func together in one call.
+func (obj *StmtResEdge) MergedGraph(env map[string]interfaces.Func) (*pgraph.Graph, error) {
+ graph, err := pgraph.NewGraph("resedge")
+ if err != nil {
+ return nil, errwrap.Wrapf(err, "could not create graph")
+ }
+
+ g, err := obj.EdgeHalf.MergedGraph(env)
+ if err != nil {
+ return nil, err
+ }
+ graph.AddGraph(g)
+
+ if obj.Condition != nil {
+ g, _, err := obj.Condition.MergedGraph(env)
+ if err != nil {
+ return nil, err
+ }
+ graph.AddGraph(g)
+ }
+
+ return graph, nil
+}
+
// StmtResMeta represents a single meta value in the parsed resource
// representation. It can also contain a struct that contains one or more meta
// parameters. If it contains such a struct, then the `Property` field contains
@@ -1942,6 +2054,30 @@ func (obj *StmtResMeta) Graph(env map[string]interfaces.Func) (*pgraph.Graph, er
return graph, nil
}
+// MergedGraph returns the graph and func together in one call.
+func (obj *StmtResMeta) MergedGraph(env map[string]interfaces.Func) (*pgraph.Graph, error) {
+ graph, err := pgraph.NewGraph("resmeta")
+ if err != nil {
+ return nil, errwrap.Wrapf(err, "could not create graph")
+ }
+
+ g, _, err := obj.MetaExpr.MergedGraph(env)
+ if err != nil {
+ return nil, err
+ }
+ graph.AddGraph(g)
+
+ if obj.Condition != nil {
+ g, _, err := obj.Condition.MergedGraph(env)
+ if err != nil {
+ return nil, err
+ }
+ graph.AddGraph(g)
+ }
+
+ return graph, nil
+}
+
// StmtEdge is a representation of a dependency. It also supports send/recv.
// Edges represents that the first resource (Kind/Name) listed in the
// EdgeHalfList should happen in the resource graph *before* the next resource
@@ -2192,6 +2328,24 @@ func (obj *StmtEdge) Graph(env map[string]interfaces.Func) (*pgraph.Graph, error
return graph, nil
}
+// MergedGraph returns the graph and func together in one call.
+func (obj *StmtEdge) MergedGraph(env map[string]interfaces.Func) (*pgraph.Graph, error) {
+ graph, err := pgraph.NewGraph("edge")
+ if err != nil {
+ return nil, errwrap.Wrapf(err, "could not create graph")
+ }
+
+ for _, x := range obj.EdgeHalfList {
+ g, err := x.MergedGraph(env)
+ if err != nil {
+ return nil, err
+ }
+ graph.AddGraph(g)
+ }
+
+ return graph, nil
+}
+
// Output returns the output that this "program" produces. This output is what
// is used to build the output graph. This only exists for statements. The
// analogous function for expressions is Value. Those Value functions might get
@@ -2426,6 +2580,15 @@ func (obj *StmtEdgeHalf) Graph(env map[string]interfaces.Func) (*pgraph.Graph, e
return g, nil
}
+// MergedGraph returns the graph and func together in one call.
+func (obj *StmtEdgeHalf) MergedGraph(env map[string]interfaces.Func) (*pgraph.Graph, error) {
+ g, _, err := obj.Name.MergedGraph(env)
+ if err != nil {
+ return nil, err
+ }
+ return g, nil
+}
+
// StmtIf represents an if condition that contains between one and two branches
// of statements to be executed based on the evaluation of the boolean condition
// over time. In particular, this is different from an ExprIf which returns a
@@ -2747,6 +2910,33 @@ func (obj *StmtIf) Graph(env map[string]interfaces.Func) (*pgraph.Graph, error)
return graph, nil
}
+// MergedGraph returns the graph and func together in one call.
+func (obj *StmtIf) MergedGraph(env map[string]interfaces.Func) (*pgraph.Graph, error) {
+ graph, err := pgraph.NewGraph("if")
+ if err != nil {
+ return nil, errwrap.Wrapf(err, "could not create graph")
+ }
+
+ g, _, err := obj.Condition.MergedGraph(env)
+ if err != nil {
+ return nil, err
+ }
+ graph.AddGraph(g)
+
+ for _, x := range []interfaces.Stmt{obj.ThenBranch, obj.ElseBranch} {
+ if x == nil {
+ continue
+ }
+ g, err := x.MergedGraph(env)
+ if err != nil {
+ return nil, err
+ }
+ graph.AddGraph(g)
+ }
+
+ return graph, nil
+}
+
// Output returns the output that this "program" produces. This output is what
// is used to build the output graph. This only exists for statements. The
// analogous function for expressions is Value. Those Value functions might get
@@ -2793,8 +2983,9 @@ func (obj *StmtIf) Output(table map[interfaces.Func]types.Value) (*interfaces.Ou
// the bind statement's are correctly applied in this scope, and irrespective of
// their order of definition.
type StmtProg struct {
- data *interfaces.Data
- scope *interfaces.Scope // store for use by imports
+ data *interfaces.Data
+ scope *interfaces.Scope // store for use by imports
+ importedVars map[string]interfaces.Expr // store for use by MergedGraph
// TODO: should this be a map? if so, how would we sort it to loop it?
importProgs []*StmtProg // list of child programs after running SetScope
@@ -3428,6 +3619,7 @@ func (obj *StmtProg) importScopeWithInputs(s string, scope *interfaces.Scope, pa
// args.
func (obj *StmtProg) SetScope(scope *interfaces.Scope) error {
newScope := scope.Copy()
+ obj.importedVars = make(map[string]interfaces.Expr)
// start by looking for any `import` statements to pull into the scope!
// this will run child lexing/parsing, interpolation, and scope setting
@@ -3484,6 +3676,7 @@ func (obj *StmtProg) SetScope(scope *interfaces.Scope) error {
}
newVariables[newName] = imp.Name
newScope.Variables[newName] = x // merge
+ obj.importedVars[newName] = x
}
for name, x := range importedScope.Functions {
newName := alias + interfaces.ModuleSep + name
@@ -3830,6 +4023,113 @@ func (obj *StmtProg) Graph(env map[string]interfaces.Func) (*pgraph.Graph, error
return graph, nil
}
+// MergedGraph returns the graph and func together in one call.
+func (obj *StmtProg) MergedGraph(env map[string]interfaces.Func) (*pgraph.Graph, error) {
+ graph, err := pgraph.NewGraph("prog")
+ if err != nil {
+ return nil, errwrap.Wrapf(err, "could not create graph")
+ }
+
+ extendedEnv := make(map[string]interfaces.Func)
+ for k, v := range env {
+ extendedEnv[k] = v
+ }
+
+ // add the imported variables to extendedEnv
+ for varName, v := range obj.importedVars {
+ g, importedFunc, err := v.MergedGraph(env) // XXX: pass in globals from scope?
+ if err != nil {
+ return nil, err
+ }
+ graph.AddGraph(g)
+ extendedEnv[varName] = importedFunc
+ }
+
+ // TODO: this could be called once at the top-level, and then cached...
+ // TODO: it currently gets called inside child programs, which is slow!
+ orderingGraph, _, err := obj.Ordering(nil) // XXX: pass in globals from scope?
+ // TODO: look at consumed variables, and prevent startup of unused ones?
+ if err != nil {
+ return nil, errwrap.Wrapf(err, "could not generate ordering")
+ }
+
+ nodeOrder, err := orderingGraph.TopologicalSort()
+ if err != nil {
+ return nil, errwrap.Wrapf(err, "recursive reference while running MergedGraph")
+ }
+
+ // XXX: implement ValidTopoSortOrder!
+ //topoSanity := (RequireTopologicalOrdering || TopologicalOrderingWarning)
+ //if topoSanity && !orderingGraph.ValidTopoSortOrder(nodeOrder) {
+ // msg := "code is out of order, you're insane!"
+ // if TopologicalOrderingWarning {
+ // obj.data.Logf(msg)
+ // if obj.data.Debug {
+ // // TODO: print out of order problems
+ // }
+ // }
+ // if RequireTopologicalOrdering {
+ // return fmt.Errorf(msg)
+ // }
+ //}
+
+ // TODO: move this function to a utility package
+ stmtInList := func(needle interfaces.Stmt, haystack []interfaces.Stmt) bool {
+ for _, x := range haystack {
+ if needle == x {
+ return true
+ }
+ }
+ return false
+ }
+
+ binds := []*StmtBind{}
+ for _, x := range nodeOrder { // these are in the correct order for MergedGraph
+ stmt, ok := x.(*StmtBind)
+ if !ok {
+ continue
+ }
+ if !stmtInList(stmt, obj.Body) {
+ // Skip any unwanted additions that we pulled in.
+ continue
+ }
+ binds = append(binds, stmt)
+ }
+
+ for _, v := range binds { // these are in the correct order for MergedGraph
+ g, boundFunc, err := v.Value.MergedGraph(extendedEnv)
+ if err != nil {
+ return nil, err
+ }
+ graph.AddGraph(g)
+ extendedEnv[v.Ident] = boundFunc
+ }
+
+ // collect all graphs that need to be included
+ for _, x := range obj.Body {
+ // skip over *StmtClass here
+ if _, ok := x.(*StmtClass); ok {
+ continue
+ }
+ // skip over StmtFunc, even though it doesn't produce anything!
+ if _, ok := x.(*StmtFunc); ok {
+ continue
+ }
+ // skip over StmtBind, even though it doesn't produce anything!
+ if _, ok := x.(*StmtBind); ok {
+ continue
+ }
+
+ g, err := x.MergedGraph(extendedEnv)
+ if err != nil {
+ return nil, err
+ }
+ graph.AddGraph(g)
+ }
+
+ return graph, nil
+}
+
// Output returns the output that this "program" produces. This output is what
// is used to build the output graph. This only exists for statements. The
// analogous function for expressions is Value. Those Value functions might get
@@ -4048,6 +4348,11 @@ func (obj *StmtFunc) Graph(map[string]interfaces.Func) (*pgraph.Graph, error) {
return pgraph.NewGraph("stmtfunc") // do this in ExprCall instead
}
+// MergedGraph returns the graph and func together in one call.
+func (obj *StmtFunc) MergedGraph(env map[string]interfaces.Func) (*pgraph.Graph, error) {
+ return obj.Graph() // does nothing
+}
+
// Output for the func statement produces no output. Any values of interest come
// from the use of the func which this binds the function to.
func (obj *StmtFunc) Output(map[interfaces.Func]types.Value) (*interfaces.Output, error) {
@@ -4218,6 +4523,11 @@ func (obj *StmtClass) Graph(env map[string]interfaces.Func) (*pgraph.Graph, erro
return obj.Body.Graph(env)
}
+// MergedGraph returns the graph and func together in one call.
+func (obj *StmtClass) MergedGraph(env map[string]interfaces.Func) (*pgraph.Graph, error) {
+ return obj.Body.MergedGraph(env)
+}
+
// Output for the class statement produces no output. Any values of interest
// come from the use of the include which this binds the statements to. This is
// usually called from the parent in StmtProg, but it skips running it so that
@@ -4562,6 +4872,23 @@ func (obj *StmtInclude) Graph(env map[string]interfaces.Func) (*pgraph.Graph, er
return graph, nil
}
+// MergedGraph returns the graph and func together in one call.
+func (obj *StmtInclude) MergedGraph(env map[string]interfaces.Func) (*pgraph.Graph, error) {
+ graph, err := pgraph.NewGraph("include")
+ if err != nil {
+ return nil, errwrap.Wrapf(err, "could not create graph")
+ }
+
+ // XXX: add the included vars to the env
+ g, err := obj.class.MergedGraph(env)
+ if err != nil {
+ return nil, err
+ }
+ graph.AddGraph(g)
+
+ return graph, nil
+}
+
// Output returns the output that this include produces. This output is what is
// used to build the output graph. This only exists for statements. The
// analogous function for expressions is Value. Those Value functions might get
@@ -4652,6 +4979,11 @@ func (obj *StmtImport) Graph(map[string]interfaces.Func) (*pgraph.Graph, error)
return pgraph.NewGraph("import") // empty graph
}
+// MergedGraph returns the graph and func together in one call.
+func (obj *StmtImport) MergedGraph(env map[string]interfaces.Func) (*pgraph.Graph, error) {
+ return obj.Graph() // does nothing
+}
+
// Output returns the output that this include produces. This output is what is
// used to build the output graph. This only exists for statements. The
// analogous function for expressions is Value. Those Value functions might get
@@ -4742,6 +5074,11 @@ func (obj *StmtComment) Graph(map[string]interfaces.Func) (*pgraph.Graph, error)
return graph, nil
}
+// MergedGraph returns the graph and func together in one call.
+func (obj *StmtComment) MergedGraph(env map[string]interfaces.Func) (*pgraph.Graph, error) {
+ return obj.Graph() // does nothing
+}
+
// Output for the comment statement produces no output.
func (obj *StmtComment) Output(map[interfaces.Func]types.Value) (*interfaces.Output, error) {
return interfaces.EmptyOutput(), nil
@@ -4832,7 +5169,7 @@ func (obj *ExprBool) Unify() ([]interfaces.Invariant, error) {
// Func returns the reactive stream of values that this expression produces.
func (obj *ExprBool) Func() (interfaces.Func, error) {
- return &structs.ConstFunc{
+ return &funcs.ConstFunc{
Value: &types.BoolValue{V: obj.V},
}, nil
}
@@ -5012,7 +5349,7 @@ func (obj *ExprStr) Unify() ([]interfaces.Invariant, error) {
// Func returns the reactive stream of values that this expression produces.
func (obj *ExprStr) Func() (interfaces.Func, error) {
- return &structs.ConstFunc{
+ return &funcs.ConstFunc{
Value: &types.StrValue{V: obj.V},
}, nil
}
@@ -5142,7 +5479,7 @@ func (obj *ExprInt) Unify() ([]interfaces.Invariant, error) {
// Func returns the reactive stream of values that this expression produces.
func (obj *ExprInt) Func() (interfaces.Func, error) {
- return &structs.ConstFunc{
+ return &funcs.ConstFunc{
Value: &types.IntValue{V: obj.V},
}, nil
}
@@ -5274,7 +5611,7 @@ func (obj *ExprFloat) Unify() ([]interfaces.Invariant, error) {
// Func returns the reactive stream of values that this expression produces.
func (obj *ExprFloat) Func() (interfaces.Func, error) {
- return &structs.ConstFunc{
+ return &funcs.ConstFunc{
Value: &types.FloatValue{V: obj.V},
}, nil
}
@@ -5592,7 +5929,7 @@ func (obj *ExprList) Func() (interfaces.Func, error) {
}
// composite func (list, map, struct)
- return &structs.CompositeFunc{
+ return &CompositeFunc{
Type: typ,
Len: len(obj.Elements),
}, nil
@@ -6106,7 +6443,7 @@ func (obj *ExprMap) Func() (interfaces.Func, error) {
}
// composite func (list, map, struct)
- return &structs.CompositeFunc{
+ return &CompositeFunc{
Type: typ, // the key/val types are known via this type
Len: len(obj.KVs),
}, nil
@@ -6570,7 +6907,7 @@ func (obj *ExprStruct) Func() (interfaces.Func, error) {
}
// composite func (list, map, struct)
- return &structs.CompositeFunc{
+ return &CompositeFunc{
Type: typ,
}, nil
}
@@ -6724,17 +7061,14 @@ type ExprStructField struct {
}
// ExprFunc is a representation of a function value. This is not a function
-// call, that is represented by ExprCall. This can represent either the contents
-// of a StmtFunc, a lambda function, or a core system function. You may only use
-// one of the internal representations of a function to build this, if you use
-// more than one then the behaviour is not defined, and could conceivably panic.
-// The first possibility is to specify the function via the Args, Return, and
-// Body fields. This is used for native mcl code. The second possibility is to
-// specify the function via the Function field only. This is used for built-in
-// functions that implement the Func API. The third possibility is to specify a
-// list of function values via the Values field. This is used for built-in
-// functions that implement the simple function API or the simplepoly function
-// API and that aren't wrapped in the Func API. (This was the historical case.)
+// call, that is represented by ExprCall.
+//
+// There are several kinds of functions which can be represented:
+// 1. The contents of a StmtFunc (set Args, Return, and Body)
+// 2. A lambda function (also set Args, Return, and Body)
+// 3. A stateful built-in function (set Function)
+// 4. A pure built-in function (set Values to a singleton)
+// 5. A pure polymorphic built-in function (set Values to a list)
type ExprFunc struct {
data *interfaces.Data
scope *interfaces.Scope // store for referencing this later
@@ -6762,10 +7096,10 @@ type ExprFunc struct {
// Values represents a list of simple functions. This means this can be
// polymorphic if more than one was specified!
- Values []*types.FuncValue
+ Values []*types.SimpleFn
// XXX: is this necessary?
- V func([]types.Value) (types.Value, error)
+ //V func(interfaces.ReversibleTxn, []pgraph.Vertex) (pgraph.Vertex, error)
}
// String returns a short representation of this expression.
@@ -6903,7 +7237,6 @@ func (obj *ExprFunc) Interpolate() (interfaces.Expr, error) {
Function: obj.Function,
function: obj.function,
Values: obj.Values,
- V: obj.V,
}, nil
}
@@ -6967,7 +7300,6 @@ func (obj *ExprFunc) Copy() (interfaces.Expr, error) {
Function: obj.Function,
function: function,
Values: obj.Values, // XXX: do we need to force rebuild these?
- V: obj.V,
}, nil
}
@@ -7065,7 +7397,7 @@ func (obj *ExprFunc) SetScope(scope *interfaces.Scope) error {
// TODO: if interfaces.Func grows a SetScope method do it here
}
if len(obj.Values) > 0 {
- // TODO: if *types.FuncValue grows a SetScope method do it here
+ // TODO: if *types.SimpleFn grows a SetScope method do it here
}
return nil
@@ -7101,7 +7433,7 @@ func (obj *ExprFunc) SetType(typ *types.Type) error {
return errwrap.Wrapf(err, "could not build values func")
}
// TODO: build the function here for later use if that is wanted
- //fn := obj.Values[index].Copy().(*types.FuncValue)
+ //fn := obj.Values[index].Copy()
//fn.T = typ.Copy() // overwrites any contained "variant" type
}
@@ -7419,52 +7751,7 @@ func (obj *ExprFunc) Unify() ([]interfaces.Invariant, error) {
// need this indirection, because our returned function that actually runs also
// accepts the "body" of the function (an expr) as an input.
func (obj *ExprFunc) Func() (interfaces.Func, error) {
- typ, err := obj.Type()
- if err != nil {
- return nil, err
- }
-
- if obj.Body != nil {
- // TODO: i think this is unused
- //f, err := obj.Body.Func()
- //if err != nil {
- // return nil, err
- //}
-
- // direct func
- return &structs.FunctionFunc{
- Type: typ, // this is a KindFunc
- //Func: f,
- Edge: "body", // the edge name used above in Graph is this...
- }, nil
- }
-
- if obj.Function != nil {
- // XXX: is this correct?
- return &structs.FunctionFunc{
- Type: typ, // this is a KindFunc
- Func: obj.function, // pass it through
- Edge: "", // no edge, since nothing is incoming to the built-in
- }, nil
- }
-
- // third kind
- //if len(obj.Values) > 0
- index, err := langutil.FnMatch(typ, obj.Values)
- if err != nil {
- // programming error ?
- return nil, errwrap.Wrapf(err, "no valid function found")
- }
- // build
- // TODO: this could probably be done in SetType and cached in the struct
- fn := obj.Values[index].Copy().(*types.FuncValue)
- fn.T = typ.Copy() // overwrites any contained "variant" type
-
- return &structs.FunctionFunc{
- Type: typ, // this is a KindFunc
- Fn: fn, // pass it through
- Edge: "", // no edge, since nothing is incoming to the built-in
- }, nil
+ panic("Please use ExprFunc.Graph() instead") // XXX !!!
}
// Graph returns the reactive function graph which is expressed by this node. It
@@ -7474,59 +7761,92 @@ func (obj *ExprFunc) Func() (interfaces.Func, error) {
// that fulfill the Stmt interface do not produces vertices, where as their
// children might. This returns a graph with a single vertex (itself) in it.
func (obj *ExprFunc) Graph(env map[string]interfaces.Func) (*pgraph.Graph, interfaces.Func, error) {
- //panic("i suspect ExprFunc->Graph might need to be different somehow") // XXX !!!
- graph, err := pgraph.NewGraph("func")
- if err != nil {
- return nil, nil, err
- }
- function, err := obj.Func()
- if err != nil {
- return nil, nil, err
- }
- graph.AddVertex(function)
+ // This implementation produces a graph with a single node of in-degree zero
+ // which outputs a single FuncValue. The FuncValue is a closure, in that it holds both
+ // a lambda body and a captured environment. This environment, which we
+ // receive from the caller, gives information about the variables declared
+ // _outside_ of the lambda, at the time the lambda is returned.
+ //
+ // Each time the FuncValue is called, it produces a separate graph, the
+ // subgraph which computes the lambda's output value from the lambda's
+ // argument values. The nodes created for that subgraph have a shorter life
+ // span than the nodes in the captured environment.
+
+ //graph, err := pgraph.NewGraph("func")
+ //if err != nil {
+ // return nil, nil, err
+ //}
+ //function, err := obj.Func()
+ //if err != nil {
+ // return nil, nil, err
+ //}
+ //graph.AddVertex(function)
+ var fnFunc interfaces.Func
if obj.Body != nil {
- g, _, err := obj.Body.Graph(env)
- if err != nil {
- return nil, nil, err
- }
+ fnFunc = simple.FuncValueToConstFunc(&fancyfunc.FuncValue{
+ V: func(innerTxn *interfaces.ReversibleTxn, args []interfaces.Func) (interfaces.Func, error) {
+ // Extend the environment with the arguments.
+ extendedEnv := make(map[string]interfaces.Func)
+ for k, v := range env {
+ extendedEnv[k] = v
+ }
+ for i, arg := range obj.Args {
+ extendedEnv[arg.Name] = args[i]
+ }
- // We need to add this edge, because if this isn't linked, then
- // when we add an edge from this, then we'll get two because the
- // contents aren't linked.
- name := "body" // TODO: what should we name this?
- edge := &interfaces.FuncEdge{Args: []string{name}}
+ // 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")
+ }
- var once bool
- edgeGenFn := func(v1, v2 pgraph.Vertex) pgraph.Edge {
- if once {
- panic(fmt.Sprintf("edgeGenFn for func was called twice"))
- }
- once = true
- return edge
+ innerTxn.AddGraph(subgraph)
+
+ return bodyFunc, nil
+ },
+ T: obj.typ,
+ })
+ } else if obj.Function != nil {
+ fnFunc = obj.Function()
+ } else /* len(obj.Values) > 0 */ {
+ index, err := langutil.FnMatch(obj.typ, obj.Values)
+ if err != nil {
+ // programming error
+ return nil, nil, errwrap.Wrapf(err, "since type checking succeeded at this point, there should only be one match")
}
- graph.AddEdgeGraphVertexLight(g, function, edgeGenFn) // body -> func
- }
+ simpleFn := obj.Values[index]
+ simpleFn.T = obj.typ
- if obj.Function != nil { // no input args are needed, func is built-in.
- // TODO: is there anything to do ?
- }
- if len(obj.Values) > 0 { // no input args are needed, func is built-in.
- // TODO: is there anything to do ?
+ fnFunc = simple.SimpleFnToConstFunc(simpleFn)
}
- return graph, function, nil
+ outerGraph, err := pgraph.NewGraph("ExprFunc")
+ if err != nil {
+ return nil, nil, err
+ }
+ outerGraph.AddVertex(fnFunc)
+ return outerGraph, fnFunc, nil
}
// SetValue for a func expression is always populated statically, and does not
// ever receive any incoming values (no incoming edges) so this should never be
// called. It has been implemented for uniformity.
func (obj *ExprFunc) SetValue(value types.Value) error {
- if err := obj.typ.Cmp(value.Type()); err != nil {
- return err
- }
- // FIXME: is this part necessary?
- obj.V = value.Func()
+ // We don't need to do anything because no resource has a function field and
+ // so nobody is going to call Value().
+
+ //if err := obj.typ.Cmp(value.Type()); err != nil {
+ // return err
+ //}
+ //// FIXME: is this part necessary?
+ //funcValue, worked := value.(*fancyfunc.FuncValue)
+ //if !worked {
+ // return fmt.Errorf("expected a FuncValue")
+ //}
+ //obj.V = funcValue.V
return nil
}
@@ -7535,11 +7855,12 @@ func (obj *ExprFunc) SetValue(value types.Value) error {
// This might get called speculatively (early) during unification to learn more.
// This particular value is always known since it is a constant.
func (obj *ExprFunc) Value() (types.Value, error) {
- // TODO: implement speculative value lookup (if not already sufficient)
- return &types.FuncValue{
- V: obj.V,
- T: obj.typ,
- }, nil
+ panic("ExprFunc does not store its latest value because resources are not expected to have function fields.")
+ //// TODO: implement speculative value lookup (if not already sufficient)
+ //return &fancyfunc.FuncValue{
+ // V: obj.V,
+ // T: obj.typ,
+ //}, nil
}
// ExprCall is a representation of a function call. This does not represent the
@@ -8390,52 +8711,7 @@ func (obj *ExprCall) Unify() ([]interfaces.Invariant, error) {
// Func returns the reactive stream of values that this expression produces.
// Reminder that this looks very similar to ExprVar...
func (obj *ExprCall) Func() (interfaces.Func, error) {
- if obj.expr == nil {
- // possible programming error
- return nil, fmt.Errorf("call doesn't contain an expr pointer yet")
- }
-
- typ, err := obj.Type()
- if err != nil {
- return nil, err
- }
-
- ftyp, err := obj.expr.Type()
- if err != nil {
- return nil, err
- }
-
- // function specific code follows...
- fn, isFn := obj.expr.(*ExprFunc)
- if isFn && fn.Function != nil {
- // NOTE: This has to be a unique pointer each time, which is why
- // the ExprFunc builds a special unique copy into .function that
- // is used here. If it was shared across the function graph, the
- // function engine would error, because it would be operating on
- // the same struct that is being touched from multiple places...
- return fn.function, nil
- //return obj.fn.Func() // this is incorrect. see ExprVar comment
- //return fn.Function(), nil // this is also incorrect.
- }
-
- // XXX: receive the ExprFunc properly, and use it in CallFunc...
- //if isFn && len(fn.Values) > 0 {
- // return &structs.CallFunc{
- // Type: typ, // this is the type of what the func returns
- // FuncType: ftyp,
- // Edge: "???",
- // Fn: ???,
- // }, nil
- //}
-
- // direct func
- return &structs.CallFunc{
- Type: typ, // this is the type of what the func returns
- FuncType: ftyp,
- // the edge name used above in Graph is this...
- Edge: fmt.Sprintf("call:%s", obj.Name),
- //Indexed: true, // 0, 1, 2 ... TODO: is this useful?
- }, nil
+ panic("Please use ExprCall.MergedGraph() instead")
}
// Graph returns the reactive function graph which is expressed by this node. It
@@ -8446,7 +8722,6 @@ func (obj *ExprCall) Func() (interfaces.Func, error) {
// children might. This returns a graph with a single vertex (itself) in it, and
// the edges from all of the child graphs to this.
func (obj *ExprCall) Graph(env map[string]interfaces.Func) (*pgraph.Graph, interfaces.Func, error) {
- //panic("i suspect ExprCall->Graph might need to be different somehow") // XXX !!!
if obj.expr == nil {
// possible programming error
return nil, nil, fmt.Errorf("call doesn't contain an expr pointer yet")
@@ -8454,112 +8729,75 @@ func (obj *ExprCall) Graph(env map[string]interfaces.Func) (*pgraph.Graph, inter
graph, err := pgraph.NewGraph("call")
if err != nil {
- return nil, nil, err
+ return nil, nil, errwrap.Wrapf(err, "could not create graph")
}
- function, err := obj.Func()
- if err != nil {
- return nil, nil, err
- }
- graph.AddVertex(function)
- // argnames!
- argNames := []string{}
-
- typ, err := obj.expr.Type()
- if err != nil {
- return nil, nil, err
- }
- // TODO: can we use this method for all of the kinds of obj.expr?
- // TODO: probably, but i've left in the expanded versions for now
- argNames = typ.Ord
- var inconsistentEdgeNames = false // probably better off with this off!
-
- // function specific code follows...
- fn, isFn := obj.expr.(*ExprFunc)
- if isFn && inconsistentEdgeNames {
- if fn.Body != nil {
- // add arg names that are seen in the ExprFunc struct!
- a := []string{}
- for _, x := range fn.Args {
- a = append(a, x.Name)
- }
- argNames = a
- }
- if fn.Function != nil {
- argNames = fn.function.Info().Sig.Ord
+ // Add the vertex for the function.
+ var fnFunc interfaces.Func
+ if obj.Var {
+ // $f(...)
+ // The function value comes from a variable, so we must use the existing
+ // Func from the environment in order to make sure each occurrence of
+ // this variable shares the same internal state. For example, suppose that
+ // $f's definition says that a coin is flipped to pick whether $f should
+ // behave like + or *. If we called obj.expr.MergeGraph(nil) here then
+ // each call site would flip its own coin, whereas by using the existing
+ // Func from the environment, a single coin is flipped at the definition
+ // site and then if + is picked then every use site of $f behaves like +.
+ fnVertex, ok := env[obj.Name]
+ if !ok {
+ return nil, nil, fmt.Errorf("var `%s` is missing from the environment", obj.Name)
}
- if len(fn.Values) > 0 {
- // add the expected arg names from the selected function
- typ, err := fn.Type()
- if err != nil {
- return nil, nil, err
- }
- argNames = typ.Ord
+ // check if fnVertex is a Func
+ fnFunc, ok = fnVertex.(interfaces.Func)
+ if !ok {
+ return nil, nil, fmt.Errorf("var `%s` is not a Func", obj.Name)
}
- }
-
- if len(argNames) != len(obj.Args) { // extra safety...
- return nil, nil, fmt.Errorf("func `%s` expected %d args, got %d", obj.Name, len(argNames), len(obj.Args))
- }
- // Each func argument needs to point to the final function expression.
- for pos, x := range obj.Args { // function arguments in order
- g, _, err := x.Graph(env)
+ graph.AddVertex(fnFunc)
+ } else {
+ // f(...)
+ // The function value comes from a func declaration, so the
+ // coin-flipping scenario is not possible, as f always has the same
+ // definition.
+ var g *pgraph.Graph
+ g, fnFunc, err = obj.expr.MergedGraph(nil) // XXX: pass in globals from scope?
if err != nil {
- return nil, nil, err
+ return nil, nil, errwrap.Wrapf(err, "could not get graph for function %s", obj.Name)
}
- //argName := fmt.Sprintf("%d", pos) // indexed!
- argName := argNames[pos]
- edge := &interfaces.FuncEdge{Args: []string{argName}}
- // TODO: replace with:
- //edge := &interfaces.FuncEdge{Args: []string{fmt.Sprintf("arg:%s", argName)}}
-
- var once bool
- edgeGenFn := func(v1, v2 pgraph.Vertex) pgraph.Edge {
- if once {
- panic(fmt.Sprintf("edgeGenFn for func `%s`, arg `%s` was called twice", obj.Name, argName))
- }
- once = true
- return edge
- }
- graph.AddEdgeGraphVertexLight(g, function, edgeGenFn) // arg -> func
+ graph.AddGraph(g)
}
- // This is important, because we don't want an extra, unnecessary edge!
- if isFn && (fn.Function != nil || len(fn.Values) > 0) {
- return graph, function, nil // built-in's don't need a vertex or an edge!
+ // 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
+ // corresponding subgraph and connects these arguments to it.
+ var argFuncs []interfaces.Func
+ for i, arg := range obj.Args {
+ argGraph, argFunc, err := arg.MergedGraph(env)
+ if err != nil {
+ return nil, nil, errwrap.Wrapf(err, "could not get graph for arg %d", i)
+ }
+ graph.AddGraph(argGraph)
+ argFuncs = append(argFuncs, argFunc)
}
- // Add the graph of the expression which must proceed the call... This
- // might already exist in graph (i think)...
- // Note: This can cause a panic if you get two NOT-connected vertices,
- // in the source graph, because it tries to add two edges! Solution: add
- // the missing edge between those in the source... Happy bug killing =D
- // XXX
- // XXX: this is probably incorrect! Graph has functions now
- // XXX
- //graph.AddVertex(obj.expr) // duplicate additions are ignored and are harmless
-
- g, f, err := obj.expr.Graph(env)
+ // Add a vertex for the call itself.
+ ftyp, err := obj.expr.Type()
if err != nil {
- return nil, nil, err
+ return nil, nil, errwrap.Wrapf(err, "could not get the type of the function")
}
- graph.AddVertex(f) // duplicate additions are ignored and are harmless
- edge := &interfaces.FuncEdge{Args: []string{fmt.Sprintf("call:%s", obj.Name)}}
-
- var once bool
- edgeGenFn := func(v1, v2 pgraph.Vertex) pgraph.Edge {
- if once {
- panic(fmt.Sprintf("edgeGenFn for call `%s` was called twice", obj.Name))
- }
- once = true
- return edge
+ callFunc := &simple.CallFunc{
+ Type: obj.typ,
+ FuncType: ftyp,
+ ArgVertices: argFuncs,
}
- graph.AddEdgeGraphVertexLight(g, function, edgeGenFn) // expr -> call
+ graph.AddVertex(callFunc)
+ graph.AddEdge(fnFunc, callFunc, &pgraph.SimpleEdge{Name: simple.CallFuncArgNameFunction})
- return graph, function, nil
+ return graph, callFunc, nil
}
// SetValue here is used to store the result of the last computation of this
@@ -8749,37 +8987,7 @@ func (obj *ExprVar) Unify() ([]interfaces.Invariant, error) {
// of the function graph engine. Reminder that this looks very similar to
// ExprCall...
func (obj *ExprVar) Func() (interfaces.Func, error) {
- //expr, exists := obj.scope.Variables[obj.Name]
- //if !exists {
- // return nil, fmt.Errorf("var `%s` does not exist in scope", obj.Name)
- //}
-
- // this is wrong, if we did it this way, this expr wouldn't exist as a
- // distinct node in the function graph to relay values through, instead,
- // it would be acting as a "substitution/lookup" function, which just
- // copies the bound function over into here. As a result, we'd have N
- // copies of that function (based on the number of times N that that
- // variable is used) instead of having that single bound function as
- // input which is sent via N different edges to the multiple locations
- // where the variables are used. Since the bound function would still
- // have a single unique pointer, this wouldn't actually exist more than
- // once in the graph, although since it's illogical, it causes the graph
- // type checking (the edge counting in the function graph engine) to
- // notice a problem and error.
- //return expr.Func() // recurse?
-
- // instead, return a function which correctly does a lookup in the scope
- // and returns *that* stream of values instead.
- typ, err := obj.Type()
- if err != nil {
- return nil, err
- }
-
- // var func
- return &structs.VarFunc{
- Type: typ,
- Edge: fmt.Sprintf("var:%s", obj.Name), // the edge name used above in Graph is this...
- }, nil
+ panic("Please use ExprCall.Graph() instead")
}
// Graph returns the reactive function graph which is expressed by this node. It
@@ -8796,49 +9004,19 @@ func (obj *ExprVar) Func() (interfaces.Func, error) {
// to avoid duplicating production of the incoming input value from the bound
// expression.
func (obj *ExprVar) Graph(env map[string]interfaces.Func) (*pgraph.Graph, interfaces.Func, error) {
- graph, err := pgraph.NewGraph("var")
- if err != nil {
- return nil, nil, err
- }
- function, err := obj.Func()
- if err != nil {
- return nil, nil, err
- }
- graph.AddVertex(function)
-
- // ??? = $foo (this is the foo)
- // lookup value from scope
- expr, exists := obj.scope.Variables[obj.Name]
+ // The environment contains every variable which is in scope, so we should
+ // be able to simply look up the variable.
+ varFunc, exists := env[obj.Name]
if !exists {
- return nil, nil, fmt.Errorf("var `%s` does not exist in this scope", obj.Name)
+ return nil, nil, fmt.Errorf("var `%s` is not in the environment", obj.Name)
}
- // should already exist in graph (i think)...
- // XXX
- // XXX: this is probably incorrect! Graph has functions now
- // XXX
- //graph.AddVertex(expr) // duplicate additions are ignored and are harmless
-
- // the expr needs to point to the var lookup expression
- g, f, err := expr.Graph(env)
+ graph, err := pgraph.NewGraph("ExprVar")
if err != nil {
- return nil, nil, err
+ return nil, nil, errwrap.Wrapf(err, "could not create graph")
}
- graph.AddVertex(f) // duplicate additions are ignored and are harmless
-
- edge := &interfaces.FuncEdge{Args: []string{fmt.Sprintf("var:%s", obj.Name)}}
-
- var once bool
- edgeGenFn := func(v1, v2 pgraph.Vertex) pgraph.Edge {
- if once {
- panic(fmt.Sprintf("edgeGenFn for var `%s` was called twice", obj.Name))
- }
- once = true
- return edge
- }
- graph.AddEdgeGraphVertexLight(g, function, edgeGenFn) // expr -> var
-
- return graph, function, nil
+ graph.AddVertex(varFunc)
+ return graph, varFunc, nil
}
// SetValue here is a no-op, because algorithmically when this is called from
@@ -9180,7 +9358,7 @@ func (obj *ExprIf) Func() (interfaces.Func, error) {
return nil, err
}
- return &structs.IfFunc{
+ return &IfFunc{
Type: typ, // this is the output type of the expression
}, nil
}
@@ -9198,13 +9376,9 @@ func (obj *ExprIf) Func() (interfaces.Func, error) {
func (obj *ExprIf) Graph(env map[string]interfaces.Func) (*pgraph.Graph, interfaces.Func, error) {
graph, err := pgraph.NewGraph("if")
if err != nil {
- return nil, nil, err
+ return nil, nil, errwrap.Wrapf(err, "could not create graph")
}
- function, err := obj.Func()
- if err != nil {
- return nil, nil, err
- }
- graph.AddVertex(function)
+ graph.AddVertex(obj)
exprs := map[string]interfaces.Expr{
"c": obj.Condition,
@@ -9213,7 +9387,7 @@ func (obj *ExprIf) Graph(env map[string]interfaces.Func) (*pgraph.Graph, interfa
}
for _, argName := range []string{"c", "a", "b"} { // deterministic order
x := exprs[argName]
- g, _, err := x.Graph(env)
+ g, _, err := x.MergedGraph(env)
if err != nil {
return nil, nil, err
}
@@ -9228,10 +9402,14 @@ func (obj *ExprIf) Graph(env map[string]interfaces.Func) (*pgraph.Graph, interfa
once = true
return edge
}
- graph.AddEdgeGraphVertexLight(g, function, edgeGenFn) // branch -> if
+ graph.AddEdgeGraphVertexLight(g, obj, edgeGenFn) // branch -> if
}
- return graph, function, nil
+ f, err := obj.Func()
+ if err != nil {
+ return nil, nil, err
+ }
+ return graph, f, nil
}
// Graph returns the reactive function graph which is expressed by this node. It
diff --git a/lang/funcs/structs/composite.go b/lang/ast/structs_composite.go
similarity index 99%
rename from lang/funcs/structs/composite.go
rename to lang/ast/structs_composite.go
index 959f20c631..cf9541300c 100644
--- a/lang/funcs/structs/composite.go
+++ b/lang/ast/structs_composite.go
@@ -15,7 +15,8 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
-package structs
+//package structs
+package ast
import (
"context"
diff --git a/lang/funcs/structs/if.go b/lang/ast/structs_if.go
similarity index 98%
rename from lang/funcs/structs/if.go
rename to lang/ast/structs_if.go
index 846eca4b74..1e1fa0c239 100644
--- a/lang/funcs/structs/if.go
+++ b/lang/ast/structs_if.go
@@ -15,11 +15,13 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
-package structs
+//package structs
+package ast
import (
"context"
"fmt"
+ "strings"
"github.com/purpleidea/mgmt/lang/interfaces"
"github.com/purpleidea/mgmt/lang/types"
diff --git a/lang/ast/util.go b/lang/ast/util.go
index 1918cd6885..cb8b786a0c 100644
--- a/lang/ast/util.go
+++ b/lang/ast/util.go
@@ -39,14 +39,14 @@ func FuncPrefixToFunctionsScope(prefix string) map[string]interfaces.Expr {
for name, f := range fns {
x := f() // inspect
- // We can pass in Fns []*types.FuncValue for the simple and
+ // We can pass in Fns []*types.SimpleFn for the simple and
// simplepoly API's and avoid the double wrapping from the
// simple/simplepoly API's to the main function api and back.
if st, ok := x.(*simple.WrappedFunc); simple.DirectInterface && ok {
fn := &ExprFunc{
Title: name,
- Values: []*types.FuncValue{st.Fn}, // just one!
+ Values: []*types.SimpleFn{st.Fn}, // just one!
}
// XXX: should we run fn.SetType(st.Fn.Type()) ?
exprs[name] = fn
@@ -196,16 +196,6 @@ func ValueToExpr(val types.Value) (interfaces.Expr, error) {
Fields: fields,
}
- case *types.FuncValue:
- // TODO: this particular case is particularly untested!
- expr = &ExprFunc{
- Title: "", // TODO: change this?
- // TODO: symmetrically, it would have used x.Func() here
- Values: []*types.FuncValue{
- x, // just one!
- },
- }
-
case *types.VariantValue:
// TODO: should this be allowed, or should we unwrap them?
return nil, fmt.Errorf("variant values are not supported")
diff --git a/lang/fancyfunc/fancy_func.go b/lang/fancyfunc/fancy_func.go
new file mode 100644
index 0000000000..8c83f9f64c
--- /dev/null
+++ b/lang/fancyfunc/fancy_func.go
@@ -0,0 +1,162 @@
+package fancyfunc
+
+import (
+ "fmt"
+
+ "github.com/purpleidea/mgmt/lang/interfaces"
+ "github.com/purpleidea/mgmt/lang/types"
+ "github.com/purpleidea/mgmt/pgraph"
+ "github.com/purpleidea/mgmt/util/errwrap"
+)
+
+// FuncValue represents a function value, for example a built-in or a lambda.
+//
+// In most languages, we can simply call a function with a list of arguments and
+// expect to receive a single value. In this language, however, a function might
+// be something like datetime.now() or fn(n) {shell(Sprintf("seq %d", n))},
+// which might not produce a value immediately, and might then produce multiple
+// values over time. Thus, in this language, a FuncValue does not receive
+// Values, instead it receives input Func nodes. The FuncValue then adds more
+// Func nodes and edges in order to arrange for output values to be sent to a
+// particular output node, which the function returns so that the caller may
+// connect that output node to more nodes down the line.
+//
+// The function can also return an error which could represent that something
+// went horribly wrong. (Think, an internal panic.)
+type FuncValue struct {
+ V func(*interfaces.ReversibleTxn, []interfaces.Func) (interfaces.Func, error)
+ T *types.Type // contains ordered field types, arg names are a bonus part
+}
+
+// NewFunc creates a new function with the specified type.
+func NewFunc(t *types.Type) *FuncValue {
+ if t.Kind != types.KindFunc {
+ return nil // sanity check
+ }
+ v := func(*interfaces.ReversibleTxn, []interfaces.Func) (interfaces.Func, error) {
+ return nil, fmt.Errorf("nil function") // TODO: is this correct?
+ }
+ return &FuncValue{
+ V: v,
+ T: t,
+ }
+}
+
+// String returns a visual representation of this value.
+func (obj *FuncValue) String() string {
+ return fmt.Sprintf("func(%+v)", obj.T) // TODO: can't print obj.V w/o vet warning
+}
+
+// Type returns the type data structure that represents this type.
+func (obj *FuncValue) Type() *types.Type { return obj.T }
+
+// Less compares to value and returns true if we're smaller. This panics if the
+// two types aren't the same.
+func (obj *FuncValue) Less(v types.Value) bool {
+ panic("functions are not comparable")
+}
+
+// Cmp returns an error if this value isn't the same as the arg passed in.
+func (obj *FuncValue) Cmp(val types.Value) error {
+ if obj == nil || val == nil {
+ return fmt.Errorf("cannot cmp to nil")
+ }
+ if err := obj.Type().Cmp(val.Type()); err != nil {
+ return errwrap.Wrapf(err, "cannot cmp types")
+ }
+
+ return fmt.Errorf("cannot cmp funcs") // TODO: can we ?
+}
+
+// Copy returns a copy of this value.
+func (obj *FuncValue) Copy() types.Value {
+ panic("cannot implement Copy() for FuncValue, because FuncValue is a FancyValue, not a Value")
+}
+
+// Value returns the raw value of this type.
+func (obj *FuncValue) Value() interface{} {
+ panic("TODO [SimpleFn] [Reflect]: what's all this reflection stuff for?")
+ //typ := obj.T.Reflect()
+
+ //// wrap our function with the translation that is necessary
+ //fn := func(args []reflect.Value) (results []reflect.Value) { // build
+ // innerArgs := []Value{}
+ // for _, x := range args {
+ // v, err := ValueOf(x) // reflect.Value -> Value
+ // if err != nil {
+ // panic(fmt.Sprintf("can't determine value of %+v", x))
+ // }
+ // innerArgs = append(innerArgs, v)
+ // }
+ // result, err := obj.V(innerArgs) // call it
+ // if err != nil {
+ // // when calling our function with the Call method, then
+ // // we get the error output and have a chance to decide
+ // // what to do with it, but when calling it from within
+ // // a normal golang function call, the error represents
+ // // that something went horribly wrong, aka a panic...
+ // panic(fmt.Sprintf("function panic: %+v", err))
+ // }
+ // return []reflect.Value{reflect.ValueOf(result.Value())} // only one result
+ //}
+ //val := reflect.MakeFunc(typ, fn)
+ //return val.Interface()
+}
+
+// Bool represents the value of this type as a bool if it is one. If this is not
+// a bool, then this panics.
+func (obj *FuncValue) Bool() bool {
+ panic("not a bool")
+}
+
+// Str represents the value of this type as a string if it is one. If this is
+// not a string, then this panics.
+func (obj *FuncValue) Str() string {
+ panic("not an str") // yes, i think this is the correct grammar
+}
+
+// Int represents the value of this type as an integer if it is one. If this is
+// not an integer, then this panics.
+func (obj *FuncValue) Int() int64 {
+ panic("not an int")
+}
+
+// Float represents the value of this type as a float if it is one. If this is
+// not a float, then this panics.
+func (obj *FuncValue) Float() float64 {
+ panic("not a float")
+}
+
+// List represents the value of this type as a list if it is one. If this is not
+// a list, then this panics.
+func (obj *FuncValue) List() []types.Value {
+ panic("not a list")
+}
+
+// Map represents the value of this type as a dictionary if it is one. If this
+// is not a map, then this panics.
+func (obj *FuncValue) Map() map[types.Value]types.Value {
+ panic("not a list")
+}
+
+// Struct represents the value of this type as a struct if it is one. If this is
+// not a struct, then this panics.
+func (obj *FuncValue) Struct() map[string]types.Value {
+ panic("not a struct")
+}
+
+// Func represents the value of this type as a function if it is one. If this is
+// not a function, then this panics.
+func (obj *FuncValue) Func() func([]pgraph.Vertex) (pgraph.Vertex, error) {
+ panic("cannot implement Func() for FuncValue, because FuncValue manipulates the graph, not just returns a value")
+}
+
+// Set sets the function value to be a new function.
+func (obj *FuncValue) Set(fn func(*interfaces.ReversibleTxn, []interfaces.Func) (interfaces.Func, error)) error { // TODO: change method name?
+ obj.V = fn
+ return nil // TODO: can we do any sort of checking here?
+}
+
+func (obj *FuncValue) Call(txn *interfaces.ReversibleTxn, args []interfaces.Func) (interfaces.Func, error) {
+ return obj.V(txn, args)
+}
diff --git a/lang/funcs/core/convert/to_float.go b/lang/funcs/core/convert/to_float.go
index 3fea00461b..a33d31d2fb 100644
--- a/lang/funcs/core/convert/to_float.go
+++ b/lang/funcs/core/convert/to_float.go
@@ -23,7 +23,7 @@ import (
)
func init() {
- simple.ModuleRegister(ModuleName, "to_float", &types.FuncValue{
+ simple.ModuleRegister(ModuleName, "to_float", &types.SimpleFn{
T: types.NewType("func(a int) float"),
V: ToFloat,
})
diff --git a/lang/funcs/core/convert/to_int.go b/lang/funcs/core/convert/to_int.go
index 18059f7f7f..66405b6e39 100644
--- a/lang/funcs/core/convert/to_int.go
+++ b/lang/funcs/core/convert/to_int.go
@@ -23,7 +23,7 @@ import (
)
func init() {
- simple.ModuleRegister(ModuleName, "to_int", &types.FuncValue{
+ simple.ModuleRegister(ModuleName, "to_int", &types.SimpleFn{
T: types.NewType("func(a float) int"),
V: ToInt,
})
diff --git a/lang/funcs/core/datetime/format_func.go b/lang/funcs/core/datetime/format_func.go
index e6f14f1772..c8ef6db7dc 100644
--- a/lang/funcs/core/datetime/format_func.go
+++ b/lang/funcs/core/datetime/format_func.go
@@ -26,7 +26,7 @@ import (
)
func init() {
- simple.ModuleRegister(ModuleName, "format", &types.FuncValue{
+ simple.ModuleRegister(ModuleName, "format", &types.SimpleFn{
T: types.NewType("func(a int, b str) str"),
V: Format,
})
diff --git a/lang/funcs/core/datetime/hour_func.go b/lang/funcs/core/datetime/hour_func.go
index 4159105249..0b1cd16f22 100644
--- a/lang/funcs/core/datetime/hour_func.go
+++ b/lang/funcs/core/datetime/hour_func.go
@@ -26,7 +26,7 @@ import (
)
func init() {
- simple.ModuleRegister(ModuleName, "hour", &types.FuncValue{
+ simple.ModuleRegister(ModuleName, "hour", &types.SimpleFn{
T: types.NewType("func(a int) int"),
V: Hour,
})
diff --git a/lang/funcs/core/datetime/print_func.go b/lang/funcs/core/datetime/print_func.go
index 1051015b18..30ed27e024 100644
--- a/lang/funcs/core/datetime/print_func.go
+++ b/lang/funcs/core/datetime/print_func.go
@@ -27,7 +27,7 @@ import (
func init() {
// FIXME: consider renaming this to printf, and add in a format string?
- simple.ModuleRegister(ModuleName, "print", &types.FuncValue{
+ simple.ModuleRegister(ModuleName, "print", &types.SimpleFn{
T: types.NewType("func(a int) str"),
V: func(input []types.Value) (types.Value, error) {
epochDelta := input[0].Int()
diff --git a/lang/funcs/core/datetime/weekday_func.go b/lang/funcs/core/datetime/weekday_func.go
index b0b2b06f33..378910a975 100644
--- a/lang/funcs/core/datetime/weekday_func.go
+++ b/lang/funcs/core/datetime/weekday_func.go
@@ -27,7 +27,7 @@ import (
)
func init() {
- simple.ModuleRegister(ModuleName, "weekday", &types.FuncValue{
+ simple.ModuleRegister(ModuleName, "weekday", &types.SimpleFn{
T: types.NewType("func(a int) str"),
V: Weekday,
})
diff --git a/lang/funcs/core/example/answer_func.go b/lang/funcs/core/example/answer_func.go
index 11b701cee3..8337aaa717 100644
--- a/lang/funcs/core/example/answer_func.go
+++ b/lang/funcs/core/example/answer_func.go
@@ -26,7 +26,7 @@ import (
const Answer = 42
func init() {
- simple.ModuleRegister(ModuleName, "answer", &types.FuncValue{
+ simple.ModuleRegister(ModuleName, "answer", &types.SimpleFn{
T: types.NewType("func() int"),
V: func([]types.Value) (types.Value, error) {
return &types.IntValue{V: Answer}, nil
diff --git a/lang/funcs/core/example/errorbool_func.go b/lang/funcs/core/example/errorbool_func.go
index 9a3332d84e..e7e2abd1c0 100644
--- a/lang/funcs/core/example/errorbool_func.go
+++ b/lang/funcs/core/example/errorbool_func.go
@@ -25,7 +25,7 @@ import (
)
func init() {
- simple.ModuleRegister(ModuleName, "errorbool", &types.FuncValue{
+ simple.ModuleRegister(ModuleName, "errorbool", &types.SimpleFn{
T: types.NewType("func(a bool) str"),
V: func(input []types.Value) (types.Value, error) {
if input[0].Bool() {
diff --git a/lang/funcs/core/example/int2str_func.go b/lang/funcs/core/example/int2str_func.go
index 8ac29d2782..8da6d748a8 100644
--- a/lang/funcs/core/example/int2str_func.go
+++ b/lang/funcs/core/example/int2str_func.go
@@ -25,7 +25,7 @@ import (
)
func init() {
- simple.ModuleRegister(ModuleName, "int2str", &types.FuncValue{
+ simple.ModuleRegister(ModuleName, "int2str", &types.SimpleFn{
T: types.NewType("func(a int) str"),
V: func(input []types.Value) (types.Value, error) {
return &types.StrValue{
diff --git a/lang/funcs/core/example/nested/hello_func.go b/lang/funcs/core/example/nested/hello_func.go
index a5f37a137d..f30e7209c6 100644
--- a/lang/funcs/core/example/nested/hello_func.go
+++ b/lang/funcs/core/example/nested/hello_func.go
@@ -24,7 +24,7 @@ import (
)
func init() {
- simple.ModuleRegister(coreexample.ModuleName+"/"+ModuleName, "hello", &types.FuncValue{
+ simple.ModuleRegister(coreexample.ModuleName+"/"+ModuleName, "hello", &types.SimpleFn{
T: types.NewType("func() str"),
V: Hello,
})
diff --git a/lang/funcs/core/example/plus_func.go b/lang/funcs/core/example/plus_func.go
index d1c85b95ed..78aec79cce 100644
--- a/lang/funcs/core/example/plus_func.go
+++ b/lang/funcs/core/example/plus_func.go
@@ -23,7 +23,7 @@ import (
)
func init() {
- simple.ModuleRegister(ModuleName, "plus", &types.FuncValue{
+ simple.ModuleRegister(ModuleName, "plus", &types.SimpleFn{
T: types.NewType("func(y str, z str) str"),
V: Plus,
})
diff --git a/lang/funcs/core/example/str2int_func.go b/lang/funcs/core/example/str2int_func.go
index c0bad61298..1e2887786e 100644
--- a/lang/funcs/core/example/str2int_func.go
+++ b/lang/funcs/core/example/str2int_func.go
@@ -25,7 +25,7 @@ import (
)
func init() {
- simple.ModuleRegister(ModuleName, "str2int", &types.FuncValue{
+ simple.ModuleRegister(ModuleName, "str2int", &types.SimpleFn{
T: types.NewType("func(a str) int"),
V: func(input []types.Value) (types.Value, error) {
var i int64
diff --git a/lang/funcs/core/iter/map_func.go b/lang/funcs/core/iter/map_func.go
index 28f16f4869..5a224d6a60 100644
--- a/lang/funcs/core/iter/map_func.go
+++ b/lang/funcs/core/iter/map_func.go
@@ -21,9 +21,12 @@ import (
"context"
"fmt"
+ "github.com/purpleidea/mgmt/lang/fancyfunc"
"github.com/purpleidea/mgmt/lang/funcs"
+ "github.com/purpleidea/mgmt/lang/funcs/simple"
"github.com/purpleidea/mgmt/lang/interfaces"
"github.com/purpleidea/mgmt/lang/types"
+ "github.com/purpleidea/mgmt/pgraph"
"github.com/purpleidea/mgmt/util"
"github.com/purpleidea/mgmt/util/errwrap"
)
@@ -57,12 +60,9 @@ type MapFunc struct {
RType *types.Type // this is the type of the elements in our output list
init *interfaces.Init
- last types.Value // last value received to use for diff
- inputs types.Value
- function func([]types.Value) (types.Value, error)
-
- result types.Value // last calculated output
+ lastFuncValue *fancyfunc.FuncValue // remember the last function value
+ lastInputListLength int // remember the last input list length
}
// String returns a simple name for this function. This is needed so this struct
@@ -561,70 +561,197 @@ func (obj *MapFunc) sig() *types.Type {
// Init runs some startup code for this function.
func (obj *MapFunc) Init(init *interfaces.Init) error {
obj.init = init
+ obj.lastFuncValue = nil
+ obj.lastInputListLength = -1
return nil
}
// Stream returns the changing values that this func has over time.
func (obj *MapFunc) Stream(ctx context.Context) error {
+ // Every time the FuncValue or the length of the list changes, recreate the
+ // subgraph, by calling the FuncValue N times on N nodes, each of which
+ // extracts one of the N values in the list.
+
defer close(obj.init.Output) // the sender closes
- rtyp := types.NewType(fmt.Sprintf("[]%s", obj.RType.String()))
- for {
- select {
- case input, ok := <-obj.init.Input:
- if !ok {
- obj.init.Input = nil // don't infinite loop back
- continue // no more inputs, but don't return!
+
+ inputListType := types.NewType(fmt.Sprintf("[]%s", obj.Type))
+ outputListType := types.NewType(fmt.Sprintf("[]%s", obj.RType))
+
+ // A Func to send input lists to the subgraph. This Func is not reset when
+ // the subgraph is recreated, so that the function graph can propagate the
+ // last list we received to the subgraph.
+ inputChan := make(chan types.Value)
+ subgraphInput := &simple.ChannelBasedSourceFunc{
+ Name: "subgraphInput",
+ Chan: inputChan,
+ Type: inputListType,
+ }
+ obj.init.Txn.AddVertex(subgraphInput)
+ defer func() {
+ close(inputChan)
+ obj.init.Txn.DeleteVertex(subgraphInput)
+ }()
+
+ // An initially-closed channel from which we receive output lists from the
+ // subgraph. This channel is reset when the subgraph is recreated.
+ var outputChan chan types.Value = nil
+
+ // Create a subgraph which splits the input list into 'n' nodes, applies
+ // 'newFuncValue' to each, then combines the 'n' outputs back into a list.
+ //
+ // Here is what the subgraph looks like:
+ //
+ // digraph {
+ // "subgraphInput" -> "inputElemFunc0"
+ // "subgraphInput" -> "inputElemFunc1"
+ // "subgraphInput" -> "inputElemFunc2"
+ //
+ // "inputElemFunc0" -> "outputElemFunc0"
+ // "inputElemFunc1" -> "outputElemFunc1"
+ // "inputElemFunc2" -> "outputElemFunc2"
+ //
+ // "outputElemFunc0" -> "outputListFunc"
+ // "outputElemFunc1" -> "outputListFunc"
+ // "outputElemFunc1" -> "outputListFunc"
+ //
+ // "outputListFunc" -> "subgraphOutput"
+ // }
+ replaceSubGraph := func(
+ newFuncValue *fancyfunc.FuncValue,
+ n int,
+ ) error {
+ // delete the old subgraph
+ obj.init.Txn.Reverse() // XXX: Reverse?
+
+ // create the new subgraph
+
+ outputChan = make(chan types.Value)
+ subgraphOutput := &simple.ChannelBasedSinkFunc{
+ Name: "subgraphOutput",
+ Chan: outputChan,
+ Type: outputListType,
+ }
+ obj.init.Txn.AddVertex(subgraphOutput)
+
+ outputListFuncArgs := "" // e.g. "outputElem0 int, outputElem1 int, outputElem2 int"
+ for i := 0; i < n; i++ {
+ if i > 0 {
+ outputListFuncArgs += ", "
}
- //if err := input.Type().Cmp(obj.Info().Sig.Input); err != nil {
- // return errwrap.Wrapf(err, "wrong function input")
- //}
+ outputListFuncArgs += fmt.Sprintf("outputElem%d %s", i, obj.RType)
+ }
+ outputListFunc := simple.SimpleFnToDirectFunc(&types.SimpleFn{
+ V: func(args []types.Value) (types.Value, error) {
+ listValue := &types.ListValue{
+ V: args,
+ T: outputListType,
+ }
- if obj.last != nil && input.Cmp(obj.last) == nil {
- continue // value didn't change, skip it
+ return listValue, nil
+ },
+ T: types.NewType(fmt.Sprintf("func(%s) %s", outputListFuncArgs, outputListType)),
+ })
+ obj.init.Txn.AddVertex(outputListFunc)
+ obj.init.Txn.AddEdge(outputListFunc, subgraphOutput, &interfaces.FuncEdge{
+ Args: []string{"arg"},
+ })
+
+ for i := 0; i < n; i++ {
+ inputElemFunc := simple.SimpleFnToDirectFunc(
+ &types.SimpleFn{
+ V: func(args []types.Value) (types.Value, error) {
+ if len(args) != 1 {
+ return nil, fmt.Errorf("inputElemFunc: expected a single argument")
+ }
+ arg := args[0]
+
+ list, ok := arg.(*types.ListValue)
+ if !ok {
+ return nil, fmt.Errorf("inputElemFunc: expected a ListValue argument")
+ }
+
+ return list.List()[i], nil
+ },
+ T: types.NewType(fmt.Sprintf("func(inputList %s) %s", inputListType, obj.Type)),
+ },
+ )
+ obj.init.Txn.AddVertex(inputElemFunc)
+
+ outputElemFunc, err := newFuncValue.Call(obj.init.Txn, []interfaces.Func{inputElemFunc})
+ if err != nil {
+ return errwrap.Wrapf(err, "could not call newFuncValue.Call()")
}
- obj.last = input // store for next
- function := input.Struct()[argNameFunction].Func() // func([]Value) (Value, error)
- //if function == obj.function { // TODO: how can we cmp?
- // continue // nothing changed
- //}
- obj.function = function
+ obj.init.Txn.AddEdge(subgraphInput, inputElemFunc, &interfaces.FuncEdge{
+ Args: []string{"inputList"},
+ })
+ obj.init.Txn.AddEdge(outputElemFunc, outputListFunc, &interfaces.FuncEdge{
+ Args: []string{"arg"},
+ })
+ }
- inputs := input.Struct()[argNameInputs]
- if obj.inputs != nil && obj.inputs.Cmp(inputs) == nil {
- continue // nothing changed
- }
- obj.inputs = inputs
-
- // run the function on each index
- output := []types.Value{}
- for ix, v := range inputs.List() { // []Value
- args := []types.Value{v} // only one input arg!
- x, err := function(args)
- if err != nil {
- return errwrap.Wrapf(err, "error running map function on index %d", ix)
+ obj.init.Txn.Commit()
+
+ return nil
+ }
+ defer func() {
+ obj.init.Txn.Reverse()
+ //obj.init.Txn.Commit()
+ }()
+
+ canReceiveMoreFuncValuesOrInputLists := true
+ canReceiveMoreOutputLists := true
+ for {
+ select {
+ case input, ok := <-obj.init.Input:
+ if !ok {
+ canReceiveMoreFuncValuesOrInputLists = false
+ } else {
+ newFuncValue := input.Struct()[argNameFunction].(*fancyfunc.FuncValue)
+ newInputList := input.Struct()[argNameInputs].(*types.ListValue)
+
+ // If we have a new function or the length of the input list has
+ // changed, then we need to replace the subgraph with a new one
+ // that uses the new function the correct number of times.
+ n := len(newInputList.V)
+ if newFuncValue != obj.lastFuncValue || n != obj.lastInputListLength {
+ if err := replaceSubGraph(newFuncValue, n); err != nil {
+ return errwrap.Wrapf(err, "could not replace subgraph")
+ }
+ canReceiveMoreOutputLists = true
+ obj.lastFuncValue = newFuncValue
+ obj.lastInputListLength = n
}
- output = append(output, x)
- }
- result := &types.ListValue{
- V: output,
- T: rtyp,
+ // send the new input list to the subgraph
+ select {
+ case inputChan <- newInputList:
+ case <-ctx.Done():
+ return nil
+ }
}
- if obj.result != nil && obj.result.Cmp(result) == nil {
- continue // result didn't change
+ case outputList, ok := <-outputChan:
+ // send the new output list downstream
+ if !ok {
+ canReceiveMoreOutputLists = false
+
+ // prevent the next loop iteration from trying to receive from a
+ // closed channel
+ outputChan = nil
+ } else {
+ select {
+ case obj.init.Output <- outputList:
+ case <-ctx.Done():
+ return nil
+ }
}
- obj.result = result // store new result
case <-ctx.Done():
return nil
}
- select {
- case obj.init.Output <- obj.result: // send
- // pass
- case <-ctx.Done():
+ if !canReceiveMoreFuncValuesOrInputLists && !canReceiveMoreOutputLists {
return nil
}
}
diff --git a/lang/funcs/core/len_func.go b/lang/funcs/core/len_func.go
index 9af7b31680..236834dc79 100644
--- a/lang/funcs/core/len_func.go
+++ b/lang/funcs/core/len_func.go
@@ -25,7 +25,7 @@ import (
)
func init() {
- simplepoly.Register("len", []*types.FuncValue{
+ simplepoly.Register("len", []*types.SimpleFn{
{
T: types.NewType("func(str) int"),
V: Len,
diff --git a/lang/funcs/core/math/fortytwo_func.go b/lang/funcs/core/math/fortytwo_func.go
index fdebba1059..49c2798f02 100644
--- a/lang/funcs/core/math/fortytwo_func.go
+++ b/lang/funcs/core/math/fortytwo_func.go
@@ -27,7 +27,7 @@ import (
func init() {
typInt := types.NewType("func() int")
typFloat := types.NewType("func() float")
- simplepoly.ModuleRegister(ModuleName, "fortytwo", []*types.FuncValue{
+ simplepoly.ModuleRegister(ModuleName, "fortytwo", []*types.SimpleFn{
{
T: typInt,
V: fortyTwo(typInt), // generate the correct function here
diff --git a/lang/funcs/core/math/mod_func.go b/lang/funcs/core/math/mod_func.go
index 8f2add93cf..758a0144c7 100644
--- a/lang/funcs/core/math/mod_func.go
+++ b/lang/funcs/core/math/mod_func.go
@@ -26,7 +26,7 @@ import (
)
func init() {
- simplepoly.ModuleRegister(ModuleName, "mod", []*types.FuncValue{
+ simplepoly.ModuleRegister(ModuleName, "mod", []*types.SimpleFn{
{
T: types.NewType("func(int, int) int"),
V: Mod,
diff --git a/lang/funcs/core/math/pow_func.go b/lang/funcs/core/math/pow_func.go
index 5f8691ab4c..04fd39729a 100644
--- a/lang/funcs/core/math/pow_func.go
+++ b/lang/funcs/core/math/pow_func.go
@@ -26,7 +26,7 @@ import (
)
func init() {
- simple.ModuleRegister(ModuleName, "pow", &types.FuncValue{
+ simple.ModuleRegister(ModuleName, "pow", &types.SimpleFn{
T: types.NewType("func(x float, y float) float"),
V: Pow,
})
diff --git a/lang/funcs/core/math/sqrt_func.go b/lang/funcs/core/math/sqrt_func.go
index d0186c08cf..c46e9f9816 100644
--- a/lang/funcs/core/math/sqrt_func.go
+++ b/lang/funcs/core/math/sqrt_func.go
@@ -26,7 +26,7 @@ import (
)
func init() {
- simple.ModuleRegister(ModuleName, "sqrt", &types.FuncValue{
+ simple.ModuleRegister(ModuleName, "sqrt", &types.SimpleFn{
T: types.NewType("func(x float) float"),
V: Sqrt,
})
diff --git a/lang/funcs/core/net/cidr_to_ip_func.go b/lang/funcs/core/net/cidr_to_ip_func.go
index 0146816dc2..dc6a3b25b9 100644
--- a/lang/funcs/core/net/cidr_to_ip_func.go
+++ b/lang/funcs/core/net/cidr_to_ip_func.go
@@ -26,7 +26,7 @@ import (
)
func init() {
- simple.ModuleRegister(ModuleName, "cidr_to_ip", &types.FuncValue{
+ simple.ModuleRegister(ModuleName, "cidr_to_ip", &types.SimpleFn{
T: types.NewType("func(a str) str"),
V: CidrToIP,
})
diff --git a/lang/funcs/core/net/macfmt_func.go b/lang/funcs/core/net/macfmt_func.go
index bec50f1bc9..167e40267d 100644
--- a/lang/funcs/core/net/macfmt_func.go
+++ b/lang/funcs/core/net/macfmt_func.go
@@ -27,7 +27,7 @@ import (
)
func init() {
- simple.ModuleRegister(ModuleName, "macfmt", &types.FuncValue{
+ simple.ModuleRegister(ModuleName, "macfmt", &types.SimpleFn{
T: types.NewType("func(a str) str"),
V: MacFmt,
})
diff --git a/lang/funcs/core/os/family_func.go b/lang/funcs/core/os/family_func.go
index 674ccaf78d..726b0df329 100644
--- a/lang/funcs/core/os/family_func.go
+++ b/lang/funcs/core/os/family_func.go
@@ -26,15 +26,15 @@ import (
func init() {
// TODO: Create a family method that will return a giant struct.
- simple.ModuleRegister(ModuleName, "is_debian", &types.FuncValue{
+ simple.ModuleRegister(ModuleName, "is_debian", &types.SimpleFn{
T: types.NewType("func() bool"),
V: IsDebian,
})
- simple.ModuleRegister(ModuleName, "is_redhat", &types.FuncValue{
+ simple.ModuleRegister(ModuleName, "is_redhat", &types.SimpleFn{
T: types.NewType("func() bool"),
V: IsRedHat,
})
- simple.ModuleRegister(ModuleName, "is_archlinux", &types.FuncValue{
+ simple.ModuleRegister(ModuleName, "is_archlinux", &types.SimpleFn{
T: types.NewType("func() bool"),
V: IsArchLinux,
})
diff --git a/lang/funcs/core/regexp/match_func.go b/lang/funcs/core/regexp/match_func.go
index ae2e5f9a77..87ef2b0ba0 100644
--- a/lang/funcs/core/regexp/match_func.go
+++ b/lang/funcs/core/regexp/match_func.go
@@ -26,7 +26,7 @@ import (
)
func init() {
- simple.ModuleRegister(ModuleName, "match", &types.FuncValue{
+ simple.ModuleRegister(ModuleName, "match", &types.SimpleFn{
T: types.NewType("func(pattern str, s str) bool"),
V: Match,
})
diff --git a/lang/funcs/core/strings/split_func.go b/lang/funcs/core/strings/split_func.go
index 63a6918c8a..aeaeb8c38a 100644
--- a/lang/funcs/core/strings/split_func.go
+++ b/lang/funcs/core/strings/split_func.go
@@ -25,7 +25,7 @@ import (
)
func init() {
- simple.ModuleRegister(ModuleName, "split", &types.FuncValue{
+ simple.ModuleRegister(ModuleName, "split", &types.SimpleFn{
T: types.NewType("func(a str, b str) []str"),
V: Split,
})
diff --git a/lang/funcs/core/strings/to_lower_func.go b/lang/funcs/core/strings/to_lower_func.go
index 537d175179..1b4e31eecc 100644
--- a/lang/funcs/core/strings/to_lower_func.go
+++ b/lang/funcs/core/strings/to_lower_func.go
@@ -25,7 +25,7 @@ import (
)
func init() {
- simple.ModuleRegister(ModuleName, "to_lower", &types.FuncValue{
+ simple.ModuleRegister(ModuleName, "to_lower", &types.SimpleFn{
T: types.NewType("func(a str) str"),
V: ToLower,
})
diff --git a/lang/funcs/core/sys/env_func.go b/lang/funcs/core/sys/env_func.go
index cfec08fd10..bb98e5a32d 100644
--- a/lang/funcs/core/sys/env_func.go
+++ b/lang/funcs/core/sys/env_func.go
@@ -26,19 +26,19 @@ import (
)
func init() {
- simple.ModuleRegister(ModuleName, "getenv", &types.FuncValue{
+ simple.ModuleRegister(ModuleName, "getenv", &types.SimpleFn{
T: types.NewType("func(str) str"),
V: GetEnv,
})
- simple.ModuleRegister(ModuleName, "defaultenv", &types.FuncValue{
+ simple.ModuleRegister(ModuleName, "defaultenv", &types.SimpleFn{
T: types.NewType("func(str, str) str"),
V: DefaultEnv,
})
- simple.ModuleRegister(ModuleName, "hasenv", &types.FuncValue{
+ simple.ModuleRegister(ModuleName, "hasenv", &types.SimpleFn{
T: types.NewType("func(str) bool"),
V: HasEnv,
})
- simple.ModuleRegister(ModuleName, "env", &types.FuncValue{
+ simple.ModuleRegister(ModuleName, "env", &types.SimpleFn{
T: types.NewType("func() map{str: str}"),
V: Env,
})
diff --git a/lang/funcs/core/template_func.go b/lang/funcs/core/template_func.go
index 5db524822e..f76626dca5 100644
--- a/lang/funcs/core/template_func.go
+++ b/lang/funcs/core/template_func.go
@@ -569,7 +569,7 @@ func safename(name string) string {
// function API with what is expected from the reflection API. It returns a
// version that includes the optional second error return value so that our
// functions can return errors without causing a panic.
-func wrap(name string, fn *types.FuncValue) interface{} {
+func wrap(name string, fn *types.SimpleFn) interface{} {
if fn.T.Map == nil {
panic("malformed func type")
}
diff --git a/lang/funcs/funcgen/templates/generated_funcs.go.tpl b/lang/funcs/funcgen/templates/generated_funcs.go.tpl
index 7823dda241..1f53cc23bf 100644
--- a/lang/funcs/funcgen/templates/generated_funcs.go.tpl
+++ b/lang/funcs/funcgen/templates/generated_funcs.go.tpl
@@ -25,7 +25,7 @@ import (
)
func init() {
-{{ range $i, $func := .Functions }} simple.ModuleRegister("{{$func.MgmtPackage}}", "{{$func.MclName}}", &types.FuncValue{
+{{ range $i, $func := .Functions }} simple.ModuleRegister("{{$func.MgmtPackage}}", "{{$func.MclName}}", &types.SimpleFn{
T: types.NewType("{{$func.Signature}}"),
V: {{$func.InternalName}},
})
diff --git a/lang/funcs/operator_func.go b/lang/funcs/operator_func.go
index 00db6a2304..cddf6c4246 100644
--- a/lang/funcs/operator_func.go
+++ b/lang/funcs/operator_func.go
@@ -41,7 +41,7 @@ const (
func init() {
// concatenation
- RegisterOperator("+", &types.FuncValue{
+ RegisterOperator("+", &types.SimpleFn{
T: types.NewType("func(a str, b str) str"),
V: func(input []types.Value) (types.Value, error) {
return &types.StrValue{
@@ -50,7 +50,7 @@ func init() {
},
})
// addition
- RegisterOperator("+", &types.FuncValue{
+ RegisterOperator("+", &types.SimpleFn{
T: types.NewType("func(a int, b int) int"),
V: func(input []types.Value) (types.Value, error) {
//if l := len(input); l != 2 {
@@ -63,7 +63,7 @@ func init() {
},
})
// floating-point addition
- RegisterOperator("+", &types.FuncValue{
+ RegisterOperator("+", &types.SimpleFn{
T: types.NewType("func(a float, b float) float"),
V: func(input []types.Value) (types.Value, error) {
return &types.FloatValue{
@@ -73,7 +73,7 @@ func init() {
})
// subtraction
- RegisterOperator("-", &types.FuncValue{
+ RegisterOperator("-", &types.SimpleFn{
T: types.NewType("func(a int, b int) int"),
V: func(input []types.Value) (types.Value, error) {
return &types.IntValue{
@@ -82,7 +82,7 @@ func init() {
},
})
// floating-point subtraction
- RegisterOperator("-", &types.FuncValue{
+ RegisterOperator("-", &types.SimpleFn{
T: types.NewType("func(a float, b float) float"),
V: func(input []types.Value) (types.Value, error) {
return &types.FloatValue{
@@ -92,7 +92,7 @@ func init() {
})
// multiplication
- RegisterOperator("*", &types.FuncValue{
+ RegisterOperator("*", &types.SimpleFn{
T: types.NewType("func(a int, b int) int"),
V: func(input []types.Value) (types.Value, error) {
// FIXME: check for overflow?
@@ -102,7 +102,7 @@ func init() {
},
})
// floating-point multiplication
- RegisterOperator("*", &types.FuncValue{
+ RegisterOperator("*", &types.SimpleFn{
T: types.NewType("func(a float, b float) float"),
V: func(input []types.Value) (types.Value, error) {
return &types.FloatValue{
@@ -113,7 +113,7 @@ func init() {
// don't add: `func(int, float) float` or: `func(float, int) float`
// division
- RegisterOperator("/", &types.FuncValue{
+ RegisterOperator("/", &types.SimpleFn{
T: types.NewType("func(a int, b int) float"),
V: func(input []types.Value) (types.Value, error) {
divisor := input[1].Int()
@@ -126,7 +126,7 @@ func init() {
},
})
// floating-point division
- RegisterOperator("/", &types.FuncValue{
+ RegisterOperator("/", &types.SimpleFn{
T: types.NewType("func(a float, b float) float"),
V: func(input []types.Value) (types.Value, error) {
divisor := input[1].Float()
@@ -140,7 +140,7 @@ func init() {
})
// string equality
- RegisterOperator("==", &types.FuncValue{
+ RegisterOperator("==", &types.SimpleFn{
T: types.NewType("func(a str, b str) bool"),
V: func(input []types.Value) (types.Value, error) {
return &types.BoolValue{
@@ -149,7 +149,7 @@ func init() {
},
})
// bool equality
- RegisterOperator("==", &types.FuncValue{
+ RegisterOperator("==", &types.SimpleFn{
T: types.NewType("func(a bool, b bool) bool"),
V: func(input []types.Value) (types.Value, error) {
return &types.BoolValue{
@@ -158,7 +158,7 @@ func init() {
},
})
// int equality
- RegisterOperator("==", &types.FuncValue{
+ RegisterOperator("==", &types.SimpleFn{
T: types.NewType("func(a int, b int) bool"),
V: func(input []types.Value) (types.Value, error) {
return &types.BoolValue{
@@ -167,7 +167,7 @@ func init() {
},
})
// floating-point equality
- RegisterOperator("==", &types.FuncValue{
+ RegisterOperator("==", &types.SimpleFn{
T: types.NewType("func(a float, b float) bool"),
V: func(input []types.Value) (types.Value, error) {
// TODO: should we do an epsilon check?
@@ -178,7 +178,7 @@ func init() {
})
// string in-equality
- RegisterOperator("!=", &types.FuncValue{
+ RegisterOperator("!=", &types.SimpleFn{
T: types.NewType("func(a str, b str) bool"),
V: func(input []types.Value) (types.Value, error) {
return &types.BoolValue{
@@ -187,7 +187,7 @@ func init() {
},
})
// bool in-equality
- RegisterOperator("!=", &types.FuncValue{
+ RegisterOperator("!=", &types.SimpleFn{
T: types.NewType("func(a bool, b bool) bool"),
V: func(input []types.Value) (types.Value, error) {
return &types.BoolValue{
@@ -196,7 +196,7 @@ func init() {
},
})
// int in-equality
- RegisterOperator("!=", &types.FuncValue{
+ RegisterOperator("!=", &types.SimpleFn{
T: types.NewType("func(a int, b int) bool"),
V: func(input []types.Value) (types.Value, error) {
return &types.BoolValue{
@@ -205,7 +205,7 @@ func init() {
},
})
// floating-point in-equality
- RegisterOperator("!=", &types.FuncValue{
+ RegisterOperator("!=", &types.SimpleFn{
T: types.NewType("func(a float, b float) bool"),
V: func(input []types.Value) (types.Value, error) {
// TODO: should we do an epsilon check?
@@ -216,7 +216,7 @@ func init() {
})
// less-than
- RegisterOperator("<", &types.FuncValue{
+ RegisterOperator("<", &types.SimpleFn{
T: types.NewType("func(a int, b int) bool"),
V: func(input []types.Value) (types.Value, error) {
return &types.BoolValue{
@@ -225,7 +225,7 @@ func init() {
},
})
// floating-point less-than
- RegisterOperator("<", &types.FuncValue{
+ RegisterOperator("<", &types.SimpleFn{
T: types.NewType("func(a float, b float) bool"),
V: func(input []types.Value) (types.Value, error) {
// TODO: should we do an epsilon check?
@@ -235,7 +235,7 @@ func init() {
},
})
// greater-than
- RegisterOperator(">", &types.FuncValue{
+ RegisterOperator(">", &types.SimpleFn{
T: types.NewType("func(a int, b int) bool"),
V: func(input []types.Value) (types.Value, error) {
return &types.BoolValue{
@@ -244,7 +244,7 @@ func init() {
},
})
// floating-point greater-than
- RegisterOperator(">", &types.FuncValue{
+ RegisterOperator(">", &types.SimpleFn{
T: types.NewType("func(a float, b float) bool"),
V: func(input []types.Value) (types.Value, error) {
// TODO: should we do an epsilon check?
@@ -254,7 +254,7 @@ func init() {
},
})
// less-than-equal
- RegisterOperator("<=", &types.FuncValue{
+ RegisterOperator("<=", &types.SimpleFn{
T: types.NewType("func(a int, b int) bool"),
V: func(input []types.Value) (types.Value, error) {
return &types.BoolValue{
@@ -263,7 +263,7 @@ func init() {
},
})
// floating-point less-than-equal
- RegisterOperator("<=", &types.FuncValue{
+ RegisterOperator("<=", &types.SimpleFn{
T: types.NewType("func(a float, b float) bool"),
V: func(input []types.Value) (types.Value, error) {
// TODO: should we do an epsilon check?
@@ -273,7 +273,7 @@ func init() {
},
})
// greater-than-equal
- RegisterOperator(">=", &types.FuncValue{
+ RegisterOperator(">=", &types.SimpleFn{
T: types.NewType("func(a int, b int) bool"),
V: func(input []types.Value) (types.Value, error) {
return &types.BoolValue{
@@ -282,7 +282,7 @@ func init() {
},
})
// floating-point greater-than-equal
- RegisterOperator(">=", &types.FuncValue{
+ RegisterOperator(">=", &types.SimpleFn{
T: types.NewType("func(a float, b float) bool"),
V: func(input []types.Value) (types.Value, error) {
// TODO: should we do an epsilon check?
@@ -295,7 +295,7 @@ func init() {
// logical and
// TODO: is there a way for the engine to have
// short-circuit operators, and does it matter?
- RegisterOperator("&&", &types.FuncValue{
+ RegisterOperator("&&", &types.SimpleFn{
T: types.NewType("func(a bool, b bool) bool"),
V: func(input []types.Value) (types.Value, error) {
return &types.BoolValue{
@@ -304,7 +304,7 @@ func init() {
},
})
// logical or
- RegisterOperator("||", &types.FuncValue{
+ RegisterOperator("||", &types.SimpleFn{
T: types.NewType("func(a bool, b bool) bool"),
V: func(input []types.Value) (types.Value, error) {
return &types.BoolValue{
@@ -314,7 +314,7 @@ func init() {
})
// logical not (unary operator)
- RegisterOperator("!", &types.FuncValue{
+ RegisterOperator("!", &types.SimpleFn{
T: types.NewType("func(a bool) bool"),
V: func(input []types.Value) (types.Value, error) {
return &types.BoolValue{
@@ -324,7 +324,7 @@ func init() {
})
// pi operator (this is an easter egg to demo a zero arg operator)
- RegisterOperator("π", &types.FuncValue{
+ RegisterOperator("π", &types.SimpleFn{
T: types.NewType("func() float"),
V: func(input []types.Value) (types.Value, error) {
return &types.FloatValue{
@@ -339,14 +339,14 @@ func init() {
var _ interfaces.PolyFunc = &OperatorFunc{} // ensure it meets this expectation
// OperatorFuncs maps an operator to a list of callable function values.
-var OperatorFuncs = make(map[string][]*types.FuncValue) // must initialize
+var OperatorFuncs = make(map[string][]*types.SimpleFn) // must initialize
// RegisterOperator registers the given string operator and function value
// implementation with the mini-database for this generalized, static,
// polymorphic operator implementation.
-func RegisterOperator(operator string, fn *types.FuncValue) {
+func RegisterOperator(operator string, fn *types.SimpleFn) {
if _, exists := OperatorFuncs[operator]; !exists {
- OperatorFuncs[operator] = []*types.FuncValue{} // init
+ OperatorFuncs[operator] = []*types.SimpleFn{} // init
}
for _, f := range OperatorFuncs[operator] {
@@ -474,7 +474,7 @@ func (obj *OperatorFunc) argNames() ([]string, error) {
// findFunc tries to find the first available registered operator function that
// matches the Operator/Type pattern requested. If none is found it returns nil.
-func (obj *OperatorFunc) findFunc(operator string) *types.FuncValue {
+func (obj *OperatorFunc) findFunc(operator string) *types.SimpleFn {
fns, exists := OperatorFuncs[operator]
if !exists {
return nil
@@ -866,7 +866,7 @@ func (obj *OperatorFunc) Init(init *interfaces.Init) error {
// Stream returns the changing values that this func has over time.
func (obj *OperatorFunc) Stream(ctx context.Context) error {
var op, lastOp string
- var fn *types.FuncValue
+ var fn *types.SimpleFn
defer close(obj.init.Output) // the sender closes
for {
select {
diff --git a/lang/funcs/simple/channel_based_sink_func.go b/lang/funcs/simple/channel_based_sink_func.go
new file mode 100644
index 0000000000..55a7b17285
--- /dev/null
+++ b/lang/funcs/simple/channel_based_sink_func.go
@@ -0,0 +1,113 @@
+// Mgmt
+// Copyright (C) 2013-2022+ James Shubin and the project contributors
+// Written by James Shubin and the project contributors
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+package simple
+
+import (
+ "fmt"
+
+ "github.com/purpleidea/mgmt/lang/interfaces"
+ "github.com/purpleidea/mgmt/lang/types"
+)
+
+// A Func which receives values from upstream nodes and emits them to a Chan.
+type ChannelBasedSinkFunc struct {
+ Name string
+
+ Chan chan types.Value
+ Type *types.Type
+
+ init *interfaces.Init
+ last types.Value // last value received to use for diff
+
+ closeChan chan struct{}
+}
+
+// String returns a simple name for this function. This is needed so this struct
+// can satisfy the pgraph.Vertex interface.
+func (obj *ChannelBasedSinkFunc) String() string {
+ return obj.Name
+}
+
+// ArgGen returns the Nth arg name for this function.
+func (obj *ChannelBasedSinkFunc) ArgGen(index int) (string, error) {
+ if index != 1 {
+ return "", fmt.Errorf("the ChannelBasedSinkFunc only has one argument")
+ }
+ return "arg", nil
+}
+
+// Validate makes sure we've built our struct properly. It is usually unused for
+// normal functions that users can use directly.
+func (obj *ChannelBasedSinkFunc) Validate() error {
+ if obj.Chan == nil {
+ return fmt.Errorf("the Chan was not set")
+ }
+ return nil
+}
+
+// Info returns some static info about itself.
+func (obj *ChannelBasedSinkFunc) Info() *interfaces.Info {
+ return &interfaces.Info{
+ Pure: false,
+ Memo: false,
+ Sig: types.NewType(fmt.Sprintf("func(%s)", obj.Type)),
+ Err: obj.Validate(),
+ }
+}
+
+// Init runs some startup code for this function.
+func (obj *ChannelBasedSinkFunc) Init(init *interfaces.Init) error {
+ obj.init = init
+ obj.closeChan = make(chan struct{})
+ return nil
+}
+
+// Stream returns the changing values that this func has over time.
+func (obj *ChannelBasedSinkFunc) Stream() error {
+ defer close(obj.Chan) // the sender closes
+ close(obj.init.Output) // we will never send any value downstream
+
+ for {
+ select {
+ case input, ok := <-obj.init.Input:
+ if !ok {
+ return nil // can't output any more
+ }
+
+ if obj.last != nil && input.Cmp(obj.last) == nil {
+ continue // value didn't change, skip it
+ }
+ obj.last = input // store so we can send after this select
+
+ case <-obj.closeChan:
+ return nil
+ }
+
+ select {
+ case obj.Chan <- obj.last: // send
+ case <-obj.closeChan:
+ return nil
+ }
+ }
+}
+
+// Close runs some shutdown code for this function and turns off the stream.
+func (obj *ChannelBasedSinkFunc) Close() error {
+ close(obj.closeChan)
+ return nil
+}
diff --git a/lang/funcs/simple/channel_based_source_func.go b/lang/funcs/simple/channel_based_source_func.go
new file mode 100644
index 0000000000..e63f81b32f
--- /dev/null
+++ b/lang/funcs/simple/channel_based_source_func.go
@@ -0,0 +1,110 @@
+// Mgmt
+// Copyright (C) 2013-2022+ James Shubin and the project contributors
+// Written by James Shubin and the project contributors
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+package simple
+
+import (
+ "fmt"
+
+ "github.com/purpleidea/mgmt/lang/interfaces"
+ "github.com/purpleidea/mgmt/lang/types"
+)
+
+// A Func which receives values from a Chan and emits them to the downstream
+// nodes.
+type ChannelBasedSourceFunc struct {
+ Name string
+
+ Chan chan types.Value
+ Type *types.Type
+
+ init *interfaces.Init
+ last types.Value // last value received to use for diff
+
+ closeChan chan struct{}
+}
+
+// String returns a simple name for this function. This is needed so this struct
+// can satisfy the pgraph.Vertex interface.
+func (obj *ChannelBasedSourceFunc) String() string {
+ return "ChannelBasedSourceFunc"
+}
+
+// ArgGen returns the Nth arg name for this function.
+func (obj *ChannelBasedSourceFunc) ArgGen(index int) (string, error) {
+ return "", fmt.Errorf("the ChannelBasedSourceFunc doesn't have any arguments")
+}
+
+// Validate makes sure we've built our struct properly. It is usually unused for
+// normal functions that users can use directly.
+func (obj *ChannelBasedSourceFunc) Validate() error {
+ if obj.Chan == nil {
+ return fmt.Errorf("the Chan was not set")
+ }
+ return nil
+}
+
+// Info returns some static info about itself.
+func (obj *ChannelBasedSourceFunc) Info() *interfaces.Info {
+ return &interfaces.Info{
+ Pure: false,
+ Memo: false,
+ Sig: types.NewType(fmt.Sprintf("func() %s", obj.Type)),
+ Err: obj.Validate(),
+ }
+}
+
+// Init runs some startup code for this function.
+func (obj *ChannelBasedSourceFunc) Init(init *interfaces.Init) error {
+ obj.init = init
+ obj.closeChan = make(chan struct{})
+ return nil
+}
+
+// Stream returns the changing values that this func has over time.
+func (obj *ChannelBasedSourceFunc) Stream() error {
+ defer close(obj.init.Output) // the sender closes
+
+ for {
+ select {
+ case input, ok := <-obj.Chan:
+ if !ok {
+ return nil // can't output any more
+ }
+
+ if obj.last != nil && input.Cmp(obj.last) == nil {
+ continue // value didn't change, skip it
+ }
+ obj.last = input // store so we can send after this select
+
+ case <-obj.closeChan:
+ return nil
+ }
+
+ select {
+ case obj.init.Output <- obj.last: // send
+ case <-obj.closeChan:
+ return nil
+ }
+ }
+}
+
+// Close runs some shutdown code for this function and turns off the stream.
+func (obj *ChannelBasedSourceFunc) Close() error {
+ close(obj.closeChan)
+ return nil
+}
diff --git a/lang/funcs/simple/simple.go b/lang/funcs/simple/simple.go
index f9d4be05b1..6e2de2c3fb 100644
--- a/lang/funcs/simple/simple.go
+++ b/lang/funcs/simple/simple.go
@@ -21,9 +21,11 @@ import (
"context"
"fmt"
+ "github.com/purpleidea/mgmt/lang/fancyfunc"
"github.com/purpleidea/mgmt/lang/funcs"
"github.com/purpleidea/mgmt/lang/interfaces"
"github.com/purpleidea/mgmt/lang/types"
+ "github.com/purpleidea/mgmt/pgraph"
"github.com/purpleidea/mgmt/util/errwrap"
)
@@ -35,11 +37,11 @@ const (
)
// RegisteredFuncs maps a function name to the corresponding static, pure func.
-var RegisteredFuncs = make(map[string]*types.FuncValue) // must initialize
+var RegisteredFuncs = make(map[string]*types.SimpleFn) // must initialize
// Register registers a simple, static, pure function. It is easier to use than
// the raw function API, but also limits you to simple, static, pure functions.
-func Register(name string, fn *types.FuncValue) {
+func Register(name string, fn *types.SimpleFn) {
if _, exists := RegisteredFuncs[name]; exists {
panic(fmt.Sprintf("a simple func named %s is already registered", name))
}
@@ -64,7 +66,7 @@ func Register(name string, fn *types.FuncValue) {
// ModuleRegister is exactly like Register, except that it registers within a
// named module. This is a helper function.
-func ModuleRegister(module, name string, fn *types.FuncValue) {
+func ModuleRegister(module, name string, fn *types.SimpleFn) {
Register(module+funcs.ModuleSep+name, fn)
}
@@ -73,7 +75,7 @@ func ModuleRegister(module, name string, fn *types.FuncValue) {
type WrappedFunc struct {
Name string
- Fn *types.FuncValue
+ Fn *types.SimpleFn
init *interfaces.Init
last types.Value // last value received to use for diff
@@ -184,3 +186,42 @@ func (obj *WrappedFunc) Stream(ctx context.Context) error {
}
}
}
+
+// In the following set of conversion functions, a "constant" Func is a node
+// with in-degree zero which always outputs the same function value, while a
+// "direct" Func is a node with one upstream node for each of the function's
+// arguments.
+
+func FuncValueToConstFunc(obj *fancyfunc.FuncValue) interfaces.Func {
+ return &funcs.ConstFunc{
+ Value: obj,
+ NameHint: "FuncValue",
+ }
+}
+
+func SimpleFnToDirectFunc(obj *types.SimpleFn) interfaces.Func {
+ return &WrappedFunc{
+ Fn: obj,
+ }
+}
+
+func SimpleFnToFuncValue(obj *types.SimpleFn) *fancyfunc.FuncValue {
+ return &fancyfunc.FuncValue{
+ V: func(txn interfaces.Txn, args []interfaces.Func) (interfaces.Func, error) {
+ wrappedFunc := SimpleFnToDirectFunc(obj)
+ txn.AddVertex(wrappedFunc)
+ for i, arg := range args {
+ argName := obj.T.Ord[i]
+ txn.AddEdge(arg, wrappedFunc, &interfaces.FuncEdge{
+ Args: []string{argName},
+ })
+ }
+ return wrappedFunc, nil
+ },
+ T: obj.T,
+ }
+}
+
+func SimpleFnToConstFunc(obj *types.SimpleFn) interfaces.Func {
+ return FuncValueToConstFunc(SimpleFnToFuncValue(obj))
+}
diff --git a/lang/funcs/simple/structs_call.go b/lang/funcs/simple/structs_call.go
new file mode 100644
index 0000000000..d6ebb2c4ff
--- /dev/null
+++ b/lang/funcs/simple/structs_call.go
@@ -0,0 +1,208 @@
+// Mgmt
+// Copyright (C) 2013-2023+ James Shubin and the project contributors
+// Written by James Shubin and the project contributors
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+//package structs
+package simple
+
+import (
+ "fmt"
+
+ "github.com/purpleidea/mgmt/lang/fancyfunc"
+ "github.com/purpleidea/mgmt/lang/interfaces"
+ "github.com/purpleidea/mgmt/lang/types"
+ "github.com/purpleidea/mgmt/pgraph"
+ "github.com/purpleidea/mgmt/util/errwrap"
+)
+
+const (
+ // CallFuncName is the unique name identifier for this function.
+ CallFuncName = "call"
+
+ // How to name the edge which connects the input function to CallFunc.
+ CallFuncArgNameFunction = "fn"
+)
+
+// CallFunc receives a function from upstream, but not the arguments. Instead,
+// the Funcs which emit those arguments must be specified at construction time.
+// The arguments are connected to the received FuncValues in such a way that
+// CallFunc emits the result of applying the function to the arguments.
+type CallFunc struct {
+ Type *types.Type // the type of the result of applying the function
+ FuncType *types.Type // the type of the function
+
+ ArgVertices []interfaces.Func
+
+ init *interfaces.Init
+ reversibleTxn *interfaces.ReversibleTxn
+
+ lastFuncValue *fancyfunc.FuncValue // remember the last function value
+
+ closeChan chan struct{}
+}
+
+// String returns a simple name for this function. This is needed so this struct
+// can satisfy the pgraph.Vertex interface.
+func (obj *CallFunc) String() string {
+ return CallFuncName
+}
+
+// Validate makes sure we've built our struct properly.
+func (obj *CallFunc) Validate() error {
+ if obj.Type == nil {
+ return fmt.Errorf("must specify a type")
+ }
+ if obj.FuncType == nil {
+ return fmt.Errorf("must specify a func type")
+ }
+ typ := obj.FuncType
+ // we only care about the output type of calling our func
+ if err := obj.Type.Cmp(typ.Out); err != nil {
+ return errwrap.Wrapf(err, "call expr type must match func out type")
+ }
+ if len(obj.ArgVertices) != len(typ.Ord) {
+ return fmt.Errorf("number of arg Funcs must match number of func args in the type")
+ }
+
+ return nil
+}
+
+// Info returns some static info about itself.
+func (obj *CallFunc) Info() *interfaces.Info {
+ var typ *types.Type
+ if obj.Type != nil && obj.FuncType != nil { // don't panic if called speculatively
+ typ = types.NewType(fmt.Sprintf("func(%s %s) %s", CallFuncArgNameFunction, obj.FuncType, obj.Type))
+ }
+
+ return &interfaces.Info{
+ Pure: true,
+ Memo: false, // TODO: ???
+ Sig: typ,
+ Err: obj.Validate(),
+ }
+}
+
+// Init runs some startup code for this composite function.
+func (obj *CallFunc) Init(init *interfaces.Init) error {
+ obj.init = init
+ obj.reversibleTxn = &interfaces.ReversibleTxn{
+ InnerTxn: init.Txn,
+ }
+ obj.lastFuncValue = nil
+ obj.closeChan = make(chan struct{})
+ return nil
+}
+
+// Stream takes an input struct in the format as described in the Func and Graph
+// methods of the Expr, and returns the actual expected value as a stream based
+// on the changing inputs to that value.
+func (obj *CallFunc) Stream() error {
+ defer close(obj.init.Output) // the sender closes
+
+ // An initially-closed channel from which we receive output lists from the
+ // subgraph. This channel is reset when the subgraph is recreated.
+ outputChan := make(chan types.Value)
+ close(outputChan)
+
+ // Create a subgraph which looks as follows. Most of the nodes are elided
+ // because we don't know which nodes the FuncValues will create.
+ //
+ // digraph {
+ // ArgVertices[0] -> ...
+ // ArgVertices[1] -> ...
+ // ArgVertices[2] -> ...
+ //
+ // outputFunc -> "subgraphOutput"
+ // }
+ replaceSubGraph := func(
+ newFuncValue *fancyfunc.FuncValue,
+ ) error {
+ // delete the old subgraph
+ obj.reversibleTxn.Reset()
+
+ // create the new subgraph
+
+ outputFunc, err := newFuncValue.Call(obj.reversibleTxn, obj.ArgVertices)
+ if err != nil {
+ return errwrap.Wrapf(err, "could not call newFuncValue.Call()")
+ }
+
+ outputChan = make(chan types.Value)
+ subgraphOutput := &ChannelBasedSinkFunc{
+ Name: "subgraphOutput",
+ Chan: outputChan,
+ Type: obj.Type,
+ }
+ obj.reversibleTxn.AddVertex(subgraphOutput)
+ obj.reversibleTxn.AddEdge(outputFunc, subgraphOutput, &pgraph.SimpleEdge{Name: "arg"})
+
+ obj.reversibleTxn.Commit()
+
+ return nil
+ }
+ defer func() {
+ obj.reversibleTxn.Reset()
+ obj.reversibleTxn.Commit()
+ }()
+
+ canReceiveMoreFuncValues := true
+ canReceiveMoreOutputValues := true
+ for {
+ select {
+ case input, ok := <-obj.init.Input:
+ if !ok {
+ canReceiveMoreFuncValues = false
+ } else {
+ newFuncValue := input.Struct()[CallFuncArgNameFunction].(*fancyfunc.FuncValue)
+
+ // If we have a new function, then we need to replace the
+ // subgraph with a new one that uses the new function.
+ if newFuncValue != obj.lastFuncValue {
+ if err := replaceSubGraph(newFuncValue); err != nil {
+ return errwrap.Wrapf(err, "could not replace subgraph")
+ }
+ canReceiveMoreOutputValues = true
+ obj.lastFuncValue = newFuncValue
+ }
+ }
+
+ case outputValue, ok := <-outputChan:
+ // send the new output value downstream
+ if !ok {
+ canReceiveMoreOutputValues = false
+ } else {
+ select {
+ case obj.init.Output <- outputValue:
+ case <-obj.closeChan:
+ return nil
+ }
+ }
+
+ case <-obj.closeChan:
+ return nil
+ }
+
+ if !canReceiveMoreFuncValues && !canReceiveMoreOutputValues {
+ return nil
+ }
+ }
+}
+
+// Close runs some shutdown code for this function and turns off the stream.
+func (obj *CallFunc) Close() error {
+ close(obj.closeChan)
+ return nil
+}
diff --git a/lang/funcs/simplepoly/simplepoly.go b/lang/funcs/simplepoly/simplepoly.go
index 26102cd16e..501ab6be59 100644
--- a/lang/funcs/simplepoly/simplepoly.go
+++ b/lang/funcs/simplepoly/simplepoly.go
@@ -45,7 +45,7 @@ const (
)
// RegisteredFuncs maps a function name to the corresponding static, pure funcs.
-var RegisteredFuncs = make(map[string][]*types.FuncValue) // must initialize
+var RegisteredFuncs = make(map[string][]*types.SimpleFn) // must initialize
// Register registers a simple, static, pure, polymorphic function. It is easier
// to use than the raw function API, but also limits you to small, finite
@@ -55,7 +55,7 @@ var RegisteredFuncs = make(map[string][]*types.FuncValue) // must initialize
// not possible with this API. Implementing a function like `printf` would not
// be possible. Implementing a function which counts the number of elements in a
// list would be.
-func Register(name string, fns []*types.FuncValue) {
+func Register(name string, fns []*types.SimpleFn) {
if _, exists := RegisteredFuncs[name]; exists {
panic(fmt.Sprintf("a simple polyfunc named %s is already registered", name))
}
@@ -96,13 +96,13 @@ func Register(name string, fns []*types.FuncValue) {
// ModuleRegister is exactly like Register, except that it registers within a
// named module. This is a helper function.
-func ModuleRegister(module, name string, fns []*types.FuncValue) {
+func ModuleRegister(module, name string, fns []*types.SimpleFn) {
Register(module+funcs.ModuleSep+name, fns)
}
// consistentArgs returns the list of arg names across all the functions or
// errors if one consistent list could not be found.
-func consistentArgs(fns []*types.FuncValue) ([]string, error) {
+func consistentArgs(fns []*types.SimpleFn) ([]string, error) {
if len(fns) == 0 {
return nil, fmt.Errorf("no functions specified for simple polyfunc")
}
@@ -136,9 +136,9 @@ var _ interfaces.PolyFunc = &WrappedFunc{} // ensure it meets this expectation
type WrappedFunc struct {
Name string
- Fns []*types.FuncValue // list of possible functions
+ Fns []*types.SimpleFn // list of possible functions
- fn *types.FuncValue // the concrete version of our chosen function
+ fn *types.SimpleFn // the concrete version of our chosen function
init *interfaces.Init
last types.Value // last value received to use for diff
@@ -494,18 +494,9 @@ func (obj *WrappedFunc) Build(typ *types.Type) (*types.Type, error) {
// buildFunction builds our concrete static function, from the potentially
// abstract, possibly variant containing list of functions.
-func (obj *WrappedFunc) buildFunction(typ *types.Type, ix int) *types.Type {
- cp := obj.Fns[ix].Copy()
- fn, ok := cp.(*types.FuncValue)
- if !ok {
- panic("unexpected type")
- }
- obj.fn = fn
- if obj.fn.T == nil { // XXX: should this even ever happen? What about argnames here?
- obj.fn.T = typ.Copy() // overwrites any contained "variant" type
- }
-
- return obj.fn.T
+func (obj *WrappedFunc) buildFunction(typ *types.Type, ix int) {
+ obj.fn = obj.Fns[ix].Copy()
+ obj.fn.T = typ.Copy() // overwrites any contained "variant" type
}
// Validate makes sure we've built our struct properly. It is usually unused for
diff --git a/lang/funcs/structs/call.go b/lang/funcs/structs/call.go
deleted file mode 100644
index 80285000c3..0000000000
--- a/lang/funcs/structs/call.go
+++ /dev/null
@@ -1,183 +0,0 @@
-// Mgmt
-// Copyright (C) 2013-2023+ James Shubin and the project contributors
-// Written by James Shubin and the project contributors
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with this program. If not, see .
-
-package structs
-
-import (
- "context"
- "fmt"
-
- "github.com/purpleidea/mgmt/lang/interfaces"
- "github.com/purpleidea/mgmt/lang/types"
- "github.com/purpleidea/mgmt/util/errwrap"
-)
-
-const (
- // CallFuncName is the unique name identifier for this function.
- CallFuncName = "call"
-)
-
-// CallFunc is a function that takes in a function and all the args, and passes
-// through the results of running the function call.
-type CallFunc struct {
- Type *types.Type // this is the type of the var's value that we hold
- FuncType *types.Type
- Edge string // name of the edge used (typically starts with: `call:`)
- //Func interfaces.Func // this isn't actually used in the Stream :/
- //Fn *types.FuncValue // pass in the actual function instead of Edge
-
- // Indexed specifies that args are accessed by index instead of name.
- // This is currently unused.
- Indexed bool
-
- init *interfaces.Init
- last types.Value // last value received to use for diff
- result types.Value // last calculated output
-}
-
-// String returns a simple name for this function. This is needed so this struct
-// can satisfy the pgraph.Vertex interface.
-func (obj *CallFunc) String() string {
- return CallFuncName
-}
-
-// Validate makes sure we've built our struct properly.
-func (obj *CallFunc) Validate() error {
- if obj.Type == nil {
- return fmt.Errorf("must specify a type")
- }
- if obj.FuncType == nil {
- return fmt.Errorf("must specify a func type")
- }
- // TODO: maybe we can remove this if we use this for core functions...
- if obj.Edge == "" {
- return fmt.Errorf("must specify an edge name")
- }
- typ := obj.FuncType
- // we only care about the output type of calling our func
- if err := obj.Type.Cmp(typ.Out); err != nil {
- return errwrap.Wrapf(err, "call expr type must match func out type")
- }
-
- return nil
-}
-
-// Info returns some static info about itself.
-func (obj *CallFunc) Info() *interfaces.Info {
- var typ *types.Type
- if obj.Type != nil { // don't panic if called speculatively
- typ = &types.Type{
- Kind: types.KindFunc, // function type
- Map: make(map[string]*types.Type),
- Ord: []string{},
- Out: obj.Type, // this is the output type for the expression
- }
-
- sig := obj.FuncType
- if obj.Edge != "" {
- typ.Map[obj.Edge] = sig // we get a function in
- typ.Ord = append(typ.Ord, obj.Edge)
- }
-
- // add any incoming args
- for _, key := range sig.Ord { // sig.Out, not sig!
- typ.Map[key] = sig.Map[key]
- typ.Ord = append(typ.Ord, key)
- }
- }
-
- return &interfaces.Info{
- Pure: true,
- Memo: false, // TODO: ???
- Sig: typ,
- Err: obj.Validate(),
- }
-}
-
-// Init runs some startup code for this composite function.
-func (obj *CallFunc) Init(init *interfaces.Init) error {
- obj.init = init
- return nil
-}
-
-// Stream takes an input struct in the format as described in the Func and Graph
-// methods of the Expr, and returns the actual expected value as a stream based
-// on the changing inputs to that value.
-func (obj *CallFunc) Stream(ctx context.Context) error {
- defer close(obj.init.Output) // the sender closes
- for {
- select {
- case input, ok := <-obj.init.Input:
- if !ok {
- return nil // can't output any more
- }
- //if err := input.Type().Cmp(obj.Info().Sig.Input); err != nil {
- // return errwrap.Wrapf(err, "wrong function input")
- //}
- if obj.last != nil && input.Cmp(obj.last) == nil {
- continue // value didn't change, skip it
- }
- obj.last = input // store for next
-
- st := input.(*types.StructValue) // must be!
-
- // get the function
- fn, exists := st.Lookup(obj.Edge)
- if !exists {
- return fmt.Errorf("missing expected input argument `%s`", obj.Edge)
- }
-
- // get the arguments to call the function
- args := []types.Value{}
- typ := obj.FuncType
- for ix, key := range typ.Ord { // sig!
- if obj.Indexed {
- key = fmt.Sprintf("%d", ix)
- }
- value, exists := st.Lookup(key)
- // TODO: replace with:
- //value, exists := st.Lookup(fmt.Sprintf("arg:%s", key))
- if !exists {
- return fmt.Errorf("missing expected input argument `%s`", key)
- }
- args = append(args, value)
- }
-
- // actually call it
- result, err := fn.(*types.FuncValue).Call(args)
- if err != nil {
- return errwrap.Wrapf(err, "error calling function")
- }
-
- // skip sending an update...
- if obj.result != nil && result.Cmp(obj.result) == nil {
- continue // result didn't change
- }
- obj.result = result // store new result
-
- case <-ctx.Done():
- return nil
- }
-
- select {
- case obj.init.Output <- obj.result: // send
- // pass
- case <-ctx.Done():
- return nil
- }
- }
-}
diff --git a/lang/funcs/structs/function.go b/lang/funcs/structs/function.go
deleted file mode 100644
index 330fa29668..0000000000
--- a/lang/funcs/structs/function.go
+++ /dev/null
@@ -1,206 +0,0 @@
-// Mgmt
-// Copyright (C) 2013-2023+ James Shubin and the project contributors
-// Written by James Shubin and the project contributors
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with this program. If not, see .
-
-package structs
-
-import (
- "context"
- "fmt"
-
- "github.com/purpleidea/mgmt/lang/funcs"
- "github.com/purpleidea/mgmt/lang/interfaces"
- "github.com/purpleidea/mgmt/lang/types"
- "github.com/purpleidea/mgmt/util/errwrap"
-)
-
-const (
- // FunctionFuncName is the unique name identifier for this function.
- FunctionFuncName = "function"
-)
-
-// FunctionFunc is a function that passes through the function body it receives.
-type FunctionFunc struct {
- Type *types.Type // this is the type of the function that we hold
- Edge string // name of the edge used (typically "body")
- Func interfaces.Func
- Fn *types.FuncValue
-
- init *interfaces.Init
- last types.Value // last value received to use for diff
- result types.Value // last calculated output
-}
-
-// String returns a simple name for this function. This is needed so this struct
-// can satisfy the pgraph.Vertex interface.
-func (obj *FunctionFunc) String() string {
- return FunctionFuncName
-}
-
-// fn returns the function that wraps the Func interface if that API is used.
-func (obj *FunctionFunc) fn() (*types.FuncValue, error) {
- fn := func(args []types.Value) (types.Value, error) {
- // FIXME: can we run a recursive engine
- // to support running non-pure functions?
- if !obj.Func.Info().Pure {
- return nil, fmt.Errorf("only pure functions can be used by value")
- }
-
- // XXX: this might not be needed anymore...
- return funcs.PureFuncExec(obj.Func, args)
- }
-
- result := types.NewFunc(obj.Type) // new func
- if err := result.Set(fn); err != nil {
- return nil, errwrap.Wrapf(err, "can't build func from built-in")
- }
-
- return result, nil
-}
-
-// Validate makes sure we've built our struct properly.
-func (obj *FunctionFunc) Validate() error {
- if obj.Type == nil {
- return fmt.Errorf("must specify a type")
- }
- if obj.Type.Kind != types.KindFunc {
- return fmt.Errorf("can't use type `%s`", obj.Type.String())
- }
- if obj.Edge == "" && obj.Func == nil && obj.Fn == nil {
- return fmt.Errorf("must specify an Edge, Func, or Fn")
- }
-
- if obj.Fn != nil && obj.Fn.Type() != obj.Type {
- return fmt.Errorf("type of Fn did not match input Type")
- }
-
- return nil
-}
-
-// Info returns some static info about itself.
-func (obj *FunctionFunc) Info() *interfaces.Info {
- var typ *types.Type
- if obj.Type != nil { // don't panic if called speculatively
- typ = &types.Type{
- Kind: types.KindFunc, // function type
- Map: make(map[string]*types.Type),
- Ord: []string{},
- Out: obj.Type, // after the function is called it's this...
- }
-
- // type of body is what we'd get by running the function (what's inside)
- if obj.Edge != "" {
- typ.Map[obj.Edge] = obj.Type.Out
- typ.Ord = append(typ.Ord, obj.Edge)
- }
- }
-
- pure := true // assume true
- if obj.Func != nil {
- pure = obj.Func.Info().Pure
- }
-
- return &interfaces.Info{
- Pure: pure, // TODO: can we guarantee this?
- Memo: false, // TODO: ???
- Sig: typ,
- Err: obj.Validate(),
- }
-}
-
-// Init runs some startup code for this composite function.
-func (obj *FunctionFunc) Init(init *interfaces.Init) error {
- obj.init = init
- return nil
-}
-
-// Stream takes an input struct in the format as described in the Func and Graph
-// methods of the Expr, and returns the actual expected value as a stream based
-// on the changing inputs to that value.
-func (obj *FunctionFunc) Stream(ctx context.Context) error {
- defer close(obj.init.Output) // the sender closes
- for {
- select {
- case input, ok := <-obj.init.Input:
- if !ok {
- if obj.Edge != "" { // then it's not a built-in
- return nil // can't output any more
- }
-
- var result *types.FuncValue
-
- if obj.Fn != nil {
- result = obj.Fn
- } else {
- var err error
- result, err = obj.fn()
- if err != nil {
- return err
- }
- }
-
- // if we never had input args, send the function
- select {
- case obj.init.Output <- result: // send
- // pass
- case <-ctx.Done():
- return nil
- }
-
- return nil
- }
- //if err := input.Type().Cmp(obj.Info().Sig.Input); err != nil {
- // return errwrap.Wrapf(err, "wrong function input")
- //}
- if obj.last != nil && input.Cmp(obj.last) == nil {
- continue // value didn't change, skip it
- }
- obj.last = input // store for next
-
- var result types.Value
-
- st := input.(*types.StructValue) // must be!
- value, exists := st.Lookup(obj.Edge) // single argName
- if !exists {
- return fmt.Errorf("missing expected input argument `%s`", obj.Edge)
- }
-
- result = obj.Type.New() // new func
- fn := func([]types.Value) (types.Value, error) {
- return value, nil
- }
- if err := result.(*types.FuncValue).Set(fn); err != nil {
- return errwrap.Wrapf(err, "can't build func with body")
- }
-
- // skip sending an update...
- if obj.result != nil && result.Cmp(obj.result) == nil {
- continue // result didn't change
- }
- obj.result = result // store new result
-
- case <-ctx.Done():
- return nil
- }
-
- select {
- case obj.init.Output <- obj.result: // send
- // pass
- case <-ctx.Done():
- return nil
- }
- }
-}
diff --git a/lang/funcs/structs/var.go b/lang/funcs/structs/var.go
deleted file mode 100644
index 3d3d66fc79..0000000000
--- a/lang/funcs/structs/var.go
+++ /dev/null
@@ -1,135 +0,0 @@
-// Mgmt
-// Copyright (C) 2013-2023+ James Shubin and the project contributors
-// Written by James Shubin and the project contributors
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with this program. If not, see .
-
-package structs
-
-import (
- "context"
- "fmt"
- "strings"
-
- "github.com/purpleidea/mgmt/lang/interfaces"
- "github.com/purpleidea/mgmt/lang/types"
- //"github.com/purpleidea/mgmt/util/errwrap"
-)
-
-const (
- // VarFuncName is the unique name identifier for this function.
- VarFuncName = "var"
-)
-
-// VarFunc is a function that passes through a function that came from a bind
-// lookup. It exists so that the reactive function engine type checks correctly.
-type VarFunc struct {
- Type *types.Type // this is the type of the var's value that we hold
- Edge string // name of the edge used
- //Func interfaces.Func // this isn't actually used in the Stream :/
-
- init *interfaces.Init
- last types.Value // last value received to use for diff
- result types.Value // last calculated output
-}
-
-// String returns a simple name for this function. This is needed so this struct
-// can satisfy the pgraph.Vertex interface.
-func (obj *VarFunc) String() string {
- // XXX: This is a bit of a temporary hack to display it nicely.
- return fmt.Sprintf("%s(%s)", VarFuncName, strings.TrimPrefix(obj.Edge, "var:"))
-}
-
-// Validate makes sure we've built our struct properly.
-func (obj *VarFunc) Validate() error {
- if obj.Type == nil {
- return fmt.Errorf("must specify a type")
- }
- if obj.Edge == "" {
- return fmt.Errorf("must specify an edge name")
- }
- return nil
-}
-
-// Info returns some static info about itself.
-func (obj *VarFunc) Info() *interfaces.Info {
- var typ *types.Type
- if obj.Type != nil { // don't panic if called speculatively
- typ = &types.Type{
- Kind: types.KindFunc, // function type
- Map: map[string]*types.Type{obj.Edge: obj.Type},
- Ord: []string{obj.Edge},
- Out: obj.Type, // this is the output type for the expression
- }
- }
-
- return &interfaces.Info{
- Pure: true,
- Memo: false, // TODO: ???
- Sig: typ,
- Err: obj.Validate(),
- }
-}
-
-// Init runs some startup code for this composite function.
-func (obj *VarFunc) Init(init *interfaces.Init) error {
- obj.init = init
- return nil
-}
-
-// Stream takes an input struct in the format as described in the Func and Graph
-// methods of the Expr, and returns the actual expected value as a stream based
-// on the changing inputs to that value.
-func (obj *VarFunc) Stream(ctx context.Context) error {
- defer close(obj.init.Output) // the sender closes
- for {
- select {
- case input, ok := <-obj.init.Input:
- if !ok {
- return nil // can't output any more
- }
- //if err := input.Type().Cmp(obj.Info().Sig.Input); err != nil {
- // return errwrap.Wrapf(err, "wrong function input")
- //}
- if obj.last != nil && input.Cmp(obj.last) == nil {
- continue // value didn't change, skip it
- }
- obj.last = input // store for next
-
- var result types.Value
- st := input.(*types.StructValue) // must be!
- value, exists := st.Lookup(obj.Edge)
- if !exists {
- return fmt.Errorf("missing expected input argument `%s`", obj.Edge)
- }
- result = value
-
- // skip sending an update...
- if obj.result != nil && result.Cmp(obj.result) == nil {
- continue // result didn't change
- }
- obj.result = result // store new result
-
- case <-ctx.Done():
- return nil
- }
-
- select {
- case obj.init.Output <- obj.result: // send
- // pass
- case <-ctx.Done():
- return nil
- }
- }
-}
diff --git a/lang/funcs/structs/const.go b/lang/funcs/structs_const.go
similarity index 98%
rename from lang/funcs/structs/const.go
rename to lang/funcs/structs_const.go
index 7b6e218784..33fff90460 100644
--- a/lang/funcs/structs/const.go
+++ b/lang/funcs/structs_const.go
@@ -15,7 +15,8 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
-package structs
+//package structs
+package funcs
import (
"context"
diff --git a/lang/interfaces/ast.go b/lang/interfaces/ast.go
index 3901e2c6d4..dd9b08dd0c 100644
--- a/lang/interfaces/ast.go
+++ b/lang/interfaces/ast.go
@@ -76,6 +76,9 @@ type Stmt interface {
// takes in the environment of any functions in scope.
Graph(map[string]Func) (*pgraph.Graph, error)
+ // MergedGraph returns the graph and func together in one call.
+ MergedGraph(env map[string]Func) (*pgraph.Graph, error)
+
// Output returns the output that this "program" produces. This output
// is what is used to build the output graph. It requires the input
// table of values that are used to populate each function.
diff --git a/lang/interpret_test.go b/lang/interpret_test.go
index d9fcec72ba..23868b58ed 100644
--- a/lang/interpret_test.go
+++ b/lang/interpret_test.go
@@ -515,7 +515,6 @@ func TestAstFunc0(t *testing.T) {
// build the function graph
graph, err := iast.Graph(nil)
-
if !fail && err != nil {
t.Errorf("test #%d: FAIL", index)
t.Errorf("test #%d: functions failed with: %+v", index, err)
@@ -921,7 +920,6 @@ func TestAstFunc1(t *testing.T) {
// build the function graph
graph, err := iast.Graph(nil)
-
if (!fail || !failGraph) && err != nil {
t.Errorf("test #%d: FAIL", index)
t.Errorf("test #%d: functions failed with: %+v", index, err)
@@ -1421,7 +1419,6 @@ func TestAstFunc2(t *testing.T) {
// build the function graph
graph, err := iast.Graph(nil)
-
if (!fail || !failGraph) && err != nil {
t.Errorf("test #%d: FAIL", index)
t.Errorf("test #%d: functions failed with: %+v", index, err)
diff --git a/lang/lang.go b/lang/lang.go
index 51861f3e21..d3b863f2b8 100644
--- a/lang/lang.go
+++ b/lang/lang.go
@@ -207,10 +207,21 @@ func (obj *Lang) Init() error {
obj.Logf("building function graph...")
// we assume that for some given code, the list of funcs doesn't change
// iow, we don't support variable, variables or absurd things like that
- graph, err := obj.ast.Graph(nil) // build the graph of functions
+ graph := &pgraph.Graph{Name: "functionGraph"}
+ env := make(map[string]interfaces.Func)
+ for k, v := range scope.Variables {
+ g, builtinFunc, err := v.MergedGraph(nil)
+ if err != nil {
+ return errwrap.Wrapf(err, "calling MergedGraph on builtins")
+ }
+ graph.AddGraph(g)
+ env[k] = builtinFunc
+ }
+ g, err := obj.ast.MergedGraph(env) // build the graph of functions
if err != nil {
return errwrap.Wrapf(err, "could not generate function graph")
}
+ graph.AddGraph(g)
if obj.Debug {
obj.Logf("function graph: %+v", obj.graph)
diff --git a/lang/types/type.go b/lang/types/type.go
index 9461b1f985..63481c9071 100644
--- a/lang/types/type.go
+++ b/lang/types/type.go
@@ -568,7 +568,8 @@ func (obj *Type) New() Value {
case KindStruct:
return NewStruct(obj)
case KindFunc:
- return NewFunc(obj)
+ panic("TODO [SimpleFn]: NewFunc is now in a different package, so we can't use it here!")
+ //return NewFunc(obj)
case KindVariant:
return NewVariant(obj)
}
diff --git a/lang/types/value.go b/lang/types/value.go
index 89adaa391a..a0b6d0ad19 100644
--- a/lang/types/value.go
+++ b/lang/types/value.go
@@ -25,6 +25,7 @@ import (
"strconv"
"strings"
+ "github.com/purpleidea/mgmt/pgraph"
"github.com/purpleidea/mgmt/util/errwrap"
)
@@ -54,7 +55,7 @@ type Value interface {
List() []Value
Map() map[Value]Value // keys must all have same type, same for values
Struct() map[string]Value
- Func() func([]Value) (Value, error)
+ Func() func([]pgraph.Vertex) (pgraph.Vertex, error)
}
// ValueOfGolang is a helper that takes a golang value, and produces the mcl
@@ -201,36 +202,37 @@ func ValueOf(v reflect.Value) (Value, error) {
}, nil
case reflect.Func:
- t, err := TypeOf(value.Type())
- if err != nil {
- return nil, errwrap.Wrapf(err, "can't determine type of %+v", value)
- }
- if t.Out == nil {
- return nil, fmt.Errorf("cannot only represent functions with one output value")
- }
-
- f := func(args []Value) (Value, error) {
- in := []reflect.Value{}
- for _, x := range args {
- // TODO: should we build this method instead?
- //v := x.Reflect() // types.Value -> reflect.Value
- v := reflect.ValueOf(x.Value())
- in = append(in, v)
- }
-
- // FIXME: can we trap panic's ?
- out := value.Call(in) // []reflect.Value
- if len(out) != 1 { // TODO: panic, b/c already checked in TypeOf?
- return nil, fmt.Errorf("cannot only represent functions with one output value")
- }
-
- return ValueOf(out[0]) // recurse
- }
-
- return &FuncValue{
- T: t,
- V: f,
- }, nil
+ panic("TODO [SimpleFn] [Reflect]: what's all this reflection stuff for?")
+ //t, err := TypeOf(value.Type())
+ //if err != nil {
+ // return nil, errwrap.Wrapf(err, "can't determine type of %+v", value)
+ //}
+ //if t.Out == nil {
+ // return nil, fmt.Errorf("cannot only represent functions with one output value")
+ //}
+
+ //f := func(args []Value) (Value, error) {
+ // in := []reflect.Value{}
+ // for _, x := range args {
+ // // TODO: should we build this method instead?
+ // //v := x.Reflect() // types.Value -> reflect.Value
+ // v := reflect.ValueOf(x.Value())
+ // in = append(in, v)
+ // }
+
+ // // FIXME: can we trap panic's ?
+ // out := value.Call(in) // []reflect.Value
+ // if len(out) != 1 { // TODO: panic, b/c already checked in TypeOf?
+ // return nil, fmt.Errorf("cannot only represent functions with one output value")
+ // }
+
+ // return ValueOf(out[0]) // recurse
+ //}
+
+ //return &FuncValue{
+ // T: t,
+ // V: f,
+ //}, nil
default:
return nil, fmt.Errorf("unable to represent value of %+v", v)
@@ -402,40 +404,6 @@ func Into(v Value, rv reflect.Value) error {
}
return nil
- case *FuncValue:
- if err := mustInto(reflect.Func); err != nil {
- return err
- }
-
- // wrap our function with the translation that is necessary
- fn := func(args []reflect.Value) (results []reflect.Value) { // build
- innerArgs := []Value{}
- for _, x := range args {
- v, err := ValueOf(x) // reflect.Value -> Value
- if err != nil {
- panic(fmt.Errorf("can't determine value of %+v", x))
- }
- innerArgs = append(innerArgs, v)
- }
- result, err := v.V(innerArgs) // call it
- if err != nil {
- // when calling our function with the Call method, then
- // we get the error output and have a chance to decide
- // what to do with it, but when calling it from within
- // a normal golang function call, the error represents
- // that something went horribly wrong, aka a panic...
- panic(fmt.Errorf("function panic: %+v", err))
- }
- out := reflect.New(rv.Type().Out(0))
- // convert the lang result back to a Go value
- if err := Into(result, out); err != nil {
- panic(fmt.Errorf("function return conversion panic: %+v", err))
- }
- return []reflect.Value{out} // only one result
- }
- rv.Set(reflect.MakeFunc(rv.Type(), fn))
- return nil
-
case *VariantValue:
return Into(v.V, rv)
@@ -498,7 +466,7 @@ func (obj *base) Struct() map[string]Value {
// Func represents the value of this type as a function if it is one. If this is
// not a function, then this panics.
-func (obj *base) Func() func([]Value) (Value, error) {
+func (obj *base) Func() func([]pgraph.Vertex) (pgraph.Vertex, error) {
panic("not a func")
}
@@ -1128,109 +1096,24 @@ func (obj *StructValue) Lookup(k string) (value Value, exists bool) {
return v, exists
}
-// FuncValue represents a function value. The defined function takes a list of
-// Value arguments and returns a Value. It can also return an error which could
-// represent that something went horribly wrong. (Think, an internal panic.)
-type FuncValue struct {
- base
+// SimpleFn represents a function which takes a list of Value arguments and
+// returns a Value. It can also return an error which could represent that
+// something went horribly wrong. (Think, an internal panic.)
+//
+// This is not general enough to represent all functions in the language (see
+// FuncValue above), but it is a useful common case.
+//
+// SimpleFn is not a Value, but it is a useful building block for implementing
+// Func nodes.
+type SimpleFn struct {
V func([]Value) (Value, error)
T *Type // contains ordered field types, arg names are a bonus part
}
-// NewFunc creates a new function with the specified type.
-func NewFunc(t *Type) *FuncValue {
- if t.Kind != KindFunc {
- return nil // sanity check
- }
- v := func([]Value) (Value, error) {
- return nil, fmt.Errorf("nil function") // TODO: is this correct?
- }
- return &FuncValue{
- V: v,
- T: t,
- }
-}
-
-// String returns a visual representation of this value.
-func (obj *FuncValue) String() string {
- return fmt.Sprintf("func(%+v)", obj.T) // TODO: can't print obj.V w/o vet warning
-}
-
-// Type returns the type data structure that represents this type.
-func (obj *FuncValue) Type() *Type { return obj.T }
-
-// Less compares to value and returns true if we're smaller. This panics if the
-// two types aren't the same.
-func (obj *FuncValue) Less(v Value) bool {
- V := v.(*FuncValue)
- return obj.String() < V.String() // FIXME: implement a proper less func
-}
-
-// Cmp returns an error if this value isn't the same as the arg passed in.
-func (obj *FuncValue) Cmp(val Value) error {
- if obj == nil || val == nil {
- return fmt.Errorf("cannot cmp to nil")
- }
- if err := obj.Type().Cmp(val.Type()); err != nil {
- return errwrap.Wrapf(err, "cannot cmp types")
- }
-
- return fmt.Errorf("cannot cmp funcs") // TODO: can we ?
-}
-
-// Copy returns a copy of this value.
-func (obj *FuncValue) Copy() Value {
- return &FuncValue{
- V: obj.V, // FIXME: can we copy the function, or do we need to?
- T: obj.T.Copy(),
- }
-}
-
-// Value returns the raw value of this type.
-func (obj *FuncValue) Value() interface{} {
- typ := obj.T.Reflect()
-
- // wrap our function with the translation that is necessary
- fn := func(args []reflect.Value) (results []reflect.Value) { // build
- innerArgs := []Value{}
- for _, x := range args {
- v, err := ValueOf(x) // reflect.Value -> Value
- if err != nil {
- panic(fmt.Sprintf("can't determine value of %+v", x))
- }
- innerArgs = append(innerArgs, v)
- }
- result, err := obj.V(innerArgs) // call it
- if err != nil {
- // when calling our function with the Call method, then
- // we get the error output and have a chance to decide
- // what to do with it, but when calling it from within
- // a normal golang function call, the error represents
- // that something went horribly wrong, aka a panic...
- panic(fmt.Sprintf("function panic: %+v", err))
- }
- return []reflect.Value{reflect.ValueOf(result.Value())} // only one result
- }
- val := reflect.MakeFunc(typ, fn)
- return val.Interface()
-}
-
-// Func represents the value of this type as a function if it is one. If this is
-// not a function, then this panics.
-func (obj *FuncValue) Func() func([]Value) (Value, error) {
- return obj.V
-}
-
-// Set sets the function value to be a new function.
-func (obj *FuncValue) Set(fn func([]Value) (Value, error)) error { // TODO: change method name?
- obj.V = fn
- return nil // TODO: can we do any sort of checking here?
-}
-
// Call runs the function value and returns its result. It returns an error if
// something goes wrong during execution, and panic's if you call this with
// inappropriate input types, or if it returns an inappropriate output type.
-func (obj *FuncValue) Call(args []Value) (Value, error) {
+func (obj *SimpleFn) Call(args []Value) (Value, error) {
// cmp input args type to obj.T
length := len(obj.T.Ord)
if length != len(args) {
@@ -1256,6 +1139,16 @@ func (obj *FuncValue) Call(args []Value) (Value, error) {
return result, err
}
+func (obj *SimpleFn) Type() *Type { return obj.T }
+
+// Copy returns a copy of this value.
+func (obj *SimpleFn) Copy() *SimpleFn {
+ return &SimpleFn{
+ V: obj.V,
+ T: obj.T.Copy(),
+ }
+}
+
// VariantValue represents a variant value.
type VariantValue struct {
base
@@ -1377,6 +1270,6 @@ func (obj *VariantValue) Struct() map[string]Value {
// Func represents the value of this type as a function if it is one. If this is
// not a function, then this panics.
-func (obj *VariantValue) Func() func([]Value) (Value, error) {
+func (obj *VariantValue) Func() func([]pgraph.Vertex) (pgraph.Vertex, error) {
return obj.V.Func()
}
diff --git a/lang/util/util.go b/lang/util/util.go
index 1431eea2d3..f90b169857 100644
--- a/lang/util/util.go
+++ b/lang/util/util.go
@@ -58,7 +58,7 @@ func HasDuplicateTypes(typs []*types.Type) error {
// FnMatch is run to turn a polymorphic, undetermined list of functions, into a
// specific statically typed version. It is usually run after Unify completes.
// It returns the index of the matched function.
-func FnMatch(typ *types.Type, fns []*types.FuncValue) (int, error) {
+func FnMatch(typ *types.Type, fns []*types.SimpleFn) (int, error) {
// typ is the KindFunc signature we're trying to build...
if typ == nil {
return 0, fmt.Errorf("type of function must be specified")