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!") -}