From f15b8313008da47512f51312aab7483e22ae80f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20G=C3=A9lineau?= Date: Sat, 19 Nov 2022 21:43:11 -0500 Subject: [PATCH] XXX: This is a combination of 58 commits. This is the 1st commit message: lang: Split FuncValue into FuncValue and SimpleFn Representing an MCL function value as a golang function from Value to Value was a mistake, it should be a function from Vertex to Vertex. Here is why this is a mistake: The output of a function like $f = fn(x) { Shell(Sprintf("seq %d", x)) } varies over time, while a single Value does not. Thus, code like Map($f, list(1, 2)) would first produce the value list("1", "1"), but then it would _not_ update to list("1", "2") when "seq 2" produces its second line. That's because with the mistaken design, when Map receives a new FuncValue or a new input list of N elements, Map calls the function from Value to Value N times and produces a single output list of N elements. Here is why the corrected design is better: Here's what happens with this new design when Map receives a new FuncValue or a new input list of N elements. First, Map constructs N item-input nodes, each of which extracts a different entry from the list. Then, Map calls the function from Vertex to Vertex N times, once for each item-input node, and thus obtain N item-output nodes. Finally, Map constructs an item-collecting node which constructs a list out of all of its inputs, and Map connects the N item-output nodes to the item-collecting node. This item-collecting node is the output of Map. The Vertex to Vertex function constructs and connects its own nodes; in this case, it constructs an Sprintf node and connects the item-input node to it, and then constructs a Shell node and connects the Sprintf node to it, and then returns the Shell node as the item-output node. The two Shell node in this sub-graph emit a first value "1", which propagates to the item-collecting node and causes it to output a first value list("1", "1"). Then, the second Shell node emits a second value "2", which propagates to the item-collecting node and causes it to output a second value list("1", "2"), as desired. Here is how this commit brings us closer to the above plan: Changing FuncValue throughout the codebase is a big change. One of the difficulties is that it is not just nodes which are emitting FuncValues, there are also many other places in the code where FuncValue is used to hold a golang function from Value to Value. Some of those places now need to hold a golang function from Vertex to Vertex, but other places still need to hold a golang function from Vertex to Vertex. Thus, as a first step, we need to split FuncValue into two types. This commit splits the old FuncValue into two types: 1. The new FuncValue will hold a function from Vertex to Vertex. FuncValue is a Value. 2. A new type named "SimpleFn" will hold a function from Value to Value. SimpleFn is not a Value. This commit replaces occurrences of the old FuncValue with one of those two new types, as appropriate. This commit does not yet adapt the surrounding code to make use of the new representation; that will be done in future commits. I have annotated the missing parts with the following panic message in order to make it easy to find which parts still need to be implemented. The "..." part explains what needs to be implemented. panic("TODO [SimpleFn]: ..."); Here's where I need help: One part of the code which is not clear to me are the parts which use reflection. I don't understand the purpose of that code well enough to explain what needs to be implemented. I have annotated those "known unknown" parts of the remaining work with the following panic message in order to make it easy to find which parts still need more thinking and planning: panic("TODO [SimpleFn] [Reflect]: ..."); This is the commit message #2: lang: Add the core func graph Txn API This will eventually let functions change the running graph via a transaction API. At the moment the core Lock and Unlock primitives aren't implemented. This is the commit message #3: lang: A reversible wrapper around Txn This is useful for the common case in which we call one FuncValue to construct a bunch of nodes, and later we switch to a different FuncValue and so we want to remove all the nodes added by the first FuncValue and replace them by the nodes added by the second FuncValue. This is the commit message #4: lang: move FuncValue to its own package This is the commit message #5: lang: combine lang/func/structs and ast Merging those two packages allows us to avoid import cycles when a Func needs to add an Expr to the graph. This is the commit message #6: lang: CallExpr must generate a subgraph FuncValues are now manipulating the graph instead of manipulating values, so the logic for calling a FuncValue must now follow suit. This is the commit message #7: lang: ExprFunc does not need to store its value This is the commit message #8: spelling This is the commit message #9: convert between SimpleFn, FuncValue, and Func This is the commit message #10: no need for ExprCall.argVertices it's the exact same thing as ExprCall.Args This is the commit message #11: ExprFunc.Func()'s three cases This is the commit message #12: FunctionFunc is no longer used This is the commit message #13: ExprBool.MergedGraph() This is the commit message #14: ExprIf.MergedGraph() This is the commit message #15: ExprFunc.MergedGraph() This is the commit message #16: lang: ast, interfaces: Add MergedGraph signature and implementation This adds a MergedGraph signature to the Expr interface. The txn type is interface{} until we have that merged. This is the commit message #17: lang: ast, interfaces: Add MergedGraph signature and implementation This adds a MergedGraph signature to the Stmt interface. The txn type is interface{} until we have that merged. This is the commit message #18: Use MergedGraph signature and implementation This puts it into play, but doesn't initialize the input args at all. This is the commit message #19: Restore ExprBool.Graph() and Func() This reverts commit 3ea38454fa2dfe270e99953a634f7a27520561c5. This is the commit message #20: Restore ExprIf.Graph() This reverts commit a62889e8f24f60705c44718e9ca4bed26688acd2. This is the commit message #21: restore ExprFunc::Graph() and Func() This is the commit message #22: fix type errors This is the commit message #23: ExprCall.MergedGraph() This is the commit message #24: a more precise type for args This is the commit message #25: GraphTxn This is the commit message #26: ReversibleTxn.AddGraph This is the commit message #27: ExprFunc.MergedGraph() This is the commit message #28: ExprVar.MergedGraph() This is the commit message #29: sub-graph is spelled subgraph in this codebase This is the commit message #30: ExprFunc.mkFunc() was unused This is the commit message #31: CallFunc.Stream() should add Funcs to the graph, not Exprs This is the commit message #32: move ConstFunc to lang.funcs This is the commit message #33: move conversion functions to lang.funcs.simple I wrote some conversion functions from SimpleFn to FuncValue to Func, and I want to call them from Func.Stream() implementations, so those conversion functions should be somewhere in lang.funcs. This is the commit message #34: it is the responsibility of the function engine to call Init on the nodes This is the commit message #35: FuncValue.Call() This is the commit message #36: drop MergedGraph's unused txn parameter This is the commit message #37: move CallFunc to funcs.simple This is the commit message #38: Func from channel This is the commit message #39: ChannelBased{Source,Sink}Func This is the commit message #40: MapFunc This is the commit message #41: remove unused CallFunc field This is the commit message #42: [REVERT ME] remove unrelated files to make VS Code happy This is the commit message #43: [REVERT ME] remove unrelated files to make VS Code happy This is the commit message #44: extend environment with StmtProg's local variables This is the commit message #45: delete unused VarFunc This is the commit message #46: appease govet This is the commit message #47: add imported variables to the environment This is the commit message #48: add builtins to the environment This is the commit message #49: accumulate all the imported variables previously, we were only keeping the imported variables from the last import statement. This is the commit message #50: XXX: wip new function engine This is the commit message #51: comments This is the commit message #52: remove Engine.{Lock,Unlock} placeholders This is the commit message #53: f(...) support for ExprCall This is the commit message #54: [REVERT ME] remove unrelated files to make VS Code happy This is the commit message #55: don't recreate the subgraphInput This is the commit message #56: CallFunc now takes a single input, the FuncValue Also, by using the map_func logic, CallFunc now detects when it can no longer emits new values downstream. This is the commit message #57: GraphTxn must take a pointer in order to modify the graph This is the commit message #58: GraphTxn is unused --- examples/dhcp_client/dhcp_client.go | 107 --- examples/lib/exec-send-recv.go | 265 ------ examples/lib/libmgmt-subgraph0.go | 275 ------ examples/lib/libmgmt-subgraph1.go | 259 ------ examples/lib/libmgmt2.go | 242 ----- examples/lib/libmgmt3.go | 264 ------ examples/longpoll/redirect-client.go | 53 -- examples/longpoll/redirect-server.go | 56 -- lang/ast/structs.go | 842 +++++++++++------- .../composite.go => ast/structs_composite.go} | 3 +- .../structs/if.go => ast/structs_if.go} | 4 +- lang/ast/util.go | 14 +- lang/fancyfunc/fancy_func.go | 162 ++++ lang/funcs/core/convert/to_float.go | 2 +- lang/funcs/core/convert/to_int.go | 2 +- lang/funcs/core/datetime/format_func.go | 2 +- lang/funcs/core/datetime/hour_func.go | 2 +- lang/funcs/core/datetime/print_func.go | 2 +- lang/funcs/core/datetime/weekday_func.go | 2 +- lang/funcs/core/example/answer_func.go | 2 +- lang/funcs/core/example/errorbool_func.go | 2 +- lang/funcs/core/example/int2str_func.go | 2 +- lang/funcs/core/example/nested/hello_func.go | 2 +- lang/funcs/core/example/plus_func.go | 2 +- lang/funcs/core/example/str2int_func.go | 2 +- lang/funcs/core/iter/map_func.go | 223 ++++- lang/funcs/core/len_func.go | 2 +- lang/funcs/core/math/fortytwo_func.go | 2 +- lang/funcs/core/math/mod_func.go | 2 +- lang/funcs/core/math/pow_func.go | 2 +- lang/funcs/core/math/sqrt_func.go | 2 +- lang/funcs/core/net/cidr_to_ip_func.go | 2 +- lang/funcs/core/net/macfmt_func.go | 2 +- lang/funcs/core/os/family_func.go | 6 +- lang/funcs/core/regexp/match_func.go | 2 +- lang/funcs/core/strings/split_func.go | 2 +- lang/funcs/core/strings/to_lower_func.go | 2 +- lang/funcs/core/sys/env_func.go | 8 +- lang/funcs/core/template_func.go | 2 +- .../funcgen/templates/generated_funcs.go.tpl | 2 +- lang/funcs/operator_func.go | 68 +- lang/funcs/simple/channel_based_sink_func.go | 113 +++ .../funcs/simple/channel_based_source_func.go | 110 +++ lang/funcs/simple/simple.go | 49 +- lang/funcs/simple/structs_call.go | 208 +++++ lang/funcs/simplepoly/simplepoly.go | 27 +- lang/funcs/structs/call.go | 183 ---- lang/funcs/structs/function.go | 206 ----- lang/funcs/structs/var.go | 135 --- .../{structs/const.go => structs_const.go} | 3 +- lang/interfaces/ast.go | 3 + lang/interpret_test.go | 3 - lang/lang.go | 13 +- lang/types/type.go | 3 +- lang/types/value.go | 219 ++--- lang/util/util.go | 2 +- test/shell/libmgmt-change1.go | 181 ---- test/shell/libmgmt-change2.go | 194 ---- 58 files changed, 1480 insertions(+), 3071 deletions(-) delete mode 100644 examples/dhcp_client/dhcp_client.go delete mode 100644 examples/lib/exec-send-recv.go delete mode 100644 examples/lib/libmgmt-subgraph0.go delete mode 100644 examples/lib/libmgmt-subgraph1.go delete mode 100644 examples/lib/libmgmt2.go delete mode 100644 examples/lib/libmgmt3.go delete mode 100644 examples/longpoll/redirect-client.go delete mode 100644 examples/longpoll/redirect-server.go rename lang/{funcs/structs/composite.go => ast/structs_composite.go} (99%) rename lang/{funcs/structs/if.go => ast/structs_if.go} (98%) create mode 100644 lang/fancyfunc/fancy_func.go create mode 100644 lang/funcs/simple/channel_based_sink_func.go create mode 100644 lang/funcs/simple/channel_based_source_func.go create mode 100644 lang/funcs/simple/structs_call.go delete mode 100644 lang/funcs/structs/call.go delete mode 100644 lang/funcs/structs/function.go delete mode 100644 lang/funcs/structs/var.go rename lang/funcs/{structs/const.go => structs_const.go} (98%) delete mode 100644 test/shell/libmgmt-change1.go delete mode 100644 test/shell/libmgmt-change2.go diff --git a/examples/dhcp_client/dhcp_client.go b/examples/dhcp_client/dhcp_client.go deleted file mode 100644 index d2a4cd7741..0000000000 --- a/examples/dhcp_client/dhcp_client.go +++ /dev/null @@ -1,107 +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 main - -import ( - "context" - "fmt" - "log" - "net" - "os" - - "github.com/insomniacslk/dhcp/dhcpv4" - "github.com/insomniacslk/dhcp/dhcpv4/nclient4" -) - -const ( - iface = "lo" // loopback for local testing - address = "127.0.0.1" -) - -func main() { - if len(os.Args) < 2 || len(os.Args) > 3 { - log.Printf("Usage: %s [port] ", os.Args[0]) - return - } - - port := string(nclient4.ServerPort) // the default is 67 - if len(os.Args) >= 3 { - port = os.Args[1] - } - hwAddr := os.Args[len(os.Args)-1] // argv[1] - - hw, err := net.ParseMAC(hwAddr) - if err != nil { - log.Printf("Invalid mac address: %v", err) - return - } - - addr := fmt.Sprintf("%s:%s", address, port) - log.Printf("Connecting to: %s", addr) - - opts := []nclient4.ClientOpt{} - { - opt := nclient4.WithHWAddr(hw) - opts = append(opts, opt) - } - { - opt := nclient4.WithSummaryLogger() - opts = append(opts, opt) - } - //{ - // opt := nclient4.WithDebugLogger() - // opts = append(opts, opt) - //} - - //c, err := nclient4.NewWithConn(conn net.PacketConn, ifaceHWAddr net.HardwareAddr, opts...) - c, err := nclient4.New(iface, opts...) - if err != nil { - log.Printf("Error connecting to server: %v", err) - return - } - defer func() { - if err := c.Close(); err != nil { - log.Printf("Error closing client: %v", err) - } - }() - - modifiers := []dhcpv4.Modifier{} - //{ - // mod := dhcpv4.WithYourIP(net.ParseIP(?)) - // modifiers = append(modifiers, mod) - //} - //{ - // mod := dhcpv4.WithClientIP(net.ParseIP(?)) - // modifiers = append(modifiers, mod) - //} - // TODO: add modifiers - - log.Printf("Requesting...") - ctx := context.Background() // TODO: add to ^C handler - offer, ack, err := c.Request(ctx, modifiers...) // (offer, ack *dhcpv4.DHCPv4, err error) - if err != nil { - log.Printf("Error requesting from server: %v", err) - return - } - - // Show the results of the D-O-R-A exchange. - log.Printf("Offer: %+v", offer) - log.Printf("Ack: %+v", ack) - - log.Printf("Done!") -} diff --git a/examples/lib/exec-send-recv.go b/examples/lib/exec-send-recv.go deleted file mode 100644 index 5ebeb04b57..0000000000 --- a/examples/lib/exec-send-recv.go +++ /dev/null @@ -1,265 +0,0 @@ -// libmgmt example of send->recv -package main - -import ( - "fmt" - "log" - "os" - "os/signal" - "sync" - "syscall" - "time" - - "github.com/purpleidea/mgmt/engine" - "github.com/purpleidea/mgmt/engine/resources" - "github.com/purpleidea/mgmt/gapi" - mgmt "github.com/purpleidea/mgmt/lib" - "github.com/purpleidea/mgmt/pgraph" - - "github.com/urfave/cli/v2" -) - -// XXX: this has not been updated to latest GAPI/Deploy API. Patches welcome! - -const ( - // Name is the name of this frontend. - Name = "libmgmt" -) - -// MyGAPI implements the main GAPI interface. -type MyGAPI struct { - Name string // graph name - Interval uint // refresh interval, 0 to never refresh - - data *gapi.Data - initialized bool - closeChan chan struct{} - wg sync.WaitGroup // sync group for tunnel go routines -} - -// NewMyGAPI creates a new MyGAPI struct and calls Init(). -func NewMyGAPI(data *gapi.Data, name string, interval uint) (*MyGAPI, error) { - obj := &MyGAPI{ - Name: name, - Interval: interval, - } - return obj, obj.Init(data) -} - -// CliFlags returns a list of flags used by the passed in subcommand. -func (obj *MyGAPI) CliFlags(string) []cli.Flag { - return []cli.Flag{ - &cli.StringFlag{ - Name: obj.Name, - Value: "", - Usage: "run", - }, - } -} - -// Cli takes a cli.Context and some other info, and returns our GAPI. If there -// are any validation problems, you should return an error. -func (obj *MyGAPI) Cli(cliInfo *gapi.CliInfo) (*gapi.Deploy, error) { - c := cliInfo.CliContext - //fs := cliInfo.Fs // copy files from local filesystem *into* this fs... - //debug := cliInfo.Debug - //logf := func(format string, v ...interface{}) { - // cliInfo.Logf(Name+": "+format, v...) - //} - - return &gapi.Deploy{ - Name: obj.Name, - Noop: c.Bool("noop"), - Sema: c.Int("sema"), - GAPI: &MyGAPI{}, - }, nil -} - -// Init initializes the MyGAPI struct. -func (obj *MyGAPI) Init(data *gapi.Data) error { - if obj.initialized { - return fmt.Errorf("already initialized") - } - if obj.Name == "" { - return fmt.Errorf("the graph name must be specified") - } - obj.data = data // store for later - obj.closeChan = make(chan struct{}) - obj.initialized = true - return nil -} - -// Graph returns a current Graph. -func (obj *MyGAPI) Graph() (*pgraph.Graph, error) { - if !obj.initialized { - return nil, fmt.Errorf("%s: MyGAPI is not initialized", Name) - } - - g, err := pgraph.NewGraph(obj.Name) - if err != nil { - return nil, err - } - - exec1 := &resources.ExecRes{ - Cmd: "echo hello world && echo goodbye world 1>&2", // to stdout && stderr - Shell: "/bin/bash", - } - g.AddVertex(exec1) - - output := &resources.FileRes{ - Path: "/tmp/mgmt/output", - State: "present", - } - // XXX: add send->recv! - //Recv: map[string]*engine.Send{ - // "Content": {Res: exec1, Key: "Output"}, - //}, - - g.AddVertex(output) - g.AddEdge(exec1, output, &engine.Edge{Name: "e0"}) - - stdout := &resources.FileRes{ - Path: "/tmp/mgmt/stdout", - State: "present", - } - // XXX: add send->recv! - //Recv: map[string]*engine.Send{ - // "Content": {Res: exec1, Key: "Stdout"}, - //}, - g.AddVertex(stdout) - g.AddEdge(exec1, stdout, &engine.Edge{Name: "e1"}) - - stderr := &resources.FileRes{ - Path: "/tmp/mgmt/stderr", - State: "present", - } - // XXX: add send->recv! - //Recv: map[string]*engine.Send{ - // "Content": {Res: exec1, Key: "Stderr"}, - //}, - - g.AddVertex(stderr) - g.AddEdge(exec1, stderr, &engine.Edge{Name: "e2"}) - - //g, err := config.NewGraphFromConfig(obj.data.Hostname, obj.data.World, obj.data.Noop) - return g, nil -} - -// Next returns nil errors every time there could be a new graph. -func (obj *MyGAPI) Next() chan gapi.Next { - ch := make(chan gapi.Next) - obj.wg.Add(1) - go func() { - defer obj.wg.Done() - defer close(ch) // this will run before the obj.wg.Done() - if !obj.initialized { - next := gapi.Next{ - Err: fmt.Errorf("%s: MyGAPI is not initialized", Name), - Exit: true, // exit, b/c programming error? - } - ch <- next - return - } - startChan := make(chan struct{}) // start signal - close(startChan) // kick it off! - - ticker := make(<-chan time.Time) - if obj.data.NoStreamWatch || obj.Interval <= 0 { - ticker = nil - } else { - // arbitrarily change graph every interval seconds - t := time.NewTicker(time.Duration(obj.Interval) * time.Second) - defer t.Stop() - ticker = t.C - } - for { - select { - case <-startChan: // kick the loop once at start - startChan = nil // disable - // pass - case <-ticker: - // pass - case <-obj.closeChan: - return - } - - log.Printf("%s: Generating new graph...", Name) - select { - case ch <- gapi.Next{}: // trigger a run - case <-obj.closeChan: - return - } - } - }() - return ch -} - -// Close shuts down the MyGAPI. -func (obj *MyGAPI) Close() error { - if !obj.initialized { - return fmt.Errorf("%s: MyGAPI is not initialized", Name) - } - close(obj.closeChan) - obj.wg.Wait() - obj.initialized = false // closed = true - return nil -} - -// Run runs an embedded mgmt server. -func Run() error { - - obj := &mgmt.Main{} - obj.Program = "libmgmt" // TODO: set on compilation - obj.Version = "0.0.1" // TODO: set on compilation - obj.TmpPrefix = true // disable for easy debugging - //prefix := "/tmp/testprefix/" - //obj.Prefix = &p // enable for easy debugging - obj.IdealClusterSize = -1 - obj.ConvergedTimeout = -1 - obj.Noop = false // FIXME: careful! - - //obj.GAPI = &MyGAPI{ // graph API - // Name: "libmgmt", // TODO: set on compilation - // Interval: 60 * 10, // arbitrarily change graph every 15 seconds - //} - - if err := obj.Init(); err != nil { - return err - } - - // install the exit signal handler - exit := make(chan struct{}) - defer close(exit) - go func() { - signals := make(chan os.Signal, 1) - signal.Notify(signals, os.Interrupt) // catch ^C - //signal.Notify(signals, os.Kill) // catch signals - signal.Notify(signals, syscall.SIGTERM) - - select { - case sig := <-signals: // any signal will do - if sig == os.Interrupt { - log.Println("Interrupted by ^C") - obj.Exit(nil) - return - } - log.Println("Interrupted by signal") - obj.Exit(fmt.Errorf("killed by %v", sig)) - return - case <-exit: - return - } - }() - - return obj.Run() -} - -func main() { - log.Printf("Hello!") - if err := Run(); err != nil { - fmt.Println(err) - os.Exit(1) - return - } - log.Printf("Goodbye!") -} diff --git a/examples/lib/libmgmt-subgraph0.go b/examples/lib/libmgmt-subgraph0.go deleted file mode 100644 index 5fc1a79ccf..0000000000 --- a/examples/lib/libmgmt-subgraph0.go +++ /dev/null @@ -1,275 +0,0 @@ -// libmgmt example of flattened subgraph -package main - -import ( - "fmt" - "log" - "os" - "os/signal" - "sync" - "syscall" - "time" - - "github.com/purpleidea/mgmt/engine" - "github.com/purpleidea/mgmt/engine/resources" - "github.com/purpleidea/mgmt/gapi" - mgmt "github.com/purpleidea/mgmt/lib" - "github.com/purpleidea/mgmt/pgraph" - "github.com/purpleidea/mgmt/util/errwrap" - - "github.com/urfave/cli/v2" -) - -// XXX: this has not been updated to latest GAPI/Deploy API. Patches welcome! - -const ( - // Name is the name of this frontend. - Name = "libmgmt" -) - -func init() { - gapi.Register(Name, func() gapi.GAPI { return &MyGAPI{} }) // register -} - -// MyGAPI implements the main GAPI interface. -type MyGAPI struct { - Name string // graph name - Interval uint // refresh interval, 0 to never refresh - - data *gapi.Data - initialized bool - closeChan chan struct{} - wg sync.WaitGroup // sync group for tunnel go routines -} - -// NewMyGAPI creates a new MyGAPI struct and calls Init(). -func NewMyGAPI(data *gapi.Data, name string, interval uint) (*MyGAPI, error) { - obj := &MyGAPI{ - Name: name, - Interval: interval, - } - return obj, obj.Init(data) -} - -// CliFlags returns a list of flags used by the passed in subcommand. -func (obj *MyGAPI) CliFlags(string) []cli.Flag { - return []cli.Flag{ - &cli.StringFlag{ - Name: obj.Name, - Value: "", - Usage: "run", - }, - } -} - -// Cli takes a cli.Context and some other info, and returns our GAPI. If there -// are any validation problems, you should return an error. -func (obj *MyGAPI) Cli(cliInfo *gapi.CliInfo) (*gapi.Deploy, error) { - c := cliInfo.CliContext - //fs := cliInfo.Fs // copy files from local filesystem *into* this fs... - //debug := cliInfo.Debug - //logf := func(format string, v ...interface{}) { - // cliInfo.Logf(Name+": "+format, v...) - //} - - return &gapi.Deploy{ - Name: obj.Name, - Noop: c.Bool("noop"), - Sema: c.Int("sema"), - GAPI: &MyGAPI{}, - }, nil -} - -// Init initializes the MyGAPI struct. -func (obj *MyGAPI) Init(data *gapi.Data) error { - if obj.initialized { - return fmt.Errorf("already initialized") - } - if obj.Name == "" { - return fmt.Errorf("the graph name must be specified") - } - obj.data = data // store for later - obj.closeChan = make(chan struct{}) - obj.initialized = true - return nil -} - -func (obj *MyGAPI) subGraph() (*pgraph.Graph, error) { - g, err := pgraph.NewGraph(obj.Name) - if err != nil { - return nil, err - } - - f1 := &resources.FileRes{ - Path: "/tmp/mgmt/sub1", - State: "present", - } - g.AddVertex(f1) - - n1 := &resources.NoopRes{} - g.AddVertex(n1) - - return g, nil -} - -// Graph returns a current Graph. -func (obj *MyGAPI) Graph() (*pgraph.Graph, error) { - if !obj.initialized { - return nil, fmt.Errorf("%s: MyGAPI is not initialized", Name) - } - - g, err := pgraph.NewGraph(obj.Name) - if err != nil { - return nil, err - } - - content := "I created a subgraph!\n" - f0 := &resources.FileRes{ - Path: "/tmp/mgmt/README", - Content: &content, - State: "present", - } - g.AddVertex(f0) - - subGraph, err := obj.subGraph() - if err != nil { - return nil, errwrap.Wrapf(err, "running subGraph() failed") - } - - edgeGenFn := func(v1, v2 pgraph.Vertex) pgraph.Edge { - edge := &engine.Edge{ - Name: fmt.Sprintf("edge: %s->%s", v1, v2), - } - - // if we want to do something specific based on input - _, v2IsFile := v2.(*resources.FileRes) - if v1 == f0 && v2IsFile { - edge.Notify = true - } - - return edge - } - g.AddEdgeVertexGraph(f0, subGraph, edgeGenFn) - - //g, err := config.NewGraphFromConfig(obj.data.Hostname, obj.data.World, obj.data.Noop) - return g, nil -} - -// Next returns nil errors every time there could be a new graph. -func (obj *MyGAPI) Next() chan gapi.Next { - ch := make(chan gapi.Next) - obj.wg.Add(1) - go func() { - defer obj.wg.Done() - defer close(ch) // this will run before the obj.wg.Done() - if !obj.initialized { - next := gapi.Next{ - Err: fmt.Errorf("%s: MyGAPI is not initialized", Name), - Exit: true, // exit, b/c programming error? - } - ch <- next - return - } - startChan := make(chan struct{}) // start signal - close(startChan) // kick it off! - - ticker := make(<-chan time.Time) - if obj.data.NoStreamWatch || obj.Interval <= 0 { - ticker = nil - } else { - // arbitrarily change graph every interval seconds - t := time.NewTicker(time.Duration(obj.Interval) * time.Second) - defer t.Stop() - ticker = t.C - } - for { - select { - case <-startChan: // kick the loop once at start - startChan = nil // disable - // pass - case <-ticker: - // pass - case <-obj.closeChan: - return - } - - log.Printf("%s: Generating new graph...", Name) - select { - case ch <- gapi.Next{}: // trigger a run - case <-obj.closeChan: - return - } - } - }() - return ch -} - -// Close shuts down the MyGAPI. -func (obj *MyGAPI) Close() error { - if !obj.initialized { - return fmt.Errorf("%s: MyGAPI is not initialized", Name) - } - close(obj.closeChan) - obj.wg.Wait() - obj.initialized = false // closed = true - return nil -} - -// Run runs an embedded mgmt server. -func Run() error { - - obj := &mgmt.Main{} - obj.Program = Name // TODO: set on compilation - obj.Version = "0.0.1" // TODO: set on compilation - obj.TmpPrefix = true // disable for easy debugging - //prefix := "/tmp/testprefix/" - //obj.Prefix = &p // enable for easy debugging - obj.IdealClusterSize = -1 - obj.ConvergedTimeout = -1 - obj.Noop = false // FIXME: careful! - - //obj.GAPI = &MyGAPI{ // graph API - // Name: Name, // TODO: set on compilation - // Interval: 60 * 10, // arbitrarily change graph every 15 seconds - //} - - if err := obj.Init(); err != nil { - return err - } - - // install the exit signal handler - exit := make(chan struct{}) - defer close(exit) - go func() { - signals := make(chan os.Signal, 1) - signal.Notify(signals, os.Interrupt) // catch ^C - //signal.Notify(signals, os.Kill) // catch signals - signal.Notify(signals, syscall.SIGTERM) - - select { - case sig := <-signals: // any signal will do - if sig == os.Interrupt { - log.Println("Interrupted by ^C") - obj.Exit(nil) - return - } - log.Println("Interrupted by signal") - obj.Exit(fmt.Errorf("killed by %v", sig)) - return - case <-exit: - return - } - }() - - return obj.Run() -} - -func main() { - log.Printf("Hello!") - if err := Run(); err != nil { - fmt.Println(err) - os.Exit(1) - return - } - log.Printf("Goodbye!") -} diff --git a/examples/lib/libmgmt-subgraph1.go b/examples/lib/libmgmt-subgraph1.go deleted file mode 100644 index b17b76fa3e..0000000000 --- a/examples/lib/libmgmt-subgraph1.go +++ /dev/null @@ -1,259 +0,0 @@ -// libmgmt example of graph resource -package main - -import ( - "fmt" - "log" - "os" - "os/signal" - "sync" - "syscall" - "time" - - "github.com/purpleidea/mgmt/engine" - "github.com/purpleidea/mgmt/engine/resources" - "github.com/purpleidea/mgmt/gapi" - mgmt "github.com/purpleidea/mgmt/lib" - "github.com/purpleidea/mgmt/pgraph" - - "github.com/urfave/cli/v2" -) - -// XXX: this has not been updated to latest GAPI/Deploy API. Patches welcome! - -const ( - // Name is the name of this frontend. - Name = "libmgmt" -) - -// MyGAPI implements the main GAPI interface. -type MyGAPI struct { - Name string // graph name - Interval uint // refresh interval, 0 to never refresh - - data *gapi.Data - initialized bool - closeChan chan struct{} - wg sync.WaitGroup // sync group for tunnel go routines -} - -// NewMyGAPI creates a new MyGAPI struct and calls Init(). -func NewMyGAPI(data *gapi.Data, name string, interval uint) (*MyGAPI, error) { - obj := &MyGAPI{ - Name: name, - Interval: interval, - } - return obj, obj.Init(data) -} - -// CliFlags returns a list of flags used by the passed in subcommand. -func (obj *MyGAPI) CliFlags(string) []cli.Flag { - return []cli.Flag{ - &cli.StringFlag{ - Name: obj.Name, - Value: "", - Usage: "run", - }, - } -} - -// Cli takes a cli.Context and some other info, and returns our GAPI. If there -// are any validation problems, you should return an error. -func (obj *MyGAPI) Cli(cliInfo *gapi.CliInfo) (*gapi.Deploy, error) { - c := cliInfo.CliContext - //fs := cliInfo.Fs // copy files from local filesystem *into* this fs... - //debug := cliInfo.Debug - //logf := func(format string, v ...interface{}) { - // cliInfo.Logf(Name+": "+format, v...) - //} - - return &gapi.Deploy{ - Name: obj.Name, - Noop: c.Bool("noop"), - Sema: c.Int("sema"), - GAPI: &MyGAPI{}, - }, nil -} - -// Init initializes the MyGAPI struct. -func (obj *MyGAPI) Init(data *gapi.Data) error { - if obj.initialized { - return fmt.Errorf("already initialized") - } - if obj.Name == "" { - return fmt.Errorf("the graph name must be specified") - } - obj.data = data // store for later - obj.closeChan = make(chan struct{}) - obj.initialized = true - return nil -} - -// Graph returns a current Graph. -func (obj *MyGAPI) Graph() (*pgraph.Graph, error) { - if !obj.initialized { - return nil, fmt.Errorf("%s: MyGAPI is not initialized", Name) - } - - g, err := pgraph.NewGraph(obj.Name) - if err != nil { - return nil, err - } - - content := "I created a subgraph!\n" - f0 := &resources.FileRes{ - Path: "/tmp/mgmt/README", - Content: &content, - State: "present", - } - g.AddVertex(f0) - - // create a subgraph to add *into* a graph resource - subGraph, err := pgraph.NewGraph(fmt.Sprintf("%s->subgraph", obj.Name)) - if err != nil { - return nil, err - } - - // add elements into the sub graph - f1 := &resources.FileRes{ - Path: "/tmp/mgmt/sub1", - - State: "present", - } - subGraph.AddVertex(f1) - - n1 := &resources.NoopRes{} - subGraph.AddVertex(n1) - - e0 := &engine.Edge{Name: "e0"} - e0.Notify = true // send a notification from v0 to v1 - subGraph.AddEdge(f1, n1, e0) - - // create the actual resource to hold the sub graph - //subGraphRes0 := &resources.GraphRes{ // TODO: should we name this SubGraphRes ? - // Graph: subGraph, - //} - //g.AddVertex(subGraphRes0) // add it to the main graph - - //g, err := config.NewGraphFromConfig(obj.data.Hostname, obj.data.World, obj.data.Noop) - return g, nil -} - -// Next returns nil errors every time there could be a new graph. -func (obj *MyGAPI) Next() chan gapi.Next { - ch := make(chan gapi.Next) - obj.wg.Add(1) - go func() { - defer obj.wg.Done() - defer close(ch) // this will run before the obj.wg.Done() - if !obj.initialized { - next := gapi.Next{ - Err: fmt.Errorf("%s: MyGAPI is not initialized", Name), - Exit: true, // exit, b/c programming error? - } - ch <- next - return - } - startChan := make(chan struct{}) // start signal - close(startChan) // kick it off! - - ticker := make(<-chan time.Time) - if obj.data.NoStreamWatch || obj.Interval <= 0 { - ticker = nil - } else { - // arbitrarily change graph every interval seconds - t := time.NewTicker(time.Duration(obj.Interval) * time.Second) - defer t.Stop() - ticker = t.C - } - for { - select { - case <-startChan: // kick the loop once at start - startChan = nil // disable - // pass - case <-ticker: - // pass - case <-obj.closeChan: - return - } - - log.Printf("%s: Generating new graph...", Name) - select { - case ch <- gapi.Next{}: // trigger a run - case <-obj.closeChan: - return - } - } - }() - return ch -} - -// Close shuts down the MyGAPI. -func (obj *MyGAPI) Close() error { - if !obj.initialized { - return fmt.Errorf("%s: MyGAPI is not initialized", Name) - } - close(obj.closeChan) - obj.wg.Wait() - obj.initialized = false // closed = true - return nil -} - -// Run runs an embedded mgmt server. -func Run() error { - - obj := &mgmt.Main{} - obj.Program = "libmgmt" // TODO: set on compilation - obj.Version = "0.0.1" // TODO: set on compilation - obj.TmpPrefix = true // disable for easy debugging - //prefix := "/tmp/testprefix/" - //obj.Prefix = &p // enable for easy debugging - obj.IdealClusterSize = -1 - obj.ConvergedTimeout = -1 - obj.Noop = false // FIXME: careful! - - //obj.GAPI = &MyGAPI{ // graph API - // Name: "libmgmt", // TODO: set on compilation - // Interval: 60 * 10, // arbitrarily change graph every 15 seconds - //} - - if err := obj.Init(); err != nil { - return err - } - - // install the exit signal handler - exit := make(chan struct{}) - defer close(exit) - go func() { - signals := make(chan os.Signal, 1) - signal.Notify(signals, os.Interrupt) // catch ^C - //signal.Notify(signals, os.Kill) // catch signals - signal.Notify(signals, syscall.SIGTERM) - - select { - case sig := <-signals: // any signal will do - if sig == os.Interrupt { - log.Println("Interrupted by ^C") - obj.Exit(nil) - return - } - log.Println("Interrupted by signal") - obj.Exit(fmt.Errorf("killed by %v", sig)) - return - case <-exit: - return - } - }() - - return obj.Run() -} - -func main() { - log.Printf("Hello!") - if err := Run(); err != nil { - fmt.Println(err) - os.Exit(1) - return - } - log.Printf("Goodbye!") -} diff --git a/examples/lib/libmgmt2.go b/examples/lib/libmgmt2.go deleted file mode 100644 index d05d66c194..0000000000 --- a/examples/lib/libmgmt2.go +++ /dev/null @@ -1,242 +0,0 @@ -// libmgmt example -package main - -import ( - "fmt" - "log" - "os" - "os/signal" - "strconv" - "sync" - "syscall" - "time" - - "github.com/purpleidea/mgmt/engine" - "github.com/purpleidea/mgmt/gapi" - mgmt "github.com/purpleidea/mgmt/lib" - "github.com/purpleidea/mgmt/pgraph" - - "github.com/urfave/cli/v2" -) - -// XXX: this has not been updated to latest GAPI/Deploy API. Patches welcome! - -const ( - // Name is the name of this frontend. - Name = "libmgmt" -) - -// MyGAPI implements the main GAPI interface. -type MyGAPI struct { - Name string // graph name - Count uint // number of resources to create - Interval uint // refresh interval, 0 to never refresh - - data *gapi.Data - initialized bool - closeChan chan struct{} - wg sync.WaitGroup // sync group for tunnel go routines -} - -// NewMyGAPI creates a new MyGAPI struct and calls Init(). -func NewMyGAPI(data *gapi.Data, name string, interval uint, count uint) (*MyGAPI, error) { - obj := &MyGAPI{ - Name: name, - Count: count, - Interval: interval, - } - return obj, obj.Init(data) -} - -// CliFlags returns a list of flags used by the passed in subcommand. -func (obj *MyGAPI) CliFlags(string) []cli.Flag { - return []cli.Flag{ - &cli.StringFlag{ - Name: obj.Name, - Value: "", - Usage: "run", - }, - } -} - -// Cli takes a cli.Context and some other info, and returns our GAPI. If there -// are any validation problems, you should return an error. -func (obj *MyGAPI) Cli(cliInfo *gapi.CliInfo) (*gapi.Deploy, error) { - c := cliInfo.CliContext - //fs := cliInfo.Fs // copy files from local filesystem *into* this fs... - //debug := cliInfo.Debug - //logf := func(format string, v ...interface{}) { - // cliInfo.Logf(Name+": "+format, v...) - //} - - return &gapi.Deploy{ - Name: obj.Name, - Noop: c.Bool("noop"), - Sema: c.Int("sema"), - GAPI: &MyGAPI{}, - }, nil -} - -// Init initializes the MyGAPI struct. -func (obj *MyGAPI) Init(data *gapi.Data) error { - if obj.initialized { - return fmt.Errorf("already initialized") - } - if obj.Name == "" { - return fmt.Errorf("the graph name must be specified") - } - obj.data = data // store for later - obj.closeChan = make(chan struct{}) - obj.initialized = true - return nil -} - -// Graph returns a current Graph. -func (obj *MyGAPI) Graph() (*pgraph.Graph, error) { - if !obj.initialized { - return nil, fmt.Errorf("%s: MyGAPI is not initialized", Name) - } - - g, err := pgraph.NewGraph(obj.Name) - if err != nil { - return nil, err - } - var vertex pgraph.Vertex - for i := uint(0); i < obj.Count; i++ { - n, err := engine.NewNamedResource("noop", fmt.Sprintf("noop%d", i)) - if err != nil { - return nil, err - } - g.AddVertex(n) - if i > 0 { - g.AddEdge(vertex, n, &engine.Edge{Name: fmt.Sprintf("e%d", i)}) - } - vertex = n // save - } - - //g, err := config.NewGraphFromConfig(obj.data.Hostname, obj.data.World, obj.data.Noop) - return g, nil -} - -// Next returns nil errors every time there could be a new graph. -func (obj *MyGAPI) Next() chan gapi.Next { - ch := make(chan gapi.Next) - obj.wg.Add(1) - go func() { - defer obj.wg.Done() - defer close(ch) // this will run before the obj.wg.Done() - if !obj.initialized { - next := gapi.Next{ - Err: fmt.Errorf("%s: MyGAPI is not initialized", Name), - Exit: true, // exit, b/c programming error? - } - ch <- next - } - startChan := make(chan struct{}) // start signal - close(startChan) // kick it off! - - ticker := make(<-chan time.Time) - if obj.data.NoStreamWatch || obj.Interval <= 0 { - ticker = nil - } else { - // arbitrarily change graph every interval seconds - t := time.NewTicker(time.Duration(obj.Interval) * time.Second) - defer t.Stop() - ticker = t.C - } - for { - select { - case <-startChan: // kick the loop once at start - startChan = nil // disable - // pass - case <-ticker: - // pass - case <-obj.closeChan: - return - } - - log.Printf("%s: Generating new graph...", Name) - select { - case ch <- gapi.Next{}: // trigger a run - case <-obj.closeChan: - return - } - } - }() - return ch -} - -// Close shuts down the MyGAPI. -func (obj *MyGAPI) Close() error { - if !obj.initialized { - return fmt.Errorf("%s: MyGAPI is not initialized", Name) - } - close(obj.closeChan) - obj.wg.Wait() - obj.initialized = false // closed = true - return nil -} - -// Run runs an embedded mgmt server. -func Run(count uint) error { - - obj := &mgmt.Main{} - obj.Program = "libmgmt" // TODO: set on compilation - obj.Version = "0.0.1" // TODO: set on compilation - obj.TmpPrefix = true - obj.IdealClusterSize = -1 - obj.ConvergedTimeout = -1 - obj.Noop = true - - //obj.GAPI = &MyGAPI{ // graph API - // Name: "libmgmt", // TODO: set on compilation - // Count: count, // number of vertices to add - // Interval: 15, // arbitrarily change graph every 15 seconds - //} - - if err := obj.Init(); err != nil { - return err - } - - // install the exit signal handler - exit := make(chan struct{}) - defer close(exit) - go func() { - signals := make(chan os.Signal, 1) - signal.Notify(signals, os.Interrupt) // catch ^C - //signal.Notify(signals, os.Kill) // catch signals - signal.Notify(signals, syscall.SIGTERM) - - select { - case sig := <-signals: // any signal will do - if sig == os.Interrupt { - log.Println("Interrupted by ^C") - obj.Exit(nil) - return - } - log.Println("Interrupted by signal") - obj.Exit(fmt.Errorf("killed by %v", sig)) - return - case <-exit: - return - } - }() - - return obj.Run() -} - -func main() { - log.Printf("Hello!") - var count uint = 1 // default - if len(os.Args) == 2 { - if i, err := strconv.Atoi(os.Args[1]); err == nil && i > 0 { - count = uint(i) - } - } - if err := Run(count); err != nil { - fmt.Println(err) - os.Exit(1) - return - } - log.Printf("Goodbye!") -} diff --git a/examples/lib/libmgmt3.go b/examples/lib/libmgmt3.go deleted file mode 100644 index 735d669f82..0000000000 --- a/examples/lib/libmgmt3.go +++ /dev/null @@ -1,264 +0,0 @@ -// libmgmt example of send->recv -package main - -import ( - "fmt" - "log" - "os" - "os/signal" - "sync" - "syscall" - "time" - - "github.com/purpleidea/mgmt/engine" - "github.com/purpleidea/mgmt/engine/resources" - "github.com/purpleidea/mgmt/gapi" - mgmt "github.com/purpleidea/mgmt/lib" - "github.com/purpleidea/mgmt/pgraph" - - "github.com/urfave/cli/v2" -) - -// XXX: this has not been updated to latest GAPI/Deploy API. Patches welcome! - -const ( - // Name is the name of this frontend. - Name = "libmgmt" -) - -// MyGAPI implements the main GAPI interface. -type MyGAPI struct { - Name string // graph name - Interval uint // refresh interval, 0 to never refresh - - data *gapi.Data - initialized bool - closeChan chan struct{} - wg sync.WaitGroup // sync group for tunnel go routines -} - -// NewMyGAPI creates a new MyGAPI struct and calls Init(). -func NewMyGAPI(data *gapi.Data, name string, interval uint) (*MyGAPI, error) { - obj := &MyGAPI{ - Name: name, - Interval: interval, - } - return obj, obj.Init(data) -} - -// CliFlags returns a list of flags used by the passed in subcommand. -func (obj *MyGAPI) CliFlags(string) []cli.Flag { - return []cli.Flag{ - &cli.StringFlag{ - Name: obj.Name, - Value: "", - Usage: "run", - }, - } -} - -// Cli takes a cli.Context and some other info, and returns our GAPI. If there -// are any validation problems, you should return an error. -func (obj *MyGAPI) Cli(cliInfo *gapi.CliInfo) (*gapi.Deploy, error) { - c := cliInfo.CliContext - //fs := cliInfo.Fs // copy files from local filesystem *into* this fs... - //debug := cliInfo.Debug - //logf := func(format string, v ...interface{}) { - // cliInfo.Logf(Name+": "+format, v...) - //} - - return &gapi.Deploy{ - Name: obj.Name, - Noop: c.Bool("noop"), - Sema: c.Int("sema"), - GAPI: &MyGAPI{}, - }, nil -} - -// Init initializes the MyGAPI struct. -func (obj *MyGAPI) Init(data *gapi.Data) error { - if obj.initialized { - return fmt.Errorf("already initialized") - } - if obj.Name == "" { - return fmt.Errorf("the graph name must be specified") - } - obj.data = data // store for later - obj.closeChan = make(chan struct{}) - obj.initialized = true - return nil -} - -// Graph returns a current Graph. -func (obj *MyGAPI) Graph() (*pgraph.Graph, error) { - if !obj.initialized { - return nil, fmt.Errorf("%s: MyGAPI is not initialized", Name) - } - - g, err := pgraph.NewGraph(obj.Name) - if err != nil { - return nil, err - } - - content := "Delete me to trigger a notification!\n" - f0 := &resources.FileRes{ - Path: "/tmp/mgmt/README", - Content: &content, - State: "present", - } - - g.AddVertex(f0) - - p1 := &resources.PasswordRes{ - Length: 8, // generated string will have this many characters - Saved: true, // this causes passwords to be stored in plain text! - } - g.AddVertex(p1) - - f1 := &resources.FileRes{ - Path: "/tmp/mgmt/secret", - //Content: p1.Password, // won't work - State: "present", - } - // XXX: add send->recv! - //Recv: map[string]*engine.Send{ - // "Content": {Res: p1, Key: "Password"}, - //}, - - g.AddVertex(f1) - - n1 := &resources.NoopRes{} - - g.AddVertex(n1) - - e0 := &engine.Edge{Name: "e0"} - e0.Notify = true // send a notification from f0 to p1 - g.AddEdge(f0, p1, e0) - - g.AddEdge(p1, f1, &engine.Edge{Name: "e1"}) - - e2 := &engine.Edge{Name: "e2"} - e2.Notify = true // send a notification from f1 to n1 - g.AddEdge(f1, n1, e2) - - //g, err := config.NewGraphFromConfig(obj.data.Hostname, obj.data.World, obj.data.Noop) - return g, nil -} - -// Next returns nil errors every time there could be a new graph. -func (obj *MyGAPI) Next() chan gapi.Next { - ch := make(chan gapi.Next) - obj.wg.Add(1) - go func() { - defer obj.wg.Done() - defer close(ch) // this will run before the obj.wg.Done() - if !obj.initialized { - next := gapi.Next{ - Err: fmt.Errorf("%s: MyGAPI is not initialized", Name), - Exit: true, // exit, b/c programming error? - } - ch <- next - } - startChan := make(chan struct{}) // start signal - close(startChan) // kick it off! - - ticker := make(<-chan time.Time) - if obj.data.NoStreamWatch || obj.Interval <= 0 { - ticker = nil - } else { - // arbitrarily change graph every interval seconds - t := time.NewTicker(time.Duration(obj.Interval) * time.Second) - defer t.Stop() - ticker = t.C - } - for { - select { - case <-startChan: // kick the loop once at start - startChan = nil // disable - // pass - case <-ticker: - // pass - case <-obj.closeChan: - return - } - - log.Printf("%s: Generating new graph...", Name) - select { - case ch <- gapi.Next{}: // trigger a run - case <-obj.closeChan: - return - } - } - }() - return ch -} - -// Close shuts down the MyGAPI. -func (obj *MyGAPI) Close() error { - if !obj.initialized { - return fmt.Errorf("%s: MyGAPI is not initialized", Name) - } - close(obj.closeChan) - obj.wg.Wait() - obj.initialized = false // closed = true - return nil -} - -// Run runs an embedded mgmt server. -func Run() error { - - obj := &mgmt.Main{} - obj.Program = "libmgmt" // TODO: set on compilation - obj.Version = "0.0.1" // TODO: set on compilation - obj.TmpPrefix = true // disable for easy debugging - //prefix := "/tmp/testprefix/" - //obj.Prefix = &p // enable for easy debugging - obj.IdealClusterSize = -1 - obj.ConvergedTimeout = -1 - obj.Noop = false // FIXME: careful! - - //obj.GAPI = &MyGAPI{ // graph API - // Name: "libmgmt", // TODO: set on compilation - // Interval: 60 * 10, // arbitrarily change graph every 15 seconds - //} - - if err := obj.Init(); err != nil { - return err - } - - // install the exit signal handler - exit := make(chan struct{}) - defer close(exit) - go func() { - signals := make(chan os.Signal, 1) - signal.Notify(signals, os.Interrupt) // catch ^C - //signal.Notify(signals, os.Kill) // catch signals - signal.Notify(signals, syscall.SIGTERM) - - select { - case sig := <-signals: // any signal will do - if sig == os.Interrupt { - log.Println("Interrupted by ^C") - obj.Exit(nil) - return - } - log.Println("Interrupted by signal") - obj.Exit(fmt.Errorf("killed by %v", sig)) - return - case <-exit: - return - } - }() - - return obj.Run() -} - -func main() { - log.Printf("Hello!") - if err := Run(); err != nil { - fmt.Println(err) - os.Exit(1) - return - } - log.Printf("Goodbye!") -} 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 1cc777f352..f728925aaa 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 @@ -1347,6 +1411,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 { @@ -1582,6 +1670,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 @@ -1922,6 +2034,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 @@ -2172,6 +2308,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 @@ -2406,6 +2560,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 @@ -2727,6 +2890,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 @@ -2773,8 +2963,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 @@ -3408,6 +3599,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 @@ -3464,6 +3656,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 @@ -3810,6 +4003,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 @@ -4028,6 +4328,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) { @@ -4198,6 +4503,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 @@ -4542,6 +4852,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 @@ -4632,6 +4959,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 @@ -4722,6 +5054,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 @@ -4812,7 +5149,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 } @@ -4992,7 +5329,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 } @@ -5122,7 +5459,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 } @@ -5254,7 +5591,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 } @@ -5572,7 +5909,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 @@ -6086,7 +6423,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 @@ -6550,7 +6887,7 @@ func (obj *ExprStruct) Func() (interfaces.Func, error) { } // composite func (list, map, struct) - return &structs.CompositeFunc{ + return &CompositeFunc{ Type: typ, }, nil } @@ -6704,17 +7041,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 @@ -6742,10 +7076,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. @@ -6883,7 +7217,6 @@ func (obj *ExprFunc) Interpolate() (interfaces.Expr, error) { Function: obj.Function, function: obj.function, Values: obj.Values, - V: obj.V, }, nil } @@ -6947,7 +7280,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 } @@ -7045,7 +7377,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 @@ -7081,7 +7413,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 } @@ -7399,52 +7731,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 @@ -7454,59 +7741,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 } @@ -7515,11 +7835,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 @@ -8370,52 +8691,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 @@ -8426,7 +8702,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") @@ -8434,112 +8709,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 @@ -8729,37 +8967,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 @@ -8776,49 +8984,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 @@ -9160,7 +9338,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 } @@ -9178,13 +9356,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, @@ -9193,7 +9367,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 } @@ -9208,10 +9382,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 f301ebf315..fb9625c324 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 26887aaf3a..c9bed708e8 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 4dc7c95804..dda16b74e6 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") diff --git a/test/shell/libmgmt-change1.go b/test/shell/libmgmt-change1.go deleted file mode 100644 index 5e32107487..0000000000 --- a/test/shell/libmgmt-change1.go +++ /dev/null @@ -1,181 +0,0 @@ -package main - -import ( - "fmt" - "log" - "os" - "os/signal" - "sync" - "syscall" - "time" - - "github.com/purpleidea/mgmt/engine" - "github.com/purpleidea/mgmt/gapi" - mgmt "github.com/purpleidea/mgmt/lib" - "github.com/purpleidea/mgmt/pgraph" -) - -// MyGAPI implements the main GAPI interface. -type MyGAPI struct { - Name string // graph name - Interval uint // refresh interval, 0 to never refresh - - data gapi.Data - initialized bool - closeChan chan struct{} - wg sync.WaitGroup // sync group for tunnel go routines -} - -// NewMyGAPI creates a new MyGAPI struct and calls Init(). -func NewMyGAPI(data gapi.Data, name string, interval uint) (*MyGAPI, error) { - obj := &MyGAPI{ - Name: name, - Interval: interval, - } - return obj, obj.Init(data) -} - -// Init initializes the MyGAPI struct. -func (obj *MyGAPI) Init(data gapi.Data) error { - if obj.initialized { - return fmt.Errorf("already initialized") - } - if obj.Name == "" { - return fmt.Errorf("the graph name must be specified") - } - - obj.data = data // store for later - obj.closeChan = make(chan struct{}) - obj.initialized = true - return nil -} - -// Graph returns a current Graph. -func (obj *MyGAPI) Graph() (*pgraph.Graph, error) { - if !obj.initialized { - return nil, fmt.Errorf("%s: MyGAPI is not initialized", obj.Name) - } - - var err error - g, err := pgraph.NewGraph(obj.Name) - if err != nil { - return nil, err - } - - n0, err := engine.NewNamedResource("noop", "noop1") - if err != nil { - return nil, err - } - g.AddVertex(n0) - - //g, err := config.NewGraphFromConfig(obj.data.Hostname, obj.data.World, obj.data.Noop) - return g, nil -} - -// Next returns nil errors every time there could be a new graph. -func (obj *MyGAPI) Next() chan gapi.Next { - ch := make(chan gapi.Next) - obj.wg.Add(1) - go func() { - defer obj.wg.Done() - defer close(ch) // this will run before the obj.wg.Done() - if !obj.initialized { - next := gapi.Next{ - Err: fmt.Errorf("%s: MyGAPI is not initialized", obj.Name), - Exit: true, // exit, b/c programming error? - } - ch <- next - return - } - - log.Printf("%s: Generating a bunch of new graphs...", obj.Name) - ch <- gapi.Next{} - log.Printf("%s: New graph...", obj.Name) - ch <- gapi.Next{} - log.Printf("%s: New graph...", obj.Name) - ch <- gapi.Next{} - log.Printf("%s: New graph...", obj.Name) - ch <- gapi.Next{} - log.Printf("%s: New graph...", obj.Name) - ch <- gapi.Next{} - log.Printf("%s: New graph...", obj.Name) - ch <- gapi.Next{} - log.Printf("%s: New graph...", obj.Name) - ch <- gapi.Next{} - log.Printf("%s: New graph...", obj.Name) - ch <- gapi.Next{} - log.Printf("%s: New graph...", obj.Name) - ch <- gapi.Next{} - time.Sleep(1 * time.Second) - log.Printf("%s: Done generating graphs!", obj.Name) - }() - return ch -} - -// Close shuts down the MyGAPI. -func (obj *MyGAPI) Close() error { - if !obj.initialized { - return fmt.Errorf("%s: MyGAPI is not initialized", obj.Name) - } - close(obj.closeChan) - obj.wg.Wait() - obj.initialized = false // closed = true - return nil -} - -// Run runs an embedded mgmt server. -func Run() error { - - obj := &mgmt.Main{} - obj.Program = "libmgmt" // TODO: set on compilation - obj.Version = "0.0.1" // TODO: set on compilation - obj.TmpPrefix = true - obj.IdealClusterSize = -1 - obj.ConvergedTimeout = 5 - obj.Noop = false // does stuff! - - obj.GAPI = &MyGAPI{ // graph API - Name: obj.Program, // graph name - Interval: 15, // arbitrarily change graph every 15 seconds - } - - if err := obj.Init(); err != nil { - return err - } - - // install the exit signal handler - exit := make(chan struct{}) - defer close(exit) - go func() { - signals := make(chan os.Signal, 1) - signal.Notify(signals, os.Interrupt) // catch ^C - //signal.Notify(signals, os.Kill) // catch signals - signal.Notify(signals, syscall.SIGTERM) - - select { - case sig := <-signals: // any signal will do - if sig == os.Interrupt { - log.Println("Interrupted by ^C") - obj.Exit(nil) - return - } - log.Println("Interrupted by signal") - obj.Exit(fmt.Errorf("killed by %v", sig)) - return - case <-exit: - return - } - }() - - return obj.Run() -} - -func main() { - log.Printf("Hello!") - if err := Run(); err != nil { - fmt.Println(err) - os.Exit(1) - return - } - log.Printf("Goodbye!") -} diff --git a/test/shell/libmgmt-change2.go b/test/shell/libmgmt-change2.go deleted file mode 100644 index 87d9622960..0000000000 --- a/test/shell/libmgmt-change2.go +++ /dev/null @@ -1,194 +0,0 @@ -package main - -import ( - "fmt" - "log" - "os" - "os/signal" - "sync" - "syscall" - "time" - - "github.com/purpleidea/mgmt/engine" - "github.com/purpleidea/mgmt/gapi" - mgmt "github.com/purpleidea/mgmt/lib" - "github.com/purpleidea/mgmt/pgraph" -) - -// MyGAPI implements the main GAPI interface. -type MyGAPI struct { - Name string // graph name - Interval uint // refresh interval, 0 to never refresh - - flipflop bool // flip flop - autoGroup bool - - data gapi.Data - initialized bool - closeChan chan struct{} - wg sync.WaitGroup // sync group for tunnel go routines -} - -// NewMyGAPI creates a new MyGAPI struct and calls Init(). -func NewMyGAPI(data gapi.Data, name string, interval uint) (*MyGAPI, error) { - obj := &MyGAPI{ - Name: name, - Interval: interval, - } - return obj, obj.Init(data) -} - -// Init initializes the MyGAPI struct. -func (obj *MyGAPI) Init(data gapi.Data) error { - if obj.initialized { - return fmt.Errorf("already initialized") - } - if obj.Name == "" { - return fmt.Errorf("the graph name must be specified") - } - - //obj.autoGroup = false // XXX: no panic - obj.autoGroup = true // XXX: causes panic! - - obj.data = data // store for later - obj.closeChan = make(chan struct{}) - obj.initialized = true - return nil -} - -// Graph returns a current Graph. -func (obj *MyGAPI) Graph() (*pgraph.Graph, error) { - if !obj.initialized { - return nil, fmt.Errorf("%s: MyGAPI is not initialized", obj.Name) - } - - var err error - g, err := pgraph.NewGraph(obj.Name) - if err != nil { - return nil, err - } - - if !obj.flipflop { - n0, err := engine.NewNamedResource("noop", "noop0") - if err != nil { - return nil, err - } - g.AddVertex(n0) - - } else { - // NOTE: these will get autogrouped - n1, err := engine.NewNamedResource("noop", "noop1") - if err != nil { - return nil, err - } - n1.Meta().AutoGroup = obj.autoGroup // enable or disable it - g.AddVertex(n1) - - n2, err := engine.NewNamedResource("noop", "noop2") - if err != nil { - return nil, err - } - n2.Meta().AutoGroup = obj.autoGroup // enable or disable it - g.AddVertex(n2) - } - obj.flipflop = !obj.flipflop // flip the bool - - //g, err := config.NewGraphFromConfig(obj.data.Hostname, obj.data.World, obj.data.Noop) - return g, nil -} - -// Next returns nil errors every time there could be a new graph. -func (obj *MyGAPI) Next() chan gapi.Next { - ch := make(chan gapi.Next) - obj.wg.Add(1) - go func() { - defer obj.wg.Done() - defer close(ch) // this will run before the obj.wg.Done() - if !obj.initialized { - next := gapi.Next{ - Err: fmt.Errorf("%s: MyGAPI is not initialized", obj.Name), - Exit: true, // exit, b/c programming error? - } - ch <- next - return - } - - log.Printf("%s: Generating a bunch of new graphs...", obj.Name) - ch <- gapi.Next{} - log.Printf("%s: Second generation...", obj.Name) - ch <- gapi.Next{} - log.Printf("%s: Third generation...", obj.Name) - ch <- gapi.Next{} - time.Sleep(1 * time.Second) - log.Printf("%s: Done generating graphs!", obj.Name) - }() - return ch -} - -// Close shuts down the MyGAPI. -func (obj *MyGAPI) Close() error { - if !obj.initialized { - return fmt.Errorf("%s: MyGAPI is not initialized", obj.Name) - } - close(obj.closeChan) - obj.wg.Wait() - obj.initialized = false // closed = true - return nil -} - -// Run runs an embedded mgmt server. -func Run() error { - - obj := &mgmt.Main{} - obj.Program = "libmgmt" // TODO: set on compilation - obj.Version = "0.0.1" // TODO: set on compilation - obj.TmpPrefix = true - obj.IdealClusterSize = -1 - obj.ConvergedTimeout = 5 - obj.Noop = false // does stuff! - - obj.GAPI = &MyGAPI{ // graph API - Name: obj.Program, // graph name - Interval: 15, // arbitrarily change graph every 15 seconds - } - - if err := obj.Init(); err != nil { - return err - } - - // install the exit signal handler - exit := make(chan struct{}) - defer close(exit) - go func() { - signals := make(chan os.Signal, 1) - signal.Notify(signals, os.Interrupt) // catch ^C - //signal.Notify(signals, os.Kill) // catch signals - signal.Notify(signals, syscall.SIGTERM) - - select { - case sig := <-signals: // any signal will do - if sig == os.Interrupt { - log.Println("Interrupted by ^C") - obj.Exit(nil) - return - } - log.Println("Interrupted by signal") - obj.Exit(fmt.Errorf("killed by %v", sig)) - return - case <-exit: - return - } - }() - - return obj.Run() -} - -func main() { - log.Printf("Hello!") - if err := Run(); err != nil { - fmt.Println(err) - os.Exit(1) - return - } - log.Printf("Goodbye!") -}