Init the mutex everywhere, but consider calling Init instead and plumbing though the data input field in the future.
9599 lines
304 KiB
Go
9599 lines
304 KiB
Go
// Mgmt
|
|
// Copyright (C) 2013-2023+ James Shubin and the project contributors
|
|
// Written by James Shubin <james@shubin.ca> 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 <http://www.gnu.org/licenses/>.
|
|
|
|
// Package ast contains the structs implementing and some utility functions for
|
|
// interacting with the abstract syntax tree for the mcl language.
|
|
package ast
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"reflect"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/purpleidea/mgmt/engine"
|
|
engineUtil "github.com/purpleidea/mgmt/engine/util"
|
|
"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/inputs"
|
|
"github.com/purpleidea/mgmt/lang/interfaces"
|
|
"github.com/purpleidea/mgmt/lang/types"
|
|
"github.com/purpleidea/mgmt/lang/types/full"
|
|
langutil "github.com/purpleidea/mgmt/lang/util"
|
|
"github.com/purpleidea/mgmt/pgraph"
|
|
"github.com/purpleidea/mgmt/util"
|
|
"github.com/purpleidea/mgmt/util/errwrap"
|
|
|
|
"golang.org/x/time/rate"
|
|
)
|
|
|
|
const (
|
|
// EdgeNotify declares an edge a -> b, such that a notification occurs.
|
|
// This is most similar to "notify" in Puppet.
|
|
EdgeNotify = "notify"
|
|
|
|
// EdgeBefore declares an edge a -> b, such that no notification occurs.
|
|
// This is most similar to "before" in Puppet.
|
|
EdgeBefore = "before"
|
|
|
|
// EdgeListen declares an edge a <- b, such that a notification occurs.
|
|
// This is most similar to "subscribe" in Puppet.
|
|
EdgeListen = "listen"
|
|
|
|
// EdgeDepend declares an edge a <- b, such that no notification occurs.
|
|
// This is most similar to "require" in Puppet.
|
|
EdgeDepend = "depend"
|
|
|
|
// MetaField is the prefix used to specify a meta parameter for the res.
|
|
MetaField = "meta"
|
|
|
|
// AllowUserDefinedPolyFunc specifies if we allow user-defined
|
|
// polymorphic functions or not. At the moment this is not implemented.
|
|
// XXX: not implemented
|
|
AllowUserDefinedPolyFunc = false
|
|
|
|
// RequireStrictModulePath can be set to true if you wish to ignore any
|
|
// of the metadata parent path searching. By default that is allowed,
|
|
// unless it is disabled per module with ParentPathBlock. This option is
|
|
// here in case we decide that the parent module searching is confusing.
|
|
RequireStrictModulePath = false
|
|
|
|
// RequireTopologicalOrdering specifies if the code *must* be written in
|
|
// a topologically correct order. This prevents "out-of-order" code that
|
|
// is valid, but possibly confusing to the read. The main author
|
|
// (purpleidea) believes that this is better of as false. This is
|
|
// because occasionally code might be more logical when out-of-order,
|
|
// and hiding the fundamental structure of the language isn't elegant.
|
|
RequireTopologicalOrdering = false
|
|
|
|
// TopologicalOrderingWarning specifies whether a warning is emitted if
|
|
// the code is not in a topologically correct order. If this warning is
|
|
// seen too often, then we should consider disabling this by default.
|
|
TopologicalOrderingWarning = true
|
|
|
|
// varOrderingPrefix is a magic prefix used for the Ordering graph.
|
|
varOrderingPrefix = "var:"
|
|
|
|
// paramOrderingPrefix is a magic prefix used for the Ordering graph.
|
|
paramOrderingPrefix = "param:"
|
|
|
|
// funcOrderingPrefix is a magic prefix used for the Ordering graph.
|
|
funcOrderingPrefix = "func:"
|
|
|
|
// classOrderingPrefix is a magic prefix used for the Ordering graph.
|
|
classOrderingPrefix = "class:"
|
|
|
|
// legacyProgSetScope enables an old version of the SetScope function
|
|
// in StmtProg. Use it for experimentation if you don't want to use the
|
|
// Ordering function for some reason. In general, this should be false!
|
|
legacyProgSetScope = false
|
|
|
|
// ErrNoStoredScope is an error that tells us we can't get a scope here.
|
|
ErrNoStoredScope = interfaces.Error("scope is not stored in this node")
|
|
|
|
// ErrFuncPointerNil is an error that explains the function pointer for
|
|
// table lookup is missing. If this happens, it's most likely a
|
|
// programming error.
|
|
ErrFuncPointerNil = interfaces.Error("missing func pointer for table")
|
|
|
|
// ErrTableNoValue is an error that explains the table is missing a
|
|
// value. If this happens, it's most likely a programming error.
|
|
ErrTableNoValue = interfaces.Error("missing value in table")
|
|
)
|
|
|
|
var (
|
|
// orderingGraphSingleton is used for debugging the ordering graph.
|
|
orderingGraphSingleton = false
|
|
)
|
|
|
|
// StmtBind is a representation of an assignment, which binds a variable to an
|
|
// expression.
|
|
type StmtBind struct {
|
|
Ident string
|
|
Value interfaces.Expr
|
|
}
|
|
|
|
// String returns a short representation of this statement.
|
|
func (obj *StmtBind) String() string {
|
|
return fmt.Sprintf("bind(%s)", obj.Ident)
|
|
}
|
|
|
|
// Apply is a general purpose iterator method that operates on any AST node. It
|
|
// is not used as the primary AST traversal function because it is less readable
|
|
// and easy to reason about than manually implementing traversal for each node.
|
|
// Nevertheless, it is a useful facility for operations that might only apply to
|
|
// a select number of node types, since they won't need extra noop iterators...
|
|
func (obj *StmtBind) Apply(fn func(interfaces.Node) error) error {
|
|
if err := obj.Value.Apply(fn); err != nil {
|
|
return err
|
|
}
|
|
return fn(obj)
|
|
}
|
|
|
|
// Init initializes this branch of the AST, and returns an error if it fails to
|
|
// validate.
|
|
func (obj *StmtBind) Init(data *interfaces.Data) error {
|
|
return obj.Value.Init(data)
|
|
}
|
|
|
|
// Interpolate returns a new node (aka a copy) once it has been expanded. This
|
|
// generally increases the size of the AST when it is used. It calls Interpolate
|
|
// on any child elements and builds the new node with those new node contents.
|
|
func (obj *StmtBind) Interpolate() (interfaces.Stmt, error) {
|
|
interpolated, err := obj.Value.Interpolate()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &StmtBind{
|
|
Ident: obj.Ident,
|
|
Value: interpolated,
|
|
}, nil
|
|
}
|
|
|
|
// Copy returns a light copy of this struct. Anything static will not be copied.
|
|
func (obj *StmtBind) Copy() (interfaces.Stmt, error) {
|
|
copied := false
|
|
value, err := obj.Value.Copy()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if value != obj.Value { // must have been copied, or pointer would be same
|
|
copied = true
|
|
}
|
|
|
|
if !copied { // it's static
|
|
return obj, nil
|
|
}
|
|
return &StmtBind{
|
|
Ident: obj.Ident,
|
|
Value: value,
|
|
}, nil
|
|
}
|
|
|
|
// Ordering returns a graph of the scope ordering that represents the data flow.
|
|
// This can be used in SetScope so that it knows the correct order to run it in.
|
|
// We only really care about the consumers here, because the "produces" aspect
|
|
// of this resource is handled by the StmtProg Ordering function. This is
|
|
// because the "prog" allows out-of-order statements, therefore it solves this
|
|
// by running an early (second) loop through the program and peering into this
|
|
// Stmt and extracting the produced name.
|
|
func (obj *StmtBind) Ordering(produces map[string]interfaces.Node) (*pgraph.Graph, map[interfaces.Node]string, error) {
|
|
graph, err := pgraph.NewGraph("ordering")
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
graph.AddVertex(obj)
|
|
|
|
// additional constraint...
|
|
edge := &pgraph.SimpleEdge{Name: "stmtbindvalue"}
|
|
graph.AddEdge(obj.Value, obj, edge) // prod -> cons
|
|
|
|
cons := make(map[interfaces.Node]string)
|
|
|
|
g, c, err := obj.Value.Ordering(produces)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
graph.AddGraph(g) // add in the child graph
|
|
|
|
for k, v := range c { // c is consumes
|
|
x, exists := cons[k]
|
|
if exists && v != x {
|
|
return nil, nil, fmt.Errorf("consumed value is different, got `%+v`, expected `%+v`", x, v)
|
|
}
|
|
cons[k] = v // add to map
|
|
|
|
n, exists := produces[v]
|
|
if !exists {
|
|
continue
|
|
}
|
|
edge := &pgraph.SimpleEdge{Name: "stmtbind"}
|
|
graph.AddEdge(n, k, edge)
|
|
}
|
|
|
|
return graph, cons, nil
|
|
}
|
|
|
|
// SetScope stores the scope for later use in this resource and its children,
|
|
// which it propagates this downwards to.
|
|
func (obj *StmtBind) SetScope(scope *interfaces.Scope) error {
|
|
emptyContext := map[string]interfaces.Expr{}
|
|
return obj.Value.SetScope(scope, emptyContext)
|
|
}
|
|
|
|
// Unify returns the list of invariants that this node produces. It recursively
|
|
// calls Unify on any children elements that exist in the AST, and returns the
|
|
// collection to the caller.
|
|
func (obj *StmtBind) Unify() ([]interfaces.Invariant, error) {
|
|
return obj.Value.Unify()
|
|
}
|
|
|
|
// Graph returns the reactive function graph which is expressed by this node. It
|
|
// includes any vertices produced by this node, and the appropriate edges to any
|
|
// vertices that are produced by its children. Nodes which fulfill the Expr
|
|
// interface directly produce vertices (and possible children) where as nodes
|
|
// that fulfill the Stmt interface do not produces vertices, where as their
|
|
// children might. This particular bind statement adds its linked expression to
|
|
// the graph. It is not logically done in the ExprVar since that could exist
|
|
// multiple times for the single binding operation done here.
|
|
func (obj *StmtBind) Graph() (*pgraph.Graph, error) {
|
|
emptyContext := map[string]interfaces.Func{}
|
|
g, _, err := obj.Value.Graph(emptyContext)
|
|
return g, err
|
|
}
|
|
|
|
// 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) {
|
|
return interfaces.EmptyOutput(), nil
|
|
}
|
|
|
|
// StmtRes is a representation of a resource and possibly some edges. The `Name`
|
|
// value can be a single string or a list of strings. The former will produce a
|
|
// single resource, the latter produces a list of resources. Using this list
|
|
// mechanism is a safe alternative to traditional flow control like `for` loops.
|
|
// TODO: Consider expanding Name to have this return a list of Res's in the
|
|
// Output function if it is a map[name]struct{}, or even a map[[]name]struct{}.
|
|
type StmtRes struct {
|
|
data *interfaces.Data
|
|
|
|
Kind string // kind of resource, eg: pkg, file, svc, etc...
|
|
Name interfaces.Expr // unique name for the res of this kind
|
|
namePtr interfaces.Func // ptr for table lookup
|
|
Contents []StmtResContents // list of fields/edges in parsed order
|
|
}
|
|
|
|
// String returns a short representation of this statement.
|
|
func (obj *StmtRes) String() string {
|
|
// TODO: add .String() for Contents and Name
|
|
return fmt.Sprintf("res(%s)", obj.Kind)
|
|
}
|
|
|
|
// Apply is a general purpose iterator method that operates on any AST node. It
|
|
// is not used as the primary AST traversal function because it is less readable
|
|
// and easy to reason about than manually implementing traversal for each node.
|
|
// Nevertheless, it is a useful facility for operations that might only apply to
|
|
// a select number of node types, since they won't need extra noop iterators...
|
|
func (obj *StmtRes) Apply(fn func(interfaces.Node) error) error {
|
|
if err := obj.Name.Apply(fn); err != nil {
|
|
return err
|
|
}
|
|
for _, x := range obj.Contents {
|
|
if err := x.Apply(fn); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return fn(obj)
|
|
}
|
|
|
|
// Init initializes this branch of the AST, and returns an error if it fails to
|
|
// validate.
|
|
func (obj *StmtRes) Init(data *interfaces.Data) error {
|
|
if strings.Contains(obj.Kind, "_") && obj.Kind != interfaces.PanicResKind {
|
|
return fmt.Errorf("kind must not contain underscores")
|
|
}
|
|
|
|
obj.data = data
|
|
if err := obj.Name.Init(data); err != nil {
|
|
return err
|
|
}
|
|
fieldNames := make(map[string]struct{})
|
|
metaNames := make(map[string]struct{})
|
|
for _, x := range obj.Contents {
|
|
|
|
// Duplicate checking for identical field names.
|
|
if line, ok := x.(*StmtResField); ok {
|
|
// Was the field already seen in this resource?
|
|
if _, exists := fieldNames[line.Field]; exists {
|
|
return fmt.Errorf("resource has duplicate field of: %s", line.Field)
|
|
}
|
|
fieldNames[line.Field] = struct{}{}
|
|
}
|
|
|
|
// NOTE: you can have as many *StmtResEdge lines as you want =D
|
|
|
|
if line, ok := x.(*StmtResMeta); ok {
|
|
// Was the meta entry already seen in this resource?
|
|
// Ignore the generic MetaField struct field for now.
|
|
// You're allowed to have more than one Meta field, but
|
|
// they can't contain the same field twice.
|
|
if _, exists := metaNames[line.Property]; exists && line.Property != MetaField {
|
|
return fmt.Errorf("resource has duplicate meta entry of: %s", line.Property)
|
|
}
|
|
metaNames[line.Property] = struct{}{}
|
|
}
|
|
|
|
if err := x.Init(data); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Interpolate returns a new node (aka a copy) once it has been expanded. This
|
|
// generally increases the size of the AST when it is used. It calls Interpolate
|
|
// on any child elements and builds the new node with those new node contents.
|
|
func (obj *StmtRes) Interpolate() (interfaces.Stmt, error) {
|
|
name, err := obj.Name.Interpolate()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
contents := []StmtResContents{}
|
|
for _, x := range obj.Contents { // make sure we preserve ordering...
|
|
interpolated, err := x.Interpolate()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
contents = append(contents, interpolated)
|
|
}
|
|
|
|
return &StmtRes{
|
|
data: obj.data,
|
|
Kind: obj.Kind,
|
|
Name: name,
|
|
Contents: contents,
|
|
}, nil
|
|
}
|
|
|
|
// Copy returns a light copy of this struct. Anything static will not be copied.
|
|
func (obj *StmtRes) Copy() (interfaces.Stmt, error) {
|
|
copied := false
|
|
name, err := obj.Name.Copy()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if name != obj.Name { // must have been copied, or pointer would be same
|
|
copied = true
|
|
}
|
|
|
|
copiedContents := false
|
|
contents := []StmtResContents{}
|
|
for _, x := range obj.Contents { // make sure we preserve ordering...
|
|
cp, err := x.Copy()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if cp != x {
|
|
copiedContents = true
|
|
}
|
|
contents = append(contents, cp)
|
|
}
|
|
if copiedContents {
|
|
copied = true
|
|
} else {
|
|
contents = obj.Contents // don't re-package it unnecessarily!
|
|
}
|
|
|
|
if !copied { // it's static
|
|
return obj, nil
|
|
}
|
|
return &StmtRes{
|
|
data: obj.data,
|
|
Kind: obj.Kind,
|
|
Name: name,
|
|
Contents: contents,
|
|
}, nil
|
|
}
|
|
|
|
// Ordering returns a graph of the scope ordering that represents the data flow.
|
|
// This can be used in SetScope so that it knows the correct order to run it in.
|
|
func (obj *StmtRes) Ordering(produces map[string]interfaces.Node) (*pgraph.Graph, map[interfaces.Node]string, error) {
|
|
graph, err := pgraph.NewGraph("ordering")
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
graph.AddVertex(obj)
|
|
|
|
// Additional constraints: We know the name has to be satisfied before
|
|
// this res statement itself can be used, since we depend on that value.
|
|
edge := &pgraph.SimpleEdge{Name: "stmtresname"}
|
|
graph.AddEdge(obj.Name, obj, edge) // prod -> cons
|
|
|
|
cons := make(map[interfaces.Node]string)
|
|
|
|
g, c, err := obj.Name.Ordering(produces)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
graph.AddGraph(g) // add in the child graph
|
|
|
|
for k, v := range c { // c is consumes
|
|
x, exists := cons[k]
|
|
if exists && v != x {
|
|
return nil, nil, fmt.Errorf("consumed value is different, got `%+v`, expected `%+v`", x, v)
|
|
}
|
|
cons[k] = v // add to map
|
|
|
|
n, exists := produces[v]
|
|
if !exists {
|
|
continue
|
|
}
|
|
edge := &pgraph.SimpleEdge{Name: "stmtres"}
|
|
graph.AddEdge(n, k, edge)
|
|
}
|
|
|
|
for _, node := range obj.Contents {
|
|
g, c, err := node.Ordering(produces)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
graph.AddGraph(g) // add in the child graph
|
|
|
|
// additional constraint...
|
|
edge := &pgraph.SimpleEdge{Name: "stmtrescontents1"}
|
|
graph.AddEdge(node, obj, edge) // prod -> cons
|
|
|
|
for k, v := range c { // c is consumes
|
|
x, exists := cons[k]
|
|
if exists && v != x {
|
|
return nil, nil, fmt.Errorf("consumed value is different, got `%+v`, expected `%+v`", x, v)
|
|
}
|
|
cons[k] = v // add to map
|
|
|
|
n, exists := produces[v]
|
|
if !exists {
|
|
continue
|
|
}
|
|
edge := &pgraph.SimpleEdge{Name: "stmtrescontents2"}
|
|
graph.AddEdge(n, k, edge)
|
|
}
|
|
}
|
|
|
|
return graph, cons, nil
|
|
}
|
|
|
|
// SetScope stores the scope for later use in this resource and its children,
|
|
// which it propagates this downwards to.
|
|
func (obj *StmtRes) SetScope(scope *interfaces.Scope) error {
|
|
if err := obj.Name.SetScope(scope, map[string]interfaces.Expr{}); err != nil {
|
|
return err
|
|
}
|
|
for _, x := range obj.Contents {
|
|
if err := x.SetScope(scope); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Unify returns the list of invariants that this node produces. It recursively
|
|
// calls Unify on any children elements that exist in the AST, and returns the
|
|
// collection to the caller.
|
|
func (obj *StmtRes) Unify() ([]interfaces.Invariant, error) {
|
|
var invariants []interfaces.Invariant
|
|
|
|
invars, err := obj.Name.Unify()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
invariants = append(invariants, invars...)
|
|
|
|
// name must be a string or a list
|
|
ors := []interfaces.Invariant{}
|
|
|
|
invarStr := &interfaces.EqualsInvariant{
|
|
Expr: obj.Name,
|
|
Type: types.TypeStr,
|
|
}
|
|
ors = append(ors, invarStr)
|
|
|
|
invarListStr := &interfaces.EqualsInvariant{
|
|
Expr: obj.Name,
|
|
Type: types.NewType("[]str"),
|
|
}
|
|
ors = append(ors, invarListStr)
|
|
|
|
invar := &interfaces.ExclusiveInvariant{
|
|
Invariants: ors, // one and only one of these should be true
|
|
}
|
|
invariants = append(invariants, invar)
|
|
|
|
// collect all the invariants of each field and edge
|
|
for _, x := range obj.Contents {
|
|
invars, err := x.Unify(obj.Kind) // pass in the resource kind
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
invariants = append(invariants, invars...)
|
|
}
|
|
|
|
return invariants, nil
|
|
}
|
|
|
|
// Graph returns the reactive function graph which is expressed by this node. It
|
|
// includes any vertices produced by this node, and the appropriate edges to any
|
|
// vertices that are produced by its children. Nodes which fulfill the Expr
|
|
// interface directly produce vertices (and possible children) where as nodes
|
|
// that fulfill the Stmt interface do not produces vertices, where as their
|
|
// children might. It is interesting to note that nothing directly adds an edge
|
|
// to the resources created, but rather, once all the values (expressions) with
|
|
// no outgoing edges have produced at least a single value, then the resources
|
|
// know they're able to be built.
|
|
//
|
|
// This runs right after type unification. For this particular resource, we can
|
|
// do some additional static analysis, but only after unification has been done.
|
|
// Since I don't think it's worth extending the Stmt API for this, we can do the
|
|
// checks here at the beginning, and error out if something was invalid. In this
|
|
// particular case, the issue is one of catching duplicate meta fields.
|
|
func (obj *StmtRes) Graph() (*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, err
|
|
}
|
|
|
|
g, f, err := obj.Name.Graph(map[string]interfaces.Func{})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
graph.AddGraph(g)
|
|
obj.namePtr = f
|
|
|
|
for _, x := range obj.Contents {
|
|
g, err := x.Graph()
|
|
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
|
|
// called by this Output function if they are needed to produce the output. In
|
|
// the case of this resource statement, this is definitely the case.
|
|
func (obj *StmtRes) Output(table map[interfaces.Func]types.Value) (*interfaces.Output, error) {
|
|
if obj.namePtr == nil {
|
|
return nil, ErrFuncPointerNil
|
|
}
|
|
nameValue, exists := table[obj.namePtr]
|
|
if !exists {
|
|
return nil, ErrTableNoValue
|
|
}
|
|
|
|
names := []string{} // list of names to build
|
|
switch {
|
|
case types.TypeStr.Cmp(nameValue.Type()) == nil:
|
|
name := nameValue.Str() // must not panic
|
|
names = append(names, name)
|
|
|
|
case types.NewType("[]str").Cmp(nameValue.Type()) == nil:
|
|
for _, x := range nameValue.List() { // must not panic
|
|
name := x.Str() // must not panic
|
|
names = append(names, name)
|
|
}
|
|
|
|
default:
|
|
// programming error
|
|
return nil, fmt.Errorf("unhandled resource name type: %+v", nameValue.Type())
|
|
}
|
|
|
|
resources := []engine.Res{}
|
|
edges := []*interfaces.Edge{}
|
|
for _, name := range names {
|
|
res, err := obj.resource(table, name)
|
|
if err != nil {
|
|
return nil, errwrap.Wrapf(err, "error building resource")
|
|
}
|
|
|
|
edgeList, err := obj.edges(table, name)
|
|
if err != nil {
|
|
return nil, errwrap.Wrapf(err, "error building edges")
|
|
}
|
|
edges = append(edges, edgeList...)
|
|
|
|
if err := obj.metaparams(table, res); err != nil { // set metaparams
|
|
return nil, errwrap.Wrapf(err, "error building meta params")
|
|
}
|
|
resources = append(resources, res)
|
|
}
|
|
|
|
return &interfaces.Output{
|
|
Resources: resources,
|
|
Edges: edges,
|
|
}, nil
|
|
}
|
|
|
|
// resource is a helper function to generate the res that comes from this.
|
|
// TODO: it could memoize some of the work to avoid re-computation when looped
|
|
func (obj *StmtRes) resource(table map[interfaces.Func]types.Value, resName string) (engine.Res, error) {
|
|
res, err := engine.NewNamedResource(obj.Kind, resName)
|
|
if err != nil {
|
|
return nil, errwrap.Wrapf(err, "cannot create resource kind `%s` with named `%s`", obj.Kind, resName)
|
|
}
|
|
|
|
sv := reflect.ValueOf(res).Elem() // pointer to struct, then struct
|
|
if k := sv.Kind(); k != reflect.Struct {
|
|
panic(fmt.Sprintf("expected struct, got: %s", k))
|
|
}
|
|
|
|
mapping, err := engineUtil.LangFieldNameToStructFieldName(obj.Kind)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
st := reflect.TypeOf(res).Elem() // pointer to struct, then struct
|
|
|
|
// FIXME: we could probably simplify this code...
|
|
for _, line := range obj.Contents {
|
|
x, ok := line.(*StmtResField)
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
if x.Condition != nil {
|
|
if x.conditionPtr == nil {
|
|
return nil, ErrFuncPointerNil
|
|
}
|
|
b, exists := table[x.conditionPtr]
|
|
if !exists {
|
|
return nil, ErrTableNoValue
|
|
}
|
|
|
|
if !b.Bool() { // if value exists, and is false, skip it
|
|
continue
|
|
}
|
|
}
|
|
|
|
typ, err := x.Value.Type()
|
|
if err != nil {
|
|
return nil, errwrap.Wrapf(err, "resource field `%s` did not return a type", x.Field)
|
|
}
|
|
|
|
name, exists := mapping[x.Field] // lookup recommended field name
|
|
if !exists { // this should be caught during unification.
|
|
return nil, fmt.Errorf("field `%s` does not exist", x.Field) // user made a typo?
|
|
}
|
|
|
|
tf, exists := st.FieldByName(name) // exported field type
|
|
if !exists {
|
|
return nil, fmt.Errorf("field `%s` type does not exist", x.Field)
|
|
}
|
|
|
|
f := sv.FieldByName(name) // exported field
|
|
if !f.IsValid() || !f.CanSet() {
|
|
return nil, fmt.Errorf("field `%s` cannot be set", name) // field is broken?
|
|
}
|
|
|
|
// is expr type compatible with expected field type?
|
|
t, err := types.ResTypeOf(tf.Type)
|
|
if err != nil {
|
|
return nil, errwrap.Wrapf(err, "resource field `%s` has no compatible type", x.Field)
|
|
}
|
|
if t == nil {
|
|
// possible programming error
|
|
return nil, fmt.Errorf("resource field `%s` of nil type cannot match type `%+v`", x.Field, typ)
|
|
}
|
|
|
|
// Let the variants pass through...
|
|
if err := t.Cmp(typ); err != nil && t.Kind != types.KindVariant {
|
|
return nil, errwrap.Wrapf(err, "resource field `%s` of type `%+v`, cannot take type `%+v`", x.Field, t, typ)
|
|
}
|
|
|
|
if x.valuePtr == nil {
|
|
return nil, ErrFuncPointerNil
|
|
}
|
|
fv, exists := table[x.valuePtr]
|
|
if !exists {
|
|
return nil, ErrTableNoValue
|
|
}
|
|
|
|
// mutate the struct field f with the mcl data in fv
|
|
if err := types.Into(fv, f); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return res, nil
|
|
}
|
|
|
|
// edges is a helper function to generate the edges that come from the resource.
|
|
func (obj *StmtRes) edges(table map[interfaces.Func]types.Value, resName string) ([]*interfaces.Edge, error) {
|
|
edges := []*interfaces.Edge{}
|
|
|
|
// to and from self, map of kind, name, notify
|
|
var to = make(map[string]map[string]bool) // to this from self
|
|
var from = make(map[string]map[string]bool) // from this to self
|
|
|
|
for _, line := range obj.Contents {
|
|
x, ok := line.(*StmtResEdge)
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
if x.Condition != nil {
|
|
if x.conditionPtr == nil {
|
|
return nil, ErrFuncPointerNil
|
|
}
|
|
b, exists := table[x.conditionPtr]
|
|
if !exists {
|
|
return nil, ErrTableNoValue
|
|
}
|
|
|
|
if !b.Bool() { // if value exists, and is false, skip it
|
|
continue
|
|
}
|
|
}
|
|
|
|
if x.EdgeHalf.namePtr == nil {
|
|
return nil, ErrFuncPointerNil
|
|
}
|
|
nameValue, exists := table[x.EdgeHalf.namePtr]
|
|
if !exists {
|
|
return nil, ErrTableNoValue
|
|
}
|
|
|
|
// the edge name can be a single string or a list of strings...
|
|
|
|
names := []string{} // list of names to build
|
|
switch {
|
|
case types.TypeStr.Cmp(nameValue.Type()) == nil:
|
|
name := nameValue.Str() // must not panic
|
|
names = append(names, name)
|
|
|
|
case types.NewType("[]str").Cmp(nameValue.Type()) == nil:
|
|
for _, x := range nameValue.List() { // must not panic
|
|
name := x.Str() // must not panic
|
|
names = append(names, name)
|
|
}
|
|
|
|
default:
|
|
// programming error
|
|
return nil, fmt.Errorf("unhandled resource name type: %+v", nameValue.Type())
|
|
}
|
|
|
|
kind := x.EdgeHalf.Kind
|
|
for _, name := range names {
|
|
var notify bool
|
|
|
|
switch p := x.Property; p {
|
|
// a -> b
|
|
// a notify b
|
|
// a before b
|
|
case EdgeNotify:
|
|
notify = true
|
|
fallthrough
|
|
case EdgeBefore:
|
|
if m, exists := to[kind]; !exists {
|
|
to[kind] = make(map[string]bool)
|
|
} else if n, exists := m[name]; exists {
|
|
notify = notify || n // collate
|
|
}
|
|
to[kind][name] = notify // to this from self
|
|
|
|
// b -> a
|
|
// b listen a
|
|
// b depend a
|
|
case EdgeListen:
|
|
notify = true
|
|
fallthrough
|
|
case EdgeDepend:
|
|
if m, exists := from[kind]; !exists {
|
|
from[kind] = make(map[string]bool)
|
|
} else if n, exists := m[name]; exists {
|
|
notify = notify || n // collate
|
|
}
|
|
from[kind][name] = notify // from this to self
|
|
|
|
default:
|
|
return nil, fmt.Errorf("unknown property: %s", p)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TODO: we could detect simple loops here (if `from` and `to` have the
|
|
// same entry) but we can leave this to the proper dag checker later on
|
|
|
|
for kind, x := range to { // to this from self
|
|
for name, notify := range x {
|
|
edge := &interfaces.Edge{
|
|
Kind1: obj.Kind,
|
|
Name1: resName, // self
|
|
//Send: "",
|
|
|
|
Kind2: kind,
|
|
Name2: name,
|
|
//Recv: "",
|
|
|
|
Notify: notify,
|
|
}
|
|
edges = append(edges, edge)
|
|
}
|
|
}
|
|
for kind, x := range from { // from this to self
|
|
for name, notify := range x {
|
|
edge := &interfaces.Edge{
|
|
Kind1: kind,
|
|
Name1: name,
|
|
//Send: "",
|
|
|
|
Kind2: obj.Kind,
|
|
Name2: resName, // self
|
|
//Recv: "",
|
|
|
|
Notify: notify,
|
|
}
|
|
edges = append(edges, edge)
|
|
}
|
|
}
|
|
|
|
return edges, nil
|
|
}
|
|
|
|
// metaparams is a helper function to set the metaparams that come from the
|
|
// resource on to the individual resource we're working on.
|
|
func (obj *StmtRes) metaparams(table map[interfaces.Func]types.Value, res engine.Res) error {
|
|
meta := engine.DefaultMetaParams.Copy() // defaults
|
|
|
|
var rm *engine.ReversibleMeta
|
|
if r, ok := res.(engine.ReversibleRes); ok {
|
|
rm = r.ReversibleMeta() // get a struct with the defaults
|
|
}
|
|
var aem *engine.AutoEdgeMeta
|
|
if r, ok := res.(engine.EdgeableRes); ok {
|
|
aem = r.AutoEdgeMeta() // get a struct with the defaults
|
|
}
|
|
var agm *engine.AutoGroupMeta
|
|
if r, ok := res.(engine.GroupableRes); ok {
|
|
agm = r.AutoGroupMeta() // get a struct with the defaults
|
|
}
|
|
|
|
for _, line := range obj.Contents {
|
|
x, ok := line.(*StmtResMeta)
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
if x.Condition != nil {
|
|
if x.conditionPtr == nil {
|
|
return ErrFuncPointerNil
|
|
}
|
|
b, exists := table[x.conditionPtr]
|
|
if !exists {
|
|
return ErrTableNoValue
|
|
}
|
|
|
|
if !b.Bool() { // if value exists, and is false, skip it
|
|
continue
|
|
}
|
|
}
|
|
|
|
if x.metaExprPtr == nil {
|
|
return ErrFuncPointerNil
|
|
}
|
|
v, exists := table[x.metaExprPtr]
|
|
if !exists {
|
|
return ErrTableNoValue
|
|
}
|
|
|
|
switch p := strings.ToLower(x.Property); p {
|
|
// TODO: we could add these fields dynamically if we were fancy!
|
|
case "noop":
|
|
meta.Noop = v.Bool() // must not panic
|
|
|
|
case "retry":
|
|
x := v.Int() // must not panic
|
|
// TODO: check that it doesn't overflow
|
|
meta.Retry = int16(x)
|
|
|
|
case "retryreset":
|
|
meta.RetryReset = v.Bool() // must not panic
|
|
|
|
case "delay":
|
|
x := v.Int() // must not panic
|
|
// TODO: check that it isn't signed
|
|
meta.Delay = uint64(x)
|
|
|
|
case "poll":
|
|
x := v.Int() // must not panic
|
|
// TODO: check that it doesn't overflow and isn't signed
|
|
meta.Poll = uint32(x)
|
|
|
|
case "limit": // rate.Limit
|
|
x := v.Float() // must not panic
|
|
meta.Limit = rate.Limit(x)
|
|
|
|
case "burst":
|
|
x := v.Int() // must not panic
|
|
// TODO: check that it doesn't overflow
|
|
meta.Burst = int(x)
|
|
|
|
case "reset":
|
|
meta.Reset = v.Bool() // must not panic
|
|
|
|
case "sema": // []string
|
|
values := []string{}
|
|
for _, x := range v.List() { // must not panic
|
|
s := x.Str() // must not panic
|
|
values = append(values, s)
|
|
}
|
|
meta.Sema = values
|
|
|
|
case "rewatch":
|
|
meta.Rewatch = v.Bool() // must not panic
|
|
|
|
case "realize":
|
|
meta.Realize = v.Bool() // must not panic
|
|
|
|
case "reverse":
|
|
if v.Type().Cmp(types.TypeBool) == nil {
|
|
if rm != nil {
|
|
rm.Disabled = !v.Bool() // must not panic
|
|
}
|
|
} else {
|
|
// TODO: read values from struct into rm.XXX
|
|
}
|
|
|
|
case "autoedge":
|
|
if aem != nil {
|
|
aem.Disabled = !v.Bool() // must not panic
|
|
}
|
|
|
|
case "autogroup":
|
|
if agm != nil {
|
|
agm.Disabled = !v.Bool() // must not panic
|
|
}
|
|
|
|
case MetaField:
|
|
if val, exists := v.Struct()["noop"]; exists {
|
|
meta.Noop = val.Bool() // must not panic
|
|
}
|
|
if val, exists := v.Struct()["retry"]; exists {
|
|
x := val.Int() // must not panic
|
|
// TODO: check that it doesn't overflow
|
|
meta.Retry = int16(x)
|
|
}
|
|
if val, exists := v.Struct()["retryreset"]; exists {
|
|
meta.RetryReset = val.Bool() // must not panic
|
|
}
|
|
if val, exists := v.Struct()["delay"]; exists {
|
|
x := val.Int() // must not panic
|
|
// TODO: check that it isn't signed
|
|
meta.Delay = uint64(x)
|
|
}
|
|
if val, exists := v.Struct()["poll"]; exists {
|
|
x := val.Int() // must not panic
|
|
// TODO: check that it doesn't overflow and isn't signed
|
|
meta.Poll = uint32(x)
|
|
}
|
|
if val, exists := v.Struct()["limit"]; exists {
|
|
x := val.Float() // must not panic
|
|
meta.Limit = rate.Limit(x)
|
|
}
|
|
if val, exists := v.Struct()["burst"]; exists {
|
|
x := val.Int() // must not panic
|
|
// TODO: check that it doesn't overflow
|
|
meta.Burst = int(x)
|
|
}
|
|
if val, exists := v.Struct()["reset"]; exists {
|
|
meta.Reset = val.Bool() // must not panic
|
|
}
|
|
if val, exists := v.Struct()["sema"]; exists {
|
|
values := []string{}
|
|
for _, x := range val.List() { // must not panic
|
|
s := x.Str() // must not panic
|
|
values = append(values, s)
|
|
}
|
|
meta.Sema = values
|
|
}
|
|
if val, exists := v.Struct()["rewatch"]; exists {
|
|
meta.Rewatch = val.Bool() // must not panic
|
|
}
|
|
if val, exists := v.Struct()["realize"]; exists {
|
|
meta.Realize = val.Bool() // must not panic
|
|
}
|
|
if val, exists := v.Struct()["reverse"]; exists && rm != nil {
|
|
if val.Type().Cmp(types.TypeBool) == nil {
|
|
rm.Disabled = !val.Bool() // must not panic
|
|
} else {
|
|
// TODO: read values from struct into rm.XXX
|
|
}
|
|
}
|
|
if val, exists := v.Struct()["autoedge"]; exists && aem != nil {
|
|
aem.Disabled = !val.Bool() // must not panic
|
|
}
|
|
if val, exists := v.Struct()["autogroup"]; exists && agm != nil {
|
|
agm.Disabled = !val.Bool() // must not panic
|
|
}
|
|
|
|
default:
|
|
return fmt.Errorf("unknown property: %s", p)
|
|
}
|
|
}
|
|
|
|
res.SetMetaParams(meta) // set it!
|
|
if r, ok := res.(engine.ReversibleRes); ok {
|
|
r.SetReversibleMeta(rm) // set
|
|
}
|
|
if r, ok := res.(engine.EdgeableRes); ok {
|
|
r.SetAutoEdgeMeta(aem) // set
|
|
}
|
|
if r, ok := res.(engine.GroupableRes); ok {
|
|
r.SetAutoGroupMeta(agm) // set
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// StmtResContents is the interface that is met by the resource contents. Look
|
|
// closely for while it is similar to the Stmt interface, it is quite different.
|
|
type StmtResContents interface {
|
|
interfaces.Node
|
|
Init(*interfaces.Data) error
|
|
Interpolate() (StmtResContents, error) // different!
|
|
Copy() (StmtResContents, error)
|
|
Ordering(map[string]interfaces.Node) (*pgraph.Graph, map[interfaces.Node]string, error)
|
|
SetScope(*interfaces.Scope) error
|
|
Unify(kind string) ([]interfaces.Invariant, error) // different!
|
|
Graph() (*pgraph.Graph, error)
|
|
}
|
|
|
|
// StmtResField represents a single field in the parsed resource representation.
|
|
// This does not satisfy the Stmt interface.
|
|
type StmtResField struct {
|
|
Field string
|
|
Value interfaces.Expr
|
|
valuePtr interfaces.Func // ptr for table lookup
|
|
Condition interfaces.Expr // the value will be used if nil or true
|
|
conditionPtr interfaces.Func // ptr for table lookup
|
|
}
|
|
|
|
// String returns a short representation of this statement.
|
|
func (obj *StmtResField) String() string {
|
|
// TODO: add .String() for Condition and Value
|
|
return fmt.Sprintf("resfield(%s)", obj.Field)
|
|
}
|
|
|
|
// Apply is a general purpose iterator method that operates on any AST node. It
|
|
// is not used as the primary AST traversal function because it is less readable
|
|
// and easy to reason about than manually implementing traversal for each node.
|
|
// Nevertheless, it is a useful facility for operations that might only apply to
|
|
// a select number of node types, since they won't need extra noop iterators...
|
|
func (obj *StmtResField) Apply(fn func(interfaces.Node) error) error {
|
|
if obj.Condition != nil {
|
|
if err := obj.Condition.Apply(fn); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if err := obj.Value.Apply(fn); err != nil {
|
|
return err
|
|
}
|
|
return fn(obj)
|
|
}
|
|
|
|
// Init initializes this branch of the AST, and returns an error if it fails to
|
|
// validate.
|
|
func (obj *StmtResField) Init(data *interfaces.Data) error {
|
|
if obj.Condition != nil {
|
|
if err := obj.Condition.Init(data); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return obj.Value.Init(data)
|
|
}
|
|
|
|
// Interpolate returns a new node (aka a copy) once it has been expanded. This
|
|
// generally increases the size of the AST when it is used. It calls Interpolate
|
|
// on any child elements and builds the new node with those new node contents.
|
|
// This interpolate is different It is different from the interpolate found in
|
|
// the Expr and Stmt interfaces because it returns a different type as output.
|
|
func (obj *StmtResField) Interpolate() (StmtResContents, error) {
|
|
interpolated, err := obj.Value.Interpolate()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var condition interfaces.Expr
|
|
if obj.Condition != nil {
|
|
condition, err = obj.Condition.Interpolate()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
return &StmtResField{
|
|
Field: obj.Field,
|
|
Value: interpolated,
|
|
Condition: condition,
|
|
}, nil
|
|
}
|
|
|
|
// Copy returns a light copy of this struct. Anything static will not be copied.
|
|
func (obj *StmtResField) Copy() (StmtResContents, error) {
|
|
copied := false
|
|
value, err := obj.Value.Copy()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if value != obj.Value { // must have been copied, or pointer would be same
|
|
copied = true
|
|
}
|
|
|
|
var condition interfaces.Expr
|
|
if obj.Condition != nil {
|
|
condition, err = obj.Condition.Copy()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if condition != obj.Condition {
|
|
copied = true
|
|
}
|
|
|
|
}
|
|
|
|
if !copied { // it's static
|
|
return obj, nil
|
|
}
|
|
return &StmtResField{
|
|
Field: obj.Field,
|
|
Value: value,
|
|
Condition: condition,
|
|
}, nil
|
|
}
|
|
|
|
// Ordering returns a graph of the scope ordering that represents the data flow.
|
|
// This can be used in SetScope so that it knows the correct order to run it in.
|
|
func (obj *StmtResField) Ordering(produces map[string]interfaces.Node) (*pgraph.Graph, map[interfaces.Node]string, error) {
|
|
graph, err := pgraph.NewGraph("ordering")
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
graph.AddVertex(obj)
|
|
|
|
// additional constraint...
|
|
edge := &pgraph.SimpleEdge{Name: "stmtresfieldvalue"}
|
|
graph.AddEdge(obj.Value, obj, edge) // prod -> cons
|
|
|
|
cons := make(map[interfaces.Node]string)
|
|
nodes := []interfaces.Expr{obj.Value}
|
|
if obj.Condition != nil {
|
|
nodes = append(nodes, obj.Condition)
|
|
|
|
// additional constraint...
|
|
edge := &pgraph.SimpleEdge{Name: "stmtresfieldcondition"}
|
|
graph.AddEdge(obj.Condition, obj, edge) // prod -> cons
|
|
}
|
|
|
|
for _, node := range nodes {
|
|
g, c, err := node.Ordering(produces)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
graph.AddGraph(g) // add in the child graph
|
|
|
|
for k, v := range c { // c is consumes
|
|
x, exists := cons[k]
|
|
if exists && v != x {
|
|
return nil, nil, fmt.Errorf("consumed value is different, got `%+v`, expected `%+v`", x, v)
|
|
}
|
|
cons[k] = v // add to map
|
|
|
|
n, exists := produces[v]
|
|
if !exists {
|
|
continue
|
|
}
|
|
edge := &pgraph.SimpleEdge{Name: "stmtresfield"}
|
|
graph.AddEdge(n, k, edge)
|
|
}
|
|
}
|
|
|
|
return graph, cons, nil
|
|
}
|
|
|
|
// SetScope stores the scope for later use in this resource and its children,
|
|
// which it propagates this downwards to.
|
|
func (obj *StmtResField) SetScope(scope *interfaces.Scope) error {
|
|
if err := obj.Value.SetScope(scope, map[string]interfaces.Expr{}); err != nil {
|
|
return err
|
|
}
|
|
if obj.Condition != nil {
|
|
if err := obj.Condition.SetScope(scope, map[string]interfaces.Expr{}); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Unify returns the list of invariants that this node produces. It recursively
|
|
// calls Unify on any children elements that exist in the AST, and returns the
|
|
// collection to the caller. It is different from the Unify found in the Expr
|
|
// and Stmt interfaces because it adds an input parameter.
|
|
func (obj *StmtResField) Unify(kind string) ([]interfaces.Invariant, error) {
|
|
var invariants []interfaces.Invariant
|
|
|
|
invars, err := obj.Value.Unify()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
invariants = append(invariants, invars...)
|
|
|
|
// conditional expression might have some children invariants to share
|
|
if obj.Condition != nil {
|
|
condition, err := obj.Condition.Unify()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
invariants = append(invariants, condition...)
|
|
|
|
// the condition must ultimately be a boolean
|
|
conditionInvar := &interfaces.EqualsInvariant{
|
|
Expr: obj.Condition,
|
|
Type: types.TypeBool,
|
|
}
|
|
invariants = append(invariants, conditionInvar)
|
|
}
|
|
|
|
// TODO: unfortunately this gets called separately for each field... if
|
|
// we could cache this, it might be worth looking into for performance!
|
|
typMap, err := engineUtil.LangFieldNameToStructType(kind)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
field := strings.TrimSpace(obj.Field)
|
|
if len(field) != len(obj.Field) {
|
|
return nil, fmt.Errorf("field was wrapped in whitespace")
|
|
}
|
|
if len(strings.Fields(field)) != 1 {
|
|
return nil, fmt.Errorf("field was empty or contained spaces")
|
|
}
|
|
|
|
typ, exists := typMap[obj.Field]
|
|
if !exists {
|
|
return nil, fmt.Errorf("field `%s` does not exist in `%s`", obj.Field, kind)
|
|
}
|
|
if typ == nil {
|
|
// possible programming error
|
|
return nil, fmt.Errorf("type for field `%s` in `%s` is nil", obj.Field, kind)
|
|
}
|
|
if typ.Kind == types.KindVariant { // special path, res field has interface{}
|
|
if typ.Var == nil {
|
|
invar := &interfaces.AnyInvariant{
|
|
Expr: obj.Value,
|
|
}
|
|
invariants = append(invariants, invar)
|
|
return invariants, nil
|
|
}
|
|
|
|
// in case it is present (nil is okay too)
|
|
invar := &interfaces.EqualsInvariant{
|
|
Expr: obj.Value,
|
|
Type: typ.Var, // in case it is present (nil is okay too)
|
|
}
|
|
invariants = append(invariants, invar)
|
|
return invariants, nil
|
|
}
|
|
|
|
// regular scenario
|
|
invar := &interfaces.EqualsInvariant{
|
|
Expr: obj.Value,
|
|
Type: typ,
|
|
}
|
|
invariants = append(invariants, invar)
|
|
|
|
return invariants, nil
|
|
}
|
|
|
|
// Graph returns the reactive function graph which is expressed by this node. It
|
|
// includes any vertices produced by this node, and the appropriate edges to any
|
|
// vertices that are produced by its children. Nodes which fulfill the Expr
|
|
// interface directly produce vertices (and possible children) where as nodes
|
|
// that fulfill the Stmt interface do not produces vertices, where as their
|
|
// children might. It is interesting to note that nothing directly adds an edge
|
|
// to the resources created, but rather, once all the values (expressions) with
|
|
// no outgoing edges have produced at least a single value, then the resources
|
|
// know they're able to be built.
|
|
func (obj *StmtResField) Graph() (*pgraph.Graph, error) {
|
|
graph, err := pgraph.NewGraph("resfield")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
g, f, err := obj.Value.Graph(map[string]interfaces.Func{})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
graph.AddGraph(g)
|
|
obj.valuePtr = f
|
|
|
|
if obj.Condition != nil {
|
|
g, f, err := obj.Condition.Graph(map[string]interfaces.Func{})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
graph.AddGraph(g)
|
|
obj.conditionPtr = f
|
|
}
|
|
|
|
return graph, nil
|
|
}
|
|
|
|
// StmtResEdge represents a single edge property in the parsed resource
|
|
// representation. This does not satisfy the Stmt interface.
|
|
type StmtResEdge struct {
|
|
Property string // TODO: iota constant instead?
|
|
EdgeHalf *StmtEdgeHalf
|
|
Condition interfaces.Expr // the value will be used if nil or true
|
|
conditionPtr interfaces.Func // ptr for table lookup
|
|
}
|
|
|
|
// String returns a short representation of this statement.
|
|
func (obj *StmtResEdge) String() string {
|
|
// TODO: add .String() for Condition and EdgeHalf
|
|
return fmt.Sprintf("resedge(%s)", obj.Property)
|
|
}
|
|
|
|
// Apply is a general purpose iterator method that operates on any AST node. It
|
|
// is not used as the primary AST traversal function because it is less readable
|
|
// and easy to reason about than manually implementing traversal for each node.
|
|
// Nevertheless, it is a useful facility for operations that might only apply to
|
|
// a select number of node types, since they won't need extra noop iterators...
|
|
func (obj *StmtResEdge) Apply(fn func(interfaces.Node) error) error {
|
|
if obj.Condition != nil {
|
|
if err := obj.Condition.Apply(fn); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if err := obj.EdgeHalf.Apply(fn); err != nil {
|
|
return err
|
|
}
|
|
return fn(obj)
|
|
}
|
|
|
|
// Init initializes this branch of the AST, and returns an error if it fails to
|
|
// validate.
|
|
func (obj *StmtResEdge) Init(data *interfaces.Data) error {
|
|
if obj.Property == "" {
|
|
return fmt.Errorf("empty property")
|
|
}
|
|
if obj.Property != EdgeNotify && obj.Property != EdgeBefore && obj.Property != EdgeListen && obj.Property != EdgeDepend {
|
|
return fmt.Errorf("invalid property: `%s`", obj.Property)
|
|
}
|
|
|
|
if obj.Condition != nil {
|
|
if err := obj.Condition.Init(data); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return obj.EdgeHalf.Init(data)
|
|
}
|
|
|
|
// Interpolate returns a new node (aka a copy) once it has been expanded. This
|
|
// generally increases the size of the AST when it is used. It calls Interpolate
|
|
// on any child elements and builds the new node with those new node contents.
|
|
// This interpolate is different It is different from the interpolate found in
|
|
// the Expr and Stmt interfaces because it returns a different type as output.
|
|
func (obj *StmtResEdge) Interpolate() (StmtResContents, error) {
|
|
interpolated, err := obj.EdgeHalf.Interpolate()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var condition interfaces.Expr
|
|
if obj.Condition != nil {
|
|
condition, err = obj.Condition.Interpolate()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
return &StmtResEdge{
|
|
Property: obj.Property,
|
|
EdgeHalf: interpolated,
|
|
Condition: condition,
|
|
}, nil
|
|
}
|
|
|
|
// Copy returns a light copy of this struct. Anything static will not be copied.
|
|
func (obj *StmtResEdge) Copy() (StmtResContents, error) {
|
|
copied := false
|
|
edgeHalf, err := obj.EdgeHalf.Copy()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if edgeHalf != obj.EdgeHalf { // must have been copied, or pointer would be same
|
|
copied = true
|
|
}
|
|
|
|
var condition interfaces.Expr
|
|
if obj.Condition != nil {
|
|
condition, err = obj.Condition.Copy()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if condition != obj.Condition {
|
|
copied = true
|
|
}
|
|
}
|
|
|
|
if !copied { // it's static
|
|
return obj, nil
|
|
}
|
|
return &StmtResEdge{
|
|
Property: obj.Property,
|
|
EdgeHalf: edgeHalf,
|
|
Condition: condition,
|
|
}, nil
|
|
}
|
|
|
|
// Ordering returns a graph of the scope ordering that represents the data flow.
|
|
// This can be used in SetScope so that it knows the correct order to run it in.
|
|
func (obj *StmtResEdge) Ordering(produces map[string]interfaces.Node) (*pgraph.Graph, map[interfaces.Node]string, error) {
|
|
graph, err := pgraph.NewGraph("ordering")
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
graph.AddVertex(obj)
|
|
|
|
// additional constraint...
|
|
edge := &pgraph.SimpleEdge{Name: "stmtresedgehalf"}
|
|
// TODO: obj.EdgeHalf or obj.EdgeHalf.Name ?
|
|
graph.AddEdge(obj.EdgeHalf.Name, obj, edge) // prod -> cons
|
|
|
|
cons := make(map[interfaces.Node]string)
|
|
nodes := []interfaces.Expr{obj.EdgeHalf.Name}
|
|
if obj.Condition != nil {
|
|
nodes = append(nodes, obj.Condition)
|
|
|
|
// additional constraint...
|
|
edge := &pgraph.SimpleEdge{Name: "stmtresedgecondition"}
|
|
graph.AddEdge(obj.Condition, obj, edge) // prod -> cons
|
|
}
|
|
|
|
for _, node := range nodes {
|
|
g, c, err := node.Ordering(produces)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
graph.AddGraph(g) // add in the child graph
|
|
|
|
for k, v := range c { // c is consumes
|
|
x, exists := cons[k]
|
|
if exists && v != x {
|
|
return nil, nil, fmt.Errorf("consumed value is different, got `%+v`, expected `%+v`", x, v)
|
|
}
|
|
cons[k] = v // add to map
|
|
|
|
n, exists := produces[v]
|
|
if !exists {
|
|
continue
|
|
}
|
|
edge := &pgraph.SimpleEdge{Name: "stmtresedge"}
|
|
graph.AddEdge(n, k, edge)
|
|
}
|
|
}
|
|
|
|
return graph, cons, nil
|
|
}
|
|
|
|
// SetScope stores the scope for later use in this resource and its children,
|
|
// which it propagates this downwards to.
|
|
func (obj *StmtResEdge) SetScope(scope *interfaces.Scope) error {
|
|
if err := obj.EdgeHalf.SetScope(scope); err != nil {
|
|
return err
|
|
}
|
|
if obj.Condition != nil {
|
|
if err := obj.Condition.SetScope(scope, map[string]interfaces.Expr{}); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Unify returns the list of invariants that this node produces. It recursively
|
|
// calls Unify on any children elements that exist in the AST, and returns the
|
|
// collection to the caller. It is different from the Unify found in the Expr
|
|
// and Stmt interfaces because it adds an input parameter.
|
|
func (obj *StmtResEdge) Unify(kind string) ([]interfaces.Invariant, error) {
|
|
var invariants []interfaces.Invariant
|
|
|
|
invars, err := obj.EdgeHalf.Unify()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
invariants = append(invariants, invars...)
|
|
|
|
// conditional expression might have some children invariants to share
|
|
if obj.Condition != nil {
|
|
condition, err := obj.Condition.Unify()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
invariants = append(invariants, condition...)
|
|
|
|
// the condition must ultimately be a boolean
|
|
conditionInvar := &interfaces.EqualsInvariant{
|
|
Expr: obj.Condition,
|
|
Type: types.TypeBool,
|
|
}
|
|
invariants = append(invariants, conditionInvar)
|
|
}
|
|
|
|
return invariants, nil
|
|
}
|
|
|
|
// Graph returns the reactive function graph which is expressed by this node. It
|
|
// includes any vertices produced by this node, and the appropriate edges to any
|
|
// vertices that are produced by its children. Nodes which fulfill the Expr
|
|
// interface directly produce vertices (and possible children) where as nodes
|
|
// that fulfill the Stmt interface do not produces vertices, where as their
|
|
// children might. It is interesting to note that nothing directly adds an edge
|
|
// to the resources created, but rather, once all the values (expressions) with
|
|
// no outgoing edges have produced at least a single value, then the resources
|
|
// know they're able to be built.
|
|
func (obj *StmtResEdge) Graph() (*pgraph.Graph, error) {
|
|
graph, err := pgraph.NewGraph("resedge")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
g, err := obj.EdgeHalf.Graph()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
graph.AddGraph(g)
|
|
|
|
if obj.Condition != nil {
|
|
g, f, err := obj.Condition.Graph(map[string]interfaces.Func{})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
graph.AddGraph(g)
|
|
obj.conditionPtr = f
|
|
}
|
|
|
|
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
|
|
// the string found in the MetaField constant, otherwise this field will
|
|
// correspond to the particular meta parameter specified. This does not satisfy
|
|
// the Stmt interface.
|
|
type StmtResMeta struct {
|
|
Property string // TODO: iota constant instead?
|
|
MetaExpr interfaces.Expr
|
|
metaExprPtr interfaces.Func // ptr for table lookup
|
|
Condition interfaces.Expr // the value will be used if nil or true
|
|
conditionPtr interfaces.Func // ptr for table lookup
|
|
}
|
|
|
|
// String returns a short representation of this statement.
|
|
func (obj *StmtResMeta) String() string {
|
|
// TODO: add .String() for Condition and MetaExpr
|
|
return fmt.Sprintf("resmeta(%s)", obj.Property)
|
|
}
|
|
|
|
// Apply is a general purpose iterator method that operates on any AST node. It
|
|
// is not used as the primary AST traversal function because it is less readable
|
|
// and easy to reason about than manually implementing traversal for each node.
|
|
// Nevertheless, it is a useful facility for operations that might only apply to
|
|
// a select number of node types, since they won't need extra noop iterators...
|
|
func (obj *StmtResMeta) Apply(fn func(interfaces.Node) error) error {
|
|
if obj.Condition != nil {
|
|
if err := obj.Condition.Apply(fn); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if err := obj.MetaExpr.Apply(fn); err != nil {
|
|
return err
|
|
}
|
|
return fn(obj)
|
|
}
|
|
|
|
// Init initializes this branch of the AST, and returns an error if it fails to
|
|
// validate.
|
|
func (obj *StmtResMeta) Init(data *interfaces.Data) error {
|
|
if obj.Property == "" {
|
|
return fmt.Errorf("empty property")
|
|
}
|
|
|
|
switch p := strings.ToLower(obj.Property); p {
|
|
// TODO: we could add these fields dynamically if we were fancy!
|
|
case "noop":
|
|
case "retry":
|
|
case "retryreset":
|
|
case "delay":
|
|
case "poll":
|
|
case "limit":
|
|
case "burst":
|
|
case "reset":
|
|
case "sema":
|
|
case "rewatch":
|
|
case "realize":
|
|
case "reverse":
|
|
case "autoedge":
|
|
case "autogroup":
|
|
case MetaField:
|
|
|
|
default:
|
|
return fmt.Errorf("invalid property: `%s`", obj.Property)
|
|
}
|
|
|
|
if obj.Condition != nil {
|
|
if err := obj.Condition.Init(data); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return obj.MetaExpr.Init(data)
|
|
}
|
|
|
|
// Interpolate returns a new node (aka a copy) once it has been expanded. This
|
|
// generally increases the size of the AST when it is used. It calls Interpolate
|
|
// on any child elements and builds the new node with those new node contents.
|
|
// This interpolate is different It is different from the interpolate found in
|
|
// the Expr and Stmt interfaces because it returns a different type as output.
|
|
func (obj *StmtResMeta) Interpolate() (StmtResContents, error) {
|
|
interpolated, err := obj.MetaExpr.Interpolate()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var condition interfaces.Expr
|
|
if obj.Condition != nil {
|
|
condition, err = obj.Condition.Interpolate()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
return &StmtResMeta{
|
|
Property: obj.Property,
|
|
MetaExpr: interpolated,
|
|
Condition: condition,
|
|
}, nil
|
|
}
|
|
|
|
// Copy returns a light copy of this struct. Anything static will not be copied.
|
|
func (obj *StmtResMeta) Copy() (StmtResContents, error) {
|
|
copied := false
|
|
metaExpr, err := obj.MetaExpr.Copy()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if metaExpr != obj.MetaExpr { // must have been copied, or pointer would be same
|
|
copied = true
|
|
}
|
|
|
|
var condition interfaces.Expr
|
|
if obj.Condition != nil {
|
|
condition, err = obj.Condition.Copy()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if condition != obj.Condition {
|
|
copied = true
|
|
}
|
|
}
|
|
|
|
if !copied { // it's static
|
|
return obj, nil
|
|
}
|
|
return &StmtResMeta{
|
|
Property: obj.Property,
|
|
MetaExpr: metaExpr,
|
|
Condition: condition,
|
|
}, nil
|
|
}
|
|
|
|
// Ordering returns a graph of the scope ordering that represents the data flow.
|
|
// This can be used in SetScope so that it knows the correct order to run it in.
|
|
func (obj *StmtResMeta) Ordering(produces map[string]interfaces.Node) (*pgraph.Graph, map[interfaces.Node]string, error) {
|
|
graph, err := pgraph.NewGraph("ordering")
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
graph.AddVertex(obj)
|
|
|
|
// additional constraint...
|
|
edge := &pgraph.SimpleEdge{Name: "stmtresmetaexpr"}
|
|
graph.AddEdge(obj.MetaExpr, obj, edge) // prod -> cons
|
|
|
|
cons := make(map[interfaces.Node]string)
|
|
nodes := []interfaces.Expr{obj.MetaExpr}
|
|
if obj.Condition != nil {
|
|
nodes = append(nodes, obj.Condition)
|
|
|
|
// additional constraint...
|
|
edge := &pgraph.SimpleEdge{Name: "stmtresmetacondition"}
|
|
graph.AddEdge(obj.Condition, obj, edge) // prod -> cons
|
|
}
|
|
|
|
for _, node := range nodes {
|
|
g, c, err := node.Ordering(produces)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
graph.AddGraph(g) // add in the child graph
|
|
|
|
for k, v := range c { // c is consumes
|
|
x, exists := cons[k]
|
|
if exists && v != x {
|
|
return nil, nil, fmt.Errorf("consumed value is different, got `%+v`, expected `%+v`", x, v)
|
|
}
|
|
cons[k] = v // add to map
|
|
|
|
n, exists := produces[v]
|
|
if !exists {
|
|
continue
|
|
}
|
|
edge := &pgraph.SimpleEdge{Name: "stmtresmeta"}
|
|
graph.AddEdge(n, k, edge)
|
|
}
|
|
}
|
|
|
|
return graph, cons, nil
|
|
}
|
|
|
|
// SetScope stores the scope for later use in this resource and its children,
|
|
// which it propagates this downwards to.
|
|
func (obj *StmtResMeta) SetScope(scope *interfaces.Scope) error {
|
|
if err := obj.MetaExpr.SetScope(scope, map[string]interfaces.Expr{}); err != nil {
|
|
return err
|
|
}
|
|
if obj.Condition != nil {
|
|
if err := obj.Condition.SetScope(scope, map[string]interfaces.Expr{}); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Unify returns the list of invariants that this node produces. It recursively
|
|
// calls Unify on any children elements that exist in the AST, and returns the
|
|
// collection to the caller. It is different from the Unify found in the Expr
|
|
// and Stmt interfaces because it adds an input parameter.
|
|
// XXX: Allow specifying partial meta param structs and unify the subset type.
|
|
// XXX: The resource fields have the same limitation with field structs.
|
|
func (obj *StmtResMeta) Unify(kind string) ([]interfaces.Invariant, error) {
|
|
var invariants []interfaces.Invariant
|
|
|
|
invars, err := obj.MetaExpr.Unify()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
invariants = append(invariants, invars...)
|
|
|
|
// conditional expression might have some children invariants to share
|
|
if obj.Condition != nil {
|
|
condition, err := obj.Condition.Unify()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
invariants = append(invariants, condition...)
|
|
|
|
// the condition must ultimately be a boolean
|
|
conditionInvar := &interfaces.EqualsInvariant{
|
|
Expr: obj.Condition,
|
|
Type: types.TypeBool,
|
|
}
|
|
invariants = append(invariants, conditionInvar)
|
|
}
|
|
|
|
// add additional invariants based on what's in obj.Property !!!
|
|
var invar interfaces.Invariant
|
|
static := func(typ *types.Type) interfaces.Invariant {
|
|
return &interfaces.EqualsInvariant{
|
|
Expr: obj.MetaExpr,
|
|
Type: typ,
|
|
}
|
|
}
|
|
switch p := strings.ToLower(obj.Property); p {
|
|
// TODO: we could add these fields dynamically if we were fancy!
|
|
case "noop":
|
|
invar = static(types.TypeBool)
|
|
|
|
case "retry":
|
|
invar = static(types.TypeInt)
|
|
|
|
case "retryreset":
|
|
invar = static(types.TypeBool)
|
|
|
|
case "delay":
|
|
invar = static(types.TypeInt)
|
|
|
|
case "poll":
|
|
invar = static(types.TypeInt)
|
|
|
|
case "limit": // rate.Limit
|
|
invar = static(types.TypeFloat)
|
|
|
|
case "burst":
|
|
invar = static(types.TypeInt)
|
|
|
|
case "reset":
|
|
invar = static(types.TypeBool)
|
|
|
|
case "sema":
|
|
invar = static(types.NewType("[]str"))
|
|
|
|
case "rewatch":
|
|
invar = static(types.TypeBool)
|
|
|
|
case "realize":
|
|
invar = static(types.TypeBool)
|
|
|
|
case "reverse":
|
|
ors := []interfaces.Invariant{}
|
|
|
|
invarBool := static(types.TypeBool)
|
|
ors = append(ors, invarBool)
|
|
|
|
// TODO: decide what fields we might want here
|
|
//invarStruct := static(types.NewType("struct{edges str}"))
|
|
//ors = append(ors, invarStruct)
|
|
|
|
invar = &interfaces.ExclusiveInvariant{
|
|
Invariants: ors, // one and only one of these should be true
|
|
}
|
|
|
|
case "autoedge":
|
|
invar = static(types.TypeBool)
|
|
|
|
case "autogroup":
|
|
invar = static(types.TypeBool)
|
|
|
|
// autoedge and autogroup aren't part of the `MetaRes` interface, but we
|
|
// can merge them in here for simplicity in the public user interface...
|
|
case MetaField:
|
|
// FIXME: allow partial subsets of this struct, and in any order
|
|
// FIXME: we might need an updated unification engine to do this
|
|
wrap := func(reverse *types.Type) *types.Type {
|
|
return types.NewType(fmt.Sprintf("struct{noop bool; retry int; retryreset bool; delay int; poll int; limit float; burst int; reset bool; sema []str; rewatch bool; realize bool; reverse %s; autoedge bool; autogroup bool}", reverse.String()))
|
|
}
|
|
ors := []interfaces.Invariant{}
|
|
invarBool := static(wrap(types.TypeBool))
|
|
ors = append(ors, invarBool)
|
|
// TODO: decide what fields we might want here
|
|
//invarStruct := static(wrap(types.NewType("struct{edges str}")))
|
|
//ors = append(ors, invarStruct)
|
|
invar = &interfaces.ExclusiveInvariant{
|
|
Invariants: ors, // one and only one of these should be true
|
|
}
|
|
|
|
default:
|
|
return nil, fmt.Errorf("unknown property: %s", p)
|
|
}
|
|
invariants = append(invariants, invar)
|
|
|
|
return invariants, nil
|
|
}
|
|
|
|
// Graph returns the reactive function graph which is expressed by this node. It
|
|
// includes any vertices produced by this node, and the appropriate edges to any
|
|
// vertices that are produced by its children. Nodes which fulfill the Expr
|
|
// interface directly produce vertices (and possible children) where as nodes
|
|
// that fulfill the Stmt interface do not produces vertices, where as their
|
|
// children might. It is interesting to note that nothing directly adds an edge
|
|
// to the resources created, but rather, once all the values (expressions) with
|
|
// no outgoing edges have produced at least a single value, then the resources
|
|
// know they're able to be built.
|
|
func (obj *StmtResMeta) Graph() (*pgraph.Graph, error) {
|
|
graph, err := pgraph.NewGraph("resmeta")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
g, f, err := obj.MetaExpr.Graph(map[string]interfaces.Func{})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
graph.AddGraph(g)
|
|
obj.metaExprPtr = f
|
|
|
|
if obj.Condition != nil {
|
|
g, f, err := obj.Condition.Graph(map[string]interfaces.Func{})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
graph.AddGraph(g)
|
|
obj.conditionPtr = f
|
|
|
|
}
|
|
|
|
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
|
|
// in the list. If there are multiple StmtEdgeHalf structs listed, then they
|
|
// should represent a chain, eg: a->b->c, should compile into a->b & b->c. If
|
|
// specified, values are sent and received along these edges if the Send/Recv
|
|
// names are compatible and listed. In this case of Send/Recv, only lists of
|
|
// length two are legal.
|
|
type StmtEdge struct {
|
|
EdgeHalfList []*StmtEdgeHalf // represents a chain of edges
|
|
|
|
// TODO: should notify be an Expr?
|
|
Notify bool // specifies that this edge sends a notification as well
|
|
}
|
|
|
|
// String returns a short representation of this statement.
|
|
func (obj *StmtEdge) String() string {
|
|
return "edge" // TODO: improve this
|
|
}
|
|
|
|
// Apply is a general purpose iterator method that operates on any AST node. It
|
|
// is not used as the primary AST traversal function because it is less readable
|
|
// and easy to reason about than manually implementing traversal for each node.
|
|
// Nevertheless, it is a useful facility for operations that might only apply to
|
|
// a select number of node types, since they won't need extra noop iterators...
|
|
func (obj *StmtEdge) Apply(fn func(interfaces.Node) error) error {
|
|
for _, x := range obj.EdgeHalfList {
|
|
if err := x.Apply(fn); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return fn(obj)
|
|
}
|
|
|
|
// Init initializes this branch of the AST, and returns an error if it fails to
|
|
// validate.
|
|
func (obj *StmtEdge) Init(data *interfaces.Data) error {
|
|
for _, x := range obj.EdgeHalfList {
|
|
if err := x.Init(data); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Interpolate returns a new node (aka a copy) once it has been expanded. This
|
|
// generally increases the size of the AST when it is used. It calls Interpolate
|
|
// on any child elements and builds the new node with those new node contents.
|
|
// TODO: could we expand the Name's from the EdgeHalf (if they're lists) to have
|
|
// them return a list of Edges's ?
|
|
// XXX: type check the kind1:send -> kind2:recv fields are compatible!
|
|
// XXX: we won't know the names yet, but it's okay.
|
|
func (obj *StmtEdge) Interpolate() (interfaces.Stmt, error) {
|
|
edgeHalfList := []*StmtEdgeHalf{}
|
|
for _, x := range obj.EdgeHalfList {
|
|
edgeHalf, err := x.Interpolate()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
edgeHalfList = append(edgeHalfList, edgeHalf)
|
|
}
|
|
|
|
return &StmtEdge{
|
|
EdgeHalfList: edgeHalfList,
|
|
Notify: obj.Notify,
|
|
}, nil
|
|
}
|
|
|
|
// Copy returns a light copy of this struct. Anything static will not be copied.
|
|
func (obj *StmtEdge) Copy() (interfaces.Stmt, error) {
|
|
copied := false
|
|
edgeHalfList := []*StmtEdgeHalf{}
|
|
for _, x := range obj.EdgeHalfList {
|
|
edgeHalf, err := x.Copy()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if edgeHalf != x { // must have been copied, or pointer would be same
|
|
copied = true
|
|
}
|
|
edgeHalfList = append(edgeHalfList, edgeHalf)
|
|
}
|
|
|
|
if !copied { // it's static
|
|
return obj, nil
|
|
}
|
|
return &StmtEdge{
|
|
EdgeHalfList: edgeHalfList,
|
|
Notify: obj.Notify,
|
|
}, nil
|
|
}
|
|
|
|
// Ordering returns a graph of the scope ordering that represents the data flow.
|
|
// This can be used in SetScope so that it knows the correct order to run it in.
|
|
func (obj *StmtEdge) Ordering(produces map[string]interfaces.Node) (*pgraph.Graph, map[interfaces.Node]string, error) {
|
|
graph, err := pgraph.NewGraph("ordering")
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
graph.AddVertex(obj)
|
|
|
|
cons := make(map[interfaces.Node]string)
|
|
|
|
for _, edgeHalf := range obj.EdgeHalfList {
|
|
node := edgeHalf.Name
|
|
g, c, err := node.Ordering(produces)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
graph.AddGraph(g) // add in the child graph
|
|
|
|
// additional constraint...
|
|
edge := &pgraph.SimpleEdge{Name: "stmtedgehalf"}
|
|
graph.AddEdge(node, obj, edge) // prod -> cons
|
|
|
|
for k, v := range c { // c is consumes
|
|
x, exists := cons[k]
|
|
if exists && v != x {
|
|
return nil, nil, fmt.Errorf("consumed value is different, got `%+v`, expected `%+v`", x, v)
|
|
}
|
|
cons[k] = v // add to map
|
|
|
|
n, exists := produces[v]
|
|
if !exists {
|
|
continue
|
|
}
|
|
edge := &pgraph.SimpleEdge{Name: "stmtedge"}
|
|
graph.AddEdge(n, k, edge)
|
|
}
|
|
}
|
|
|
|
return graph, cons, nil
|
|
}
|
|
|
|
// SetScope stores the scope for later use in this resource and its children,
|
|
// which it propagates this downwards to.
|
|
func (obj *StmtEdge) SetScope(scope *interfaces.Scope) error {
|
|
for _, x := range obj.EdgeHalfList {
|
|
if err := x.SetScope(scope); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Unify returns the list of invariants that this node produces. It recursively
|
|
// calls Unify on any children elements that exist in the AST, and returns the
|
|
// collection to the caller.
|
|
func (obj *StmtEdge) Unify() ([]interfaces.Invariant, error) {
|
|
var invariants []interfaces.Invariant
|
|
|
|
// TODO: this sort of sideloaded validation could happen in a dedicated
|
|
// Validate() function, but for now is here for lack of a better place!
|
|
if len(obj.EdgeHalfList) == 1 {
|
|
return nil, fmt.Errorf("can't create an edge with only one half")
|
|
}
|
|
if len(obj.EdgeHalfList) == 2 {
|
|
sr1 := obj.EdgeHalfList[0].SendRecv
|
|
sr2 := obj.EdgeHalfList[1].SendRecv
|
|
if (sr1 == "") != (sr2 == "") { // xor
|
|
return nil, fmt.Errorf("you must specify both send/recv fields or neither")
|
|
}
|
|
|
|
if sr1 != "" && sr2 != "" {
|
|
k1 := obj.EdgeHalfList[0].Kind
|
|
k2 := obj.EdgeHalfList[1].Kind
|
|
|
|
r1, err := engine.NewResource(k1)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
r2, err := engine.NewResource(k2)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
res1, ok := r1.(engine.SendableRes)
|
|
if !ok {
|
|
return nil, fmt.Errorf("cannot send from resource of kind: %s", k1)
|
|
}
|
|
res2, ok := r2.(engine.RecvableRes)
|
|
if !ok {
|
|
return nil, fmt.Errorf("cannot recv to resource of kind: %s", k2)
|
|
}
|
|
|
|
// Check that the kind1:send -> kind2:recv fields are type
|
|
// compatible! We won't know the names yet, but it's okay.
|
|
if err := engineUtil.StructFieldCompat(res1.Sends(), sr1, res2, sr2); err != nil {
|
|
p1 := k1 // print defaults
|
|
p2 := k2
|
|
if v, err := obj.EdgeHalfList[0].Name.Value(); err == nil { // statically known
|
|
// display something nicer
|
|
if v.Type().Kind == types.KindStr {
|
|
p1 = engine.Repr(k1, v.Str())
|
|
} else if v.Type().Cmp(types.NewType("[]str")) == nil {
|
|
p1 = engine.Repr(k1, v.String())
|
|
}
|
|
}
|
|
if v, err := obj.EdgeHalfList[1].Name.Value(); err == nil {
|
|
if v.Type().Kind == types.KindStr {
|
|
p2 = engine.Repr(k2, v.Str())
|
|
} else if v.Type().Cmp(types.NewType("[]str")) == nil {
|
|
p2 = engine.Repr(k2, v.String())
|
|
}
|
|
}
|
|
return nil, errwrap.Wrapf(err, "cannot send/recv from %s.%s to %s.%s", p1, sr1, p2, sr2)
|
|
}
|
|
}
|
|
}
|
|
|
|
for _, x := range obj.EdgeHalfList {
|
|
if x.SendRecv != "" && len(obj.EdgeHalfList) != 2 {
|
|
return nil, fmt.Errorf("send/recv edges must come in pairs")
|
|
}
|
|
|
|
invars, err := x.Unify()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
invariants = append(invariants, invars...)
|
|
}
|
|
|
|
return invariants, nil
|
|
}
|
|
|
|
// Graph returns the reactive function graph which is expressed by this node. It
|
|
// includes any vertices produced by this node, and the appropriate edges to any
|
|
// vertices that are produced by its children. Nodes which fulfill the Expr
|
|
// interface directly produce vertices (and possible children) where as nodes
|
|
// that fulfill the Stmt interface do not produces vertices, where as their
|
|
// children might. It is interesting to note that nothing directly adds an edge
|
|
// to the edges created, but rather, once all the values (expressions) with no
|
|
// outgoing function graph edges have produced at least a single value, then the
|
|
// edges know they're able to be built.
|
|
func (obj *StmtEdge) Graph() (*pgraph.Graph, error) {
|
|
graph, err := pgraph.NewGraph("edge")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for _, x := range obj.EdgeHalfList {
|
|
g, err := x.Graph()
|
|
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
|
|
// called by this Output function if they are needed to produce the output. In
|
|
// the case of this edge statement, this is definitely the case. This edge stmt
|
|
// returns output consisting of edges.
|
|
func (obj *StmtEdge) Output(table map[interfaces.Func]types.Value) (*interfaces.Output, error) {
|
|
edges := []*interfaces.Edge{}
|
|
|
|
// EdgeHalfList goes in a chain, so we increment like i++ and not i+=2.
|
|
for i := 0; i < len(obj.EdgeHalfList)-1; i++ {
|
|
if obj.EdgeHalfList[i].namePtr == nil {
|
|
return nil, ErrFuncPointerNil
|
|
}
|
|
nameValue1, exists := table[obj.EdgeHalfList[i].namePtr]
|
|
if !exists {
|
|
return nil, ErrTableNoValue
|
|
}
|
|
|
|
// the edge name can be a single string or a list of strings...
|
|
|
|
names1 := []string{} // list of names to build
|
|
switch {
|
|
case types.TypeStr.Cmp(nameValue1.Type()) == nil:
|
|
name := nameValue1.Str() // must not panic
|
|
names1 = append(names1, name)
|
|
|
|
case types.NewType("[]str").Cmp(nameValue1.Type()) == nil:
|
|
for _, x := range nameValue1.List() { // must not panic
|
|
name := x.Str() // must not panic
|
|
names1 = append(names1, name)
|
|
}
|
|
|
|
default:
|
|
// programming error
|
|
return nil, fmt.Errorf("unhandled resource name type: %+v", nameValue1.Type())
|
|
}
|
|
|
|
if obj.EdgeHalfList[i+1].namePtr == nil {
|
|
return nil, ErrFuncPointerNil
|
|
}
|
|
nameValue2, exists := table[obj.EdgeHalfList[i+1].namePtr]
|
|
if !exists {
|
|
return nil, ErrTableNoValue
|
|
}
|
|
|
|
names2 := []string{} // list of names to build
|
|
switch {
|
|
case types.TypeStr.Cmp(nameValue2.Type()) == nil:
|
|
name := nameValue2.Str() // must not panic
|
|
names2 = append(names2, name)
|
|
|
|
case types.NewType("[]str").Cmp(nameValue2.Type()) == nil:
|
|
for _, x := range nameValue2.List() { // must not panic
|
|
name := x.Str() // must not panic
|
|
names2 = append(names2, name)
|
|
}
|
|
|
|
default:
|
|
// programming error
|
|
return nil, fmt.Errorf("unhandled resource name type: %+v", nameValue2.Type())
|
|
}
|
|
|
|
for _, name1 := range names1 {
|
|
for _, name2 := range names2 {
|
|
edge := &interfaces.Edge{
|
|
Kind1: obj.EdgeHalfList[i].Kind,
|
|
Name1: name1,
|
|
Send: obj.EdgeHalfList[i].SendRecv,
|
|
|
|
Kind2: obj.EdgeHalfList[i+1].Kind,
|
|
Name2: name2,
|
|
Recv: obj.EdgeHalfList[i+1].SendRecv,
|
|
|
|
Notify: obj.Notify,
|
|
}
|
|
edges = append(edges, edge)
|
|
}
|
|
}
|
|
}
|
|
|
|
return &interfaces.Output{
|
|
Edges: edges,
|
|
}, nil
|
|
}
|
|
|
|
// StmtEdgeHalf represents half of an edge in the parsed edge representation.
|
|
// This does not satisfy the Stmt interface.
|
|
type StmtEdgeHalf struct {
|
|
Kind string // kind of resource, eg: pkg, file, svc, etc...
|
|
Name interfaces.Expr // unique name for the res of this kind
|
|
namePtr interfaces.Func // ptr for table lookup
|
|
SendRecv string // name of field to send/recv from/to, empty to ignore
|
|
}
|
|
|
|
// String returns a short representation of this statement.
|
|
func (obj *StmtEdgeHalf) String() string {
|
|
// TODO: add .String() for Name
|
|
return fmt.Sprintf("edgehalf(%s)", obj.Kind)
|
|
}
|
|
|
|
// Apply is a general purpose iterator method that operates on any AST node. It
|
|
// is not used as the primary AST traversal function because it is less readable
|
|
// and easy to reason about than manually implementing traversal for each node.
|
|
// Nevertheless, it is a useful facility for operations that might only apply to
|
|
// a select number of node types, since they won't need extra noop iterators...
|
|
func (obj *StmtEdgeHalf) Apply(fn func(interfaces.Node) error) error {
|
|
if err := obj.Name.Apply(fn); err != nil {
|
|
return err
|
|
}
|
|
return fn(obj)
|
|
}
|
|
|
|
// Init initializes this branch of the AST, and returns an error if it fails to
|
|
// validate.
|
|
func (obj *StmtEdgeHalf) Init(data *interfaces.Data) error {
|
|
if strings.Contains(obj.Kind, "_") {
|
|
return fmt.Errorf("kind must not contain underscores")
|
|
}
|
|
|
|
return obj.Name.Init(data)
|
|
}
|
|
|
|
// Interpolate returns a new node (aka a copy) once it has been expanded. This
|
|
// generally increases the size of the AST when it is used. It calls Interpolate
|
|
// on any child elements and builds the new node with those new node contents.
|
|
// This interpolate is different It is different from the interpolate found in
|
|
// the Expr and Stmt interfaces because it returns a different type as output.
|
|
func (obj *StmtEdgeHalf) Interpolate() (*StmtEdgeHalf, error) {
|
|
name, err := obj.Name.Interpolate()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &StmtEdgeHalf{
|
|
Kind: obj.Kind,
|
|
Name: name,
|
|
SendRecv: obj.SendRecv,
|
|
}, nil
|
|
}
|
|
|
|
// Copy returns a light copy of this struct. Anything static will not be copied.
|
|
func (obj *StmtEdgeHalf) Copy() (*StmtEdgeHalf, error) {
|
|
copied := false
|
|
name, err := obj.Name.Copy()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if name != obj.Name { // must have been copied, or pointer would be same
|
|
copied = true
|
|
}
|
|
|
|
if !copied { // it's static
|
|
return obj, nil
|
|
}
|
|
return &StmtEdgeHalf{
|
|
Kind: obj.Kind,
|
|
Name: name,
|
|
SendRecv: obj.SendRecv,
|
|
}, nil
|
|
}
|
|
|
|
// SetScope stores the scope for later use in this resource and its children,
|
|
// which it propagates this downwards to.
|
|
func (obj *StmtEdgeHalf) SetScope(scope *interfaces.Scope) error {
|
|
return obj.Name.SetScope(scope, map[string]interfaces.Expr{})
|
|
}
|
|
|
|
// Unify returns the list of invariants that this node produces. It recursively
|
|
// calls Unify on any children elements that exist in the AST, and returns the
|
|
// collection to the caller.
|
|
func (obj *StmtEdgeHalf) Unify() ([]interfaces.Invariant, error) {
|
|
var invariants []interfaces.Invariant
|
|
|
|
if obj.Kind == "" {
|
|
return nil, fmt.Errorf("missing resource kind in edge")
|
|
}
|
|
|
|
if obj.SendRecv != "" {
|
|
// FIXME: write this function (get expected type of field)
|
|
//invar, err := StructFieldInvariant(obj.Kind, obj.SendRecv)
|
|
//if err != nil {
|
|
// return nil, err
|
|
//}
|
|
//invariants = append(invariants, invar...)
|
|
}
|
|
|
|
invars, err := obj.Name.Unify()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
invariants = append(invariants, invars...)
|
|
|
|
// name must be a string or a list
|
|
ors := []interfaces.Invariant{}
|
|
|
|
invarStr := &interfaces.EqualsInvariant{
|
|
Expr: obj.Name,
|
|
Type: types.TypeStr,
|
|
}
|
|
ors = append(ors, invarStr)
|
|
|
|
invarListStr := &interfaces.EqualsInvariant{
|
|
Expr: obj.Name,
|
|
Type: types.NewType("[]str"),
|
|
}
|
|
ors = append(ors, invarListStr)
|
|
|
|
invar := &interfaces.ExclusiveInvariant{
|
|
Invariants: ors, // one and only one of these should be true
|
|
}
|
|
invariants = append(invariants, invar)
|
|
|
|
return invariants, nil
|
|
}
|
|
|
|
// Graph returns the reactive function graph which is expressed by this node. It
|
|
// includes any vertices produced by this node, and the appropriate edges to any
|
|
// vertices that are produced by its children. Nodes which fulfill the Expr
|
|
// interface directly produce vertices (and possible children) where as nodes
|
|
// that fulfill the Stmt interface do not produces vertices, where as their
|
|
// children might. It is interesting to note that nothing directly adds an edge
|
|
// to the resources created, but rather, once all the values (expressions) with
|
|
// no outgoing edges have produced at least a single value, then the resources
|
|
// know they're able to be built.
|
|
func (obj *StmtEdgeHalf) Graph() (*pgraph.Graph, error) {
|
|
g, f, err := obj.Name.Graph(map[string]interfaces.Func{})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
obj.namePtr = f
|
|
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
|
|
// value, where as this produces some Output. Normally if one of the branches is
|
|
// optional, it is the else branch, although this struct allows either to be
|
|
// optional, even if it is not commonly used.
|
|
type StmtIf struct {
|
|
Condition interfaces.Expr
|
|
conditionPtr interfaces.Func // ptr for table lookup
|
|
ThenBranch interfaces.Stmt // optional, but usually present
|
|
ElseBranch interfaces.Stmt // optional
|
|
}
|
|
|
|
// String returns a short representation of this statement.
|
|
func (obj *StmtIf) String() string {
|
|
s := fmt.Sprintf("if( %s )", obj.Condition.String())
|
|
|
|
if obj.ThenBranch != nil {
|
|
s += fmt.Sprintf(" { %s }", obj.ThenBranch.String())
|
|
} else {
|
|
s += " { }"
|
|
}
|
|
if obj.ElseBranch != nil {
|
|
s += fmt.Sprintf(" else { %s }", obj.ElseBranch.String())
|
|
}
|
|
|
|
return s
|
|
}
|
|
|
|
// Apply is a general purpose iterator method that operates on any AST node. It
|
|
// is not used as the primary AST traversal function because it is less readable
|
|
// and easy to reason about than manually implementing traversal for each node.
|
|
// Nevertheless, it is a useful facility for operations that might only apply to
|
|
// a select number of node types, since they won't need extra noop iterators...
|
|
func (obj *StmtIf) Apply(fn func(interfaces.Node) error) error {
|
|
if err := obj.Condition.Apply(fn); err != nil {
|
|
return err
|
|
}
|
|
if obj.ThenBranch != nil {
|
|
if err := obj.ThenBranch.Apply(fn); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if obj.ElseBranch != nil {
|
|
if err := obj.ElseBranch.Apply(fn); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return fn(obj)
|
|
}
|
|
|
|
// Init initializes this branch of the AST, and returns an error if it fails to
|
|
// validate.
|
|
func (obj *StmtIf) Init(data *interfaces.Data) error {
|
|
if err := obj.Condition.Init(data); err != nil {
|
|
return err
|
|
}
|
|
if obj.ThenBranch != nil {
|
|
if err := obj.ThenBranch.Init(data); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if obj.ElseBranch != nil {
|
|
if err := obj.ElseBranch.Init(data); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Interpolate returns a new node (aka a copy) once it has been expanded. This
|
|
// generally increases the size of the AST when it is used. It calls Interpolate
|
|
// on any child elements and builds the new node with those new node contents.
|
|
func (obj *StmtIf) Interpolate() (interfaces.Stmt, error) {
|
|
condition, err := obj.Condition.Interpolate()
|
|
if err != nil {
|
|
return nil, errwrap.Wrapf(err, "could not interpolate Condition")
|
|
}
|
|
var thenBranch interfaces.Stmt
|
|
if obj.ThenBranch != nil {
|
|
thenBranch, err = obj.ThenBranch.Interpolate()
|
|
if err != nil {
|
|
return nil, errwrap.Wrapf(err, "could not interpolate ThenBranch")
|
|
}
|
|
}
|
|
var elseBranch interfaces.Stmt
|
|
if obj.ElseBranch != nil {
|
|
elseBranch, err = obj.ElseBranch.Interpolate()
|
|
if err != nil {
|
|
return nil, errwrap.Wrapf(err, "could not interpolate ElseBranch")
|
|
}
|
|
}
|
|
return &StmtIf{
|
|
Condition: condition,
|
|
ThenBranch: thenBranch,
|
|
ElseBranch: elseBranch,
|
|
}, nil
|
|
}
|
|
|
|
// Copy returns a light copy of this struct. Anything static will not be copied.
|
|
func (obj *StmtIf) Copy() (interfaces.Stmt, error) {
|
|
copied := false
|
|
condition, err := obj.Condition.Copy()
|
|
if err != nil {
|
|
return nil, errwrap.Wrapf(err, "could not copy Condition")
|
|
}
|
|
if condition != obj.Condition { // must have been copied, or pointer would be same
|
|
copied = true
|
|
}
|
|
|
|
var thenBranch interfaces.Stmt
|
|
if obj.ThenBranch != nil {
|
|
thenBranch, err = obj.ThenBranch.Copy()
|
|
if err != nil {
|
|
return nil, errwrap.Wrapf(err, "could not copy ThenBranch")
|
|
}
|
|
if thenBranch != obj.ThenBranch {
|
|
copied = true
|
|
}
|
|
}
|
|
var elseBranch interfaces.Stmt
|
|
if obj.ElseBranch != nil {
|
|
elseBranch, err = obj.ElseBranch.Copy()
|
|
if err != nil {
|
|
return nil, errwrap.Wrapf(err, "could not copy ElseBranch")
|
|
}
|
|
if elseBranch != obj.ElseBranch {
|
|
copied = true
|
|
}
|
|
}
|
|
|
|
if !copied { // it's static
|
|
return obj, nil
|
|
}
|
|
return &StmtIf{
|
|
Condition: condition,
|
|
ThenBranch: thenBranch,
|
|
ElseBranch: elseBranch,
|
|
}, nil
|
|
}
|
|
|
|
// Ordering returns a graph of the scope ordering that represents the data flow.
|
|
// This can be used in SetScope so that it knows the correct order to run it in.
|
|
func (obj *StmtIf) Ordering(produces map[string]interfaces.Node) (*pgraph.Graph, map[interfaces.Node]string, error) {
|
|
graph, err := pgraph.NewGraph("ordering")
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
graph.AddVertex(obj)
|
|
|
|
// Additional constraints: We know the condition has to be satisfied
|
|
// before this if statement itself can be used, since we depend on that
|
|
// value.
|
|
edge := &pgraph.SimpleEdge{Name: "stmtif"}
|
|
graph.AddEdge(obj.Condition, obj, edge) // prod -> cons
|
|
|
|
cons := make(map[interfaces.Node]string)
|
|
|
|
g, c, err := obj.Condition.Ordering(produces)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
graph.AddGraph(g) // add in the child graph
|
|
|
|
for k, v := range c { // c is consumes
|
|
x, exists := cons[k]
|
|
if exists && v != x {
|
|
return nil, nil, fmt.Errorf("consumed value is different, got `%+v`, expected `%+v`", x, v)
|
|
}
|
|
cons[k] = v // add to map
|
|
|
|
n, exists := produces[v]
|
|
if !exists {
|
|
continue
|
|
}
|
|
edge := &pgraph.SimpleEdge{Name: "stmtifcondition"}
|
|
graph.AddEdge(n, k, edge)
|
|
}
|
|
|
|
nodes := []interfaces.Stmt{}
|
|
if obj.ThenBranch != nil {
|
|
nodes = append(nodes, obj.ThenBranch)
|
|
|
|
// additional constraints...
|
|
edge1 := &pgraph.SimpleEdge{Name: "stmtifthencondition"}
|
|
graph.AddEdge(obj.Condition, obj.ThenBranch, edge1) // prod -> cons
|
|
edge2 := &pgraph.SimpleEdge{Name: "stmtifthen"}
|
|
graph.AddEdge(obj.ThenBranch, obj, edge2) // prod -> cons
|
|
}
|
|
if obj.ElseBranch != nil {
|
|
nodes = append(nodes, obj.ElseBranch)
|
|
|
|
// additional constraints...
|
|
edge1 := &pgraph.SimpleEdge{Name: "stmtifelsecondition"}
|
|
graph.AddEdge(obj.Condition, obj.ElseBranch, edge1) // prod -> cons
|
|
edge2 := &pgraph.SimpleEdge{Name: "stmtifelse"}
|
|
graph.AddEdge(obj.ElseBranch, obj, edge2) // prod -> cons
|
|
}
|
|
|
|
for _, node := range nodes { // "dry"
|
|
g, c, err := node.Ordering(produces)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
graph.AddGraph(g) // add in the child graph
|
|
|
|
for k, v := range c { // c is consumes
|
|
x, exists := cons[k]
|
|
if exists && v != x {
|
|
return nil, nil, fmt.Errorf("consumed value is different, got `%+v`, expected `%+v`", x, v)
|
|
}
|
|
cons[k] = v // add to map
|
|
|
|
n, exists := produces[v]
|
|
if !exists {
|
|
continue
|
|
}
|
|
edge := &pgraph.SimpleEdge{Name: "stmtifbranch"}
|
|
graph.AddEdge(n, k, edge)
|
|
}
|
|
}
|
|
|
|
return graph, cons, nil
|
|
}
|
|
|
|
// SetScope stores the scope for later use in this resource and its children,
|
|
// which it propagates this downwards to.
|
|
func (obj *StmtIf) SetScope(scope *interfaces.Scope) error {
|
|
if err := obj.Condition.SetScope(scope, map[string]interfaces.Expr{}); err != nil {
|
|
return err
|
|
}
|
|
if obj.ThenBranch != nil {
|
|
if err := obj.ThenBranch.SetScope(scope); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if obj.ElseBranch != nil {
|
|
if err := obj.ElseBranch.SetScope(scope); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Unify returns the list of invariants that this node produces. It recursively
|
|
// calls Unify on any children elements that exist in the AST, and returns the
|
|
// collection to the caller.
|
|
func (obj *StmtIf) Unify() ([]interfaces.Invariant, error) {
|
|
var invariants []interfaces.Invariant
|
|
|
|
// conditional expression might have some children invariants to share
|
|
condition, err := obj.Condition.Unify()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
invariants = append(invariants, condition...)
|
|
|
|
// the condition must ultimately be a boolean
|
|
conditionInvar := &interfaces.EqualsInvariant{
|
|
Expr: obj.Condition,
|
|
Type: types.TypeBool,
|
|
}
|
|
invariants = append(invariants, conditionInvar)
|
|
|
|
// recurse into the two branches
|
|
if obj.ThenBranch != nil {
|
|
thenBranch, err := obj.ThenBranch.Unify()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
invariants = append(invariants, thenBranch...)
|
|
}
|
|
|
|
if obj.ElseBranch != nil {
|
|
elseBranch, err := obj.ElseBranch.Unify()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
invariants = append(invariants, elseBranch...)
|
|
}
|
|
|
|
return invariants, nil
|
|
}
|
|
|
|
// Graph returns the reactive function graph which is expressed by this node. It
|
|
// includes any vertices produced by this node, and the appropriate edges to any
|
|
// vertices that are produced by its children. Nodes which fulfill the Expr
|
|
// interface directly produce vertices (and possible children) where as nodes
|
|
// that fulfill the Stmt interface do not produces vertices, where as their
|
|
// children might. This particular if statement doesn't do anything clever here
|
|
// other than adding in both branches of the graph. Since we're functional, this
|
|
// shouldn't have any ill effects.
|
|
// XXX: is this completely true if we're running technically impure, but safe
|
|
// built-in functions on both branches? Can we turn off half of this?
|
|
func (obj *StmtIf) Graph() (*pgraph.Graph, error) {
|
|
graph, err := pgraph.NewGraph("if")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
g, f, err := obj.Condition.Graph(map[string]interfaces.Func{})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
graph.AddGraph(g)
|
|
obj.conditionPtr = f
|
|
|
|
for _, x := range []interfaces.Stmt{obj.ThenBranch, obj.ElseBranch} {
|
|
if x == nil {
|
|
continue
|
|
}
|
|
g, err := x.Graph()
|
|
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
|
|
// called by this Output function if they are needed to produce the output.
|
|
func (obj *StmtIf) Output(table map[interfaces.Func]types.Value) (*interfaces.Output, error) {
|
|
if obj.conditionPtr == nil {
|
|
return nil, ErrFuncPointerNil
|
|
}
|
|
b, exists := table[obj.conditionPtr]
|
|
if !exists {
|
|
return nil, ErrTableNoValue
|
|
}
|
|
|
|
var output *interfaces.Output
|
|
var err error
|
|
if b.Bool() { // must not panic!
|
|
if obj.ThenBranch != nil { // logically then branch is optional
|
|
output, err = obj.ThenBranch.Output(table)
|
|
}
|
|
} else {
|
|
if obj.ElseBranch != nil { // else branch is optional
|
|
output, err = obj.ElseBranch.Output(table)
|
|
}
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
resources := []engine.Res{}
|
|
edges := []*interfaces.Edge{}
|
|
if output != nil {
|
|
resources = append(resources, output.Resources...)
|
|
edges = append(edges, output.Edges...)
|
|
}
|
|
|
|
return &interfaces.Output{
|
|
Resources: resources,
|
|
Edges: edges,
|
|
}, nil
|
|
}
|
|
|
|
// StmtProg represents a list of stmt's. This usually occurs at the top-level of
|
|
// any program, and often within an if stmt. It also contains the logic so that
|
|
// 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
|
|
|
|
// 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
|
|
importFiles []string // list of files seen during the SetScope import
|
|
|
|
Body []interfaces.Stmt
|
|
}
|
|
|
|
// String returns a short representation of this statement.
|
|
func (obj *StmtProg) String() string {
|
|
return "prog" // TODO: improve this
|
|
}
|
|
|
|
// Apply is a general purpose iterator method that operates on any AST node. It
|
|
// is not used as the primary AST traversal function because it is less readable
|
|
// and easy to reason about than manually implementing traversal for each node.
|
|
// Nevertheless, it is a useful facility for operations that might only apply to
|
|
// a select number of node types, since they won't need extra noop iterators...
|
|
func (obj *StmtProg) Apply(fn func(interfaces.Node) error) error {
|
|
for _, x := range obj.Body {
|
|
if err := x.Apply(fn); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// might as well Apply on these too, to make file collection easier, etc
|
|
for _, x := range obj.importProgs {
|
|
if err := x.Apply(fn); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return fn(obj)
|
|
}
|
|
|
|
// Init initializes this branch of the AST, and returns an error if it fails to
|
|
// validate.
|
|
func (obj *StmtProg) Init(data *interfaces.Data) error {
|
|
obj.data = data
|
|
obj.importProgs = []*StmtProg{}
|
|
obj.importFiles = []string{}
|
|
for _, x := range obj.Body {
|
|
if err := x.Init(data); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Interpolate returns a new node (aka a copy) once it has been expanded. This
|
|
// generally increases the size of the AST when it is used. It calls Interpolate
|
|
// on any child elements and builds the new node with those new node contents.
|
|
func (obj *StmtProg) Interpolate() (interfaces.Stmt, error) {
|
|
body := []interfaces.Stmt{}
|
|
for _, x := range obj.Body {
|
|
interpolated, err := x.Interpolate()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
body = append(body, interpolated)
|
|
}
|
|
return &StmtProg{
|
|
data: obj.data,
|
|
scope: obj.scope,
|
|
importProgs: obj.importProgs, // TODO: do we even need this here?
|
|
importFiles: obj.importFiles,
|
|
Body: body,
|
|
}, nil
|
|
}
|
|
|
|
// Copy returns a light copy of this struct. Anything static will not be copied.
|
|
func (obj *StmtProg) Copy() (interfaces.Stmt, error) {
|
|
copied := false
|
|
body := []interfaces.Stmt{}
|
|
for _, x := range obj.Body {
|
|
cp, err := x.Copy()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if cp != x { // must have been copied, or pointer would be same
|
|
copied = true
|
|
}
|
|
body = append(body, cp)
|
|
}
|
|
|
|
if !copied { // it's static
|
|
return obj, nil
|
|
}
|
|
return &StmtProg{
|
|
data: obj.data,
|
|
scope: obj.scope,
|
|
importProgs: obj.importProgs, // TODO: do we even need this here?
|
|
importFiles: obj.importFiles,
|
|
Body: body,
|
|
}, nil
|
|
}
|
|
|
|
// Ordering returns a graph of the scope ordering that represents the data flow.
|
|
// This can be used in SetScope so that it knows the correct order to run it in.
|
|
// The interesting part of the Ordering determination happens right here in
|
|
// StmtProg. This first looks at all the children to see what this produces, and
|
|
// then it recursively builds the graph by looking into all the children with
|
|
// this information from the first pass. We link production and consumption via
|
|
// a unique string name which is used to determine flow. Of particular note, all
|
|
// of this happens *before* SetScope, so we cannot follow references in the
|
|
// scope. The input to this method is a mapping of the the produced unique names
|
|
// in the parent "scope", to their associated node pointers. This returns a map
|
|
// of what is consumed in the child AST. The map is reversed, because two
|
|
// different nodes could consume the same variable key.
|
|
// TODO: deal with StmtImport's by returning them as first if necessary?
|
|
func (obj *StmtProg) Ordering(produces map[string]interfaces.Node) (*pgraph.Graph, map[interfaces.Node]string, error) {
|
|
graph, err := pgraph.NewGraph("ordering")
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
graph.AddVertex(obj)
|
|
|
|
prod := make(map[string]interfaces.Node)
|
|
for _, x := range obj.Body {
|
|
if stmt, ok := x.(*StmtClass); ok {
|
|
if stmt.Name == "" {
|
|
return nil, nil, fmt.Errorf("missing class name")
|
|
}
|
|
uid := classOrderingPrefix + stmt.Name // ordering id
|
|
n, exists := prod[uid]
|
|
if exists {
|
|
return nil, nil, fmt.Errorf("duplicate assignment to `%s`, have: %s", uid, n)
|
|
}
|
|
prod[uid] = stmt // store
|
|
}
|
|
if stmt, ok := x.(*StmtFunc); ok {
|
|
if stmt.Name == "" {
|
|
return nil, nil, fmt.Errorf("missing func name")
|
|
}
|
|
uid := funcOrderingPrefix + stmt.Name // ordering id
|
|
n, exists := prod[uid]
|
|
if exists {
|
|
return nil, nil, fmt.Errorf("duplicate assignment to `%s`, have: %s", uid, n)
|
|
}
|
|
prod[uid] = stmt // store
|
|
}
|
|
if stmt, ok := x.(*StmtBind); ok {
|
|
if stmt.Ident == "" {
|
|
return nil, nil, fmt.Errorf("missing bind name")
|
|
}
|
|
uid := varOrderingPrefix + stmt.Ident // ordering id
|
|
n, exists := prod[uid]
|
|
if exists {
|
|
return nil, nil, fmt.Errorf("duplicate assignment to `%s`, have: %s", uid, n)
|
|
}
|
|
prod[uid] = stmt // store
|
|
}
|
|
}
|
|
|
|
// TODO: move to a util package?
|
|
cp := func(in map[string]interfaces.Node) map[string]interfaces.Node {
|
|
out := make(map[string]interfaces.Node)
|
|
for k, v := range in {
|
|
out[k] = v // copy the map, not the Node's
|
|
}
|
|
return out
|
|
}
|
|
newProduces := cp(produces) // don't modify the input map!
|
|
|
|
// Overwrite anything in this scope with the shadowed parent variable!
|
|
for key, val := range prod {
|
|
newProduces[key] = val // copy, and overwrite (shadow) any parent var
|
|
}
|
|
|
|
cons := make(map[interfaces.Node]string) // swapped!
|
|
|
|
for _, node := range obj.Body {
|
|
g, c, err := node.Ordering(newProduces)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
graph.AddGraph(g) // add in the child graph
|
|
|
|
// additional constraint...
|
|
edge := &pgraph.SimpleEdge{Name: "stmtprognode"}
|
|
graph.AddEdge(node, obj, edge) // prod -> cons
|
|
|
|
for k, v := range c { // c is consumes
|
|
x, exists := cons[k]
|
|
if exists && v != x {
|
|
return nil, nil, fmt.Errorf("consumed value is different, got `%+v`, expected `%+v`", x, v)
|
|
}
|
|
cons[k] = v // add to map
|
|
|
|
n, exists := newProduces[v]
|
|
if !exists {
|
|
continue
|
|
}
|
|
edge := &pgraph.SimpleEdge{Name: "stmtprog1"}
|
|
// We want the convention to be produces -> consumes.
|
|
graph.AddEdge(n, k, edge)
|
|
}
|
|
}
|
|
|
|
// TODO: is this redundant? do we need it?
|
|
for key, val := range newProduces { // string, node
|
|
for x, str := range cons { // node, string
|
|
if key != str {
|
|
continue
|
|
}
|
|
edge := &pgraph.SimpleEdge{Name: "stmtprog2"}
|
|
graph.AddEdge(val, x, edge) // prod -> cons
|
|
}
|
|
}
|
|
|
|
return graph, cons, nil
|
|
}
|
|
|
|
// importScope is a helper function called from SetScope. If it can't find a
|
|
// particular scope, then it can also run the downloader if it is available.
|
|
func (obj *StmtProg) importScope(info *interfaces.ImportData, scope *interfaces.Scope) (*interfaces.Scope, error) {
|
|
if obj.data.Debug {
|
|
obj.data.Logf("import: %s", info.Name)
|
|
}
|
|
// the abs file path that we started actively running SetScope on is:
|
|
// obj.data.Base + obj.data.Metadata.Main
|
|
// but recursive imports mean this is not always the active file...
|
|
|
|
if info.IsSystem { // system imports are the exact name, eg "fmt"
|
|
systemScope, err := obj.importSystemScope(info.Name)
|
|
if err != nil {
|
|
return nil, errwrap.Wrapf(err, "system import of `%s` failed", info.Name)
|
|
}
|
|
return systemScope, nil
|
|
}
|
|
|
|
// graph-based recursion detection
|
|
// TODO: is this sufficiently unique, but not incorrectly unique?
|
|
// TODO: do we need to clean uvid for consistency so the compare works?
|
|
uvid := obj.data.Base + ";" + info.Name // unique vertex id
|
|
importVertex := obj.data.Imports // parent vertex
|
|
if importVertex == nil {
|
|
return nil, fmt.Errorf("programming error: missing import vertex")
|
|
}
|
|
importGraph := importVertex.Graph // existing graph (ptr stored within)
|
|
nextVertex := &pgraph.SelfVertex{ // new vertex (if one doesn't already exist)
|
|
Name: uvid, // import name
|
|
Graph: importGraph, // store a reference to ourself
|
|
}
|
|
for _, v := range importGraph.VerticesSorted() { // search for one first
|
|
gv, ok := v.(*pgraph.SelfVertex)
|
|
if !ok { // someone misused the vertex
|
|
return nil, fmt.Errorf("programming error: unexpected vertex type")
|
|
}
|
|
if gv.Name == uvid {
|
|
nextVertex = gv // found the same name (use this instead!)
|
|
// this doesn't necessarily mean a cycle. a dag is okay
|
|
break
|
|
}
|
|
}
|
|
|
|
// add an edge
|
|
edge := &pgraph.SimpleEdge{Name: ""} // TODO: name me?
|
|
importGraph.AddEdge(importVertex, nextVertex, edge)
|
|
if _, err := importGraph.TopologicalSort(); err != nil {
|
|
// TODO: print the cycle in a prettier way (with file names?)
|
|
obj.data.Logf("import: not a dag:\n%s", importGraph.Sprint())
|
|
return nil, errwrap.Wrapf(err, "recursive import of: `%s`", info.Name)
|
|
}
|
|
|
|
if info.IsLocal {
|
|
// append the relative addition of where the running code is, on
|
|
// to the base path that the metadata file (data) is relative to
|
|
// if the main code file has no additional directory, then it is
|
|
// okay, because Dirname collapses down to the empty string here
|
|
importFilePath := obj.data.Base + util.Dirname(obj.data.Metadata.Main) + info.Path
|
|
if obj.data.Debug {
|
|
obj.data.Logf("import: file: %s", importFilePath)
|
|
}
|
|
// don't do this collection here, it has moved elsewhere...
|
|
//obj.importFiles = append(obj.importFiles, importFilePath) // save for CollectFiles
|
|
|
|
localScope, err := obj.importScopeWithInputs(importFilePath, scope, nextVertex)
|
|
if err != nil {
|
|
return nil, errwrap.Wrapf(err, "local import of `%s` failed", info.Name)
|
|
}
|
|
return localScope, nil
|
|
}
|
|
|
|
// Now, info.IsLocal is false... we're dealing with a remote import!
|
|
|
|
// This takes the current metadata as input so it can use the Path
|
|
// directory to search upwards if we wanted to look in parent paths.
|
|
// Since this is an fqdn import, it must contain a metadata file...
|
|
modulesPath, err := interfaces.FindModulesPath(obj.data.Metadata, obj.data.Base, obj.data.Modules)
|
|
if err != nil {
|
|
return nil, errwrap.Wrapf(err, "module path error")
|
|
}
|
|
importFilePath := modulesPath + info.Path + interfaces.MetadataFilename
|
|
|
|
if !RequireStrictModulePath { // look upwards
|
|
modulesPathList, err := interfaces.FindModulesPathList(obj.data.Metadata, obj.data.Base, obj.data.Modules)
|
|
if err != nil {
|
|
return nil, errwrap.Wrapf(err, "module path list error")
|
|
}
|
|
for _, mp := range modulesPathList { // first one to find a file
|
|
x := mp + info.Path + interfaces.MetadataFilename
|
|
if _, err := obj.data.Fs.Stat(x); err == nil {
|
|
// found a valid location, so keep using it!
|
|
modulesPath = mp
|
|
importFilePath = x
|
|
break
|
|
}
|
|
}
|
|
// If we get here, and we didn't find anything, then we use the
|
|
// originally decided, most "precise" location... The reason we
|
|
// do that is if the sysadmin wishes to require all the modules
|
|
// to come from their top-level (or higher-level) directory, it
|
|
// can be done by adding the code there, so that it is found in
|
|
// the above upwards search. Otherwise, we just do what the mod
|
|
// asked for and use the path/ directory if it wants its own...
|
|
}
|
|
if obj.data.Debug {
|
|
obj.data.Logf("import: modules path: %s", modulesPath)
|
|
obj.data.Logf("import: file: %s", importFilePath)
|
|
}
|
|
// don't do this collection here, it has moved elsewhere...
|
|
//obj.importFiles = append(obj.importFiles, importFilePath) // save for CollectFiles
|
|
|
|
// invoke the download when a path is missing, if the downloader exists
|
|
// we need to invoke the recursive checker before we run this download!
|
|
// this should cleverly deal with skipping modules that are up-to-date!
|
|
if obj.data.Downloader != nil {
|
|
// run downloader stuff first
|
|
if err := obj.data.Downloader.Get(info, modulesPath); err != nil {
|
|
return nil, errwrap.Wrapf(err, "download of `%s` failed", info.Name)
|
|
}
|
|
}
|
|
|
|
// takes the full absolute path to the metadata.yaml file
|
|
remoteScope, err := obj.importScopeWithInputs(importFilePath, scope, nextVertex)
|
|
if err != nil {
|
|
return nil, errwrap.Wrapf(err, "remote import of `%s` failed", info.Name)
|
|
}
|
|
return remoteScope, nil
|
|
}
|
|
|
|
// importSystemScope takes the name of a built-in system scope (eg: "fmt") and
|
|
// returns the scope struct for that built-in. This function is slightly less
|
|
// trivial than expected, because the scope is built from both native mcl code
|
|
// and golang code as well. The native mcl code is compiled in with "embed".
|
|
// TODO: can we memoize?
|
|
func (obj *StmtProg) importSystemScope(name string) (*interfaces.Scope, error) {
|
|
// this basically loop through the registeredFuncs and includes
|
|
// everything that starts with the name prefix and a period, and then
|
|
// lexes and parses the compiled in code, and adds that on top of the
|
|
// scope. we error if there's a duplicate!
|
|
|
|
isEmpty := true // assume empty (which should cause an error)
|
|
|
|
functions := FuncPrefixToFunctionsScope(name) // runs funcs.LookupPrefix
|
|
if len(functions) > 0 {
|
|
isEmpty = false
|
|
}
|
|
|
|
// perform any normal "startup" for these functions...
|
|
for _, fn := range functions {
|
|
// XXX: is this the right place for this, or should it be elsewhere?
|
|
// XXX: do we need a modified obj.data for this b/c it's in a scope?
|
|
if err := fn.Init(obj.data); err != nil {
|
|
return nil, errwrap.Wrapf(err, "could not init function")
|
|
}
|
|
// TODO: do we want to run Interpolate or SetScope?
|
|
}
|
|
|
|
// initial scope, built from core golang code
|
|
scope := &interfaces.Scope{
|
|
// TODO: we could use the core API for variables somehow...
|
|
//Variables: make(map[string]interfaces.Expr),
|
|
Functions: functions, // map[string]interfaces.Expr
|
|
// TODO: we could add a core API for classes too!
|
|
//Classes: make(map[string]interfaces.Stmt),
|
|
}
|
|
|
|
// TODO: the obj.data.Fs filesystem handle is unused for now, but might
|
|
// be useful if we ever ship all the specific versions of system modules
|
|
// to the remote machines as well, and we want to load off of it...
|
|
|
|
// now add any compiled-in mcl code
|
|
paths, err := core.AssetNames()
|
|
if err != nil {
|
|
return nil, errwrap.Wrapf(err, "can't read asset names")
|
|
}
|
|
// results are not sorted by default (ascertained by reading the code!)
|
|
sort.Strings(paths)
|
|
newScope := interfaces.EmptyScope()
|
|
// XXX: consider using a virtual `append *` statement to combine these instead.
|
|
for _, p := range paths {
|
|
// we only want code from this prefix
|
|
prefix := funcs.CoreDir + name + "/"
|
|
if !strings.HasPrefix(p, prefix) {
|
|
continue
|
|
}
|
|
// we only want code from this directory level, so skip children
|
|
// heuristically, a child mcl file will contain a path separator
|
|
if strings.Contains(p[len(prefix):], "/") {
|
|
continue
|
|
}
|
|
|
|
b, err := core.Asset(p)
|
|
if err != nil {
|
|
return nil, errwrap.Wrapf(err, "can't read asset: `%s`", p)
|
|
}
|
|
|
|
// to combine multiple *.mcl files from the same directory, we
|
|
// lex and parse each one individually, which each produces a
|
|
// scope struct. we then merge the scope structs, while making
|
|
// sure we don't overwrite any values. (this logic is only valid
|
|
// for modules, as top-level code combines the output values
|
|
// instead.)
|
|
|
|
reader := bytes.NewReader(b) // wrap the byte stream
|
|
|
|
// now run the lexer/parser to do the import
|
|
ast, err := obj.data.LexParser(reader)
|
|
if err != nil {
|
|
return nil, errwrap.Wrapf(err, "could not generate AST from import `%s`", name)
|
|
}
|
|
if obj.data.Debug {
|
|
obj.data.Logf("behold, the AST: %+v", ast)
|
|
}
|
|
|
|
obj.data.Logf("init...")
|
|
// init and validate the structure of the AST
|
|
// some of this might happen *after* interpolate in SetScope or Unify...
|
|
if err := ast.Init(obj.data); err != nil {
|
|
return nil, errwrap.Wrapf(err, "could not init and validate AST")
|
|
}
|
|
|
|
obj.data.Logf("interpolating...")
|
|
// interpolate strings and other expansionable nodes in AST
|
|
interpolated, err := ast.Interpolate()
|
|
if err != nil {
|
|
return nil, errwrap.Wrapf(err, "could not interpolate AST from import `%s`", name)
|
|
}
|
|
|
|
obj.data.Logf("building scope...")
|
|
// propagate the scope down through the AST...
|
|
// most importantly, we ensure that the child imports will run!
|
|
// we pass in *our* parent scope, which will include the globals
|
|
if err := interpolated.SetScope(scope); err != nil {
|
|
return nil, errwrap.Wrapf(err, "could not set scope from import `%s`", name)
|
|
}
|
|
|
|
// is the root of our ast a program?
|
|
prog, ok := interpolated.(*StmtProg)
|
|
if !ok {
|
|
return nil, fmt.Errorf("import `%s` did not return a program", name)
|
|
}
|
|
|
|
if prog.scope == nil { // pull out the result
|
|
continue // nothing to do here, continue with the next!
|
|
}
|
|
|
|
// check for unwanted top-level elements in this module/scope
|
|
// XXX: add a test case to test for this in our core modules!
|
|
if err := prog.IsModuleUnsafe(); err != nil {
|
|
return nil, errwrap.Wrapf(err, "module contains unused statements")
|
|
}
|
|
|
|
if !prog.scope.IsEmpty() {
|
|
isEmpty = false // this module/scope isn't empty
|
|
}
|
|
|
|
// save a reference to the prog for future usage in Unify/Graph/Etc...
|
|
// XXX: we don't need to do this if we can combine with Append!
|
|
obj.importProgs = append(obj.importProgs, prog)
|
|
|
|
// attempt to merge
|
|
// XXX: test for duplicate var/func/class elements in a test!
|
|
if err := newScope.Merge(prog.scope); err != nil { // errors if something was overwritten
|
|
// XXX: we get a false positive b/c we overwrite the initial scope!
|
|
// XXX: when we switch to append, this problem will go away...
|
|
//return nil, errwrap.Wrapf(err, "duplicate scope element(s) in module found")
|
|
}
|
|
}
|
|
|
|
if err := scope.Merge(newScope); err != nil { // errors if something was overwritten
|
|
// XXX: we get a false positive b/c we overwrite the initial scope!
|
|
// XXX: when we switch to append, this problem will go away...
|
|
//return nil, errwrap.Wrapf(err, "duplicate scope element(s) found")
|
|
}
|
|
|
|
// when importing a system scope, we only error if there are zero class,
|
|
// function, or variable statements in the scope. We error in this case,
|
|
// because it is non-sensical to import such a scope.
|
|
if isEmpty {
|
|
return nil, fmt.Errorf("could not find any non-empty scope named: %s", name)
|
|
}
|
|
|
|
return scope, nil
|
|
}
|
|
|
|
// importScopeWithInputs returns a local or remote scope from an inputs string.
|
|
// The inputs string is the common frontend for a lot of our parsing decisions.
|
|
func (obj *StmtProg) importScopeWithInputs(s string, scope *interfaces.Scope, parentVertex *pgraph.SelfVertex) (*interfaces.Scope, error) {
|
|
output, err := inputs.ParseInput(s, obj.data.Fs)
|
|
if err != nil {
|
|
return nil, errwrap.Wrapf(err, "could not activate an input parser")
|
|
}
|
|
|
|
// TODO: rm this old, and incorrect, linear file duplicate checking...
|
|
// recursion detection (i guess following the imports has to be a dag!)
|
|
// run recursion detection by checking for duplicates in the seen files
|
|
// TODO: do the paths need to be cleaned for "../", etc before compare?
|
|
//for _, name := range obj.data.Files { // existing seen files
|
|
// if util.StrInList(name, output.Files) {
|
|
// return nil, fmt.Errorf("recursive import of: `%s`", name)
|
|
// }
|
|
//}
|
|
|
|
reader := bytes.NewReader(output.Main)
|
|
|
|
// nested logger
|
|
logf := func(format string, v ...interface{}) {
|
|
obj.data.Logf("import: "+format, v...)
|
|
}
|
|
|
|
// build new list of files
|
|
files := []string{}
|
|
files = append(files, output.Files...)
|
|
files = append(files, obj.data.Files...)
|
|
|
|
// store a reference to the parent metadata
|
|
metadata := output.Metadata
|
|
metadata.Metadata = obj.data.Metadata
|
|
|
|
// now run the lexer/parser to do the import
|
|
ast, err := obj.data.LexParser(reader)
|
|
if err != nil {
|
|
return nil, errwrap.Wrapf(err, "could not generate AST from import")
|
|
}
|
|
if obj.data.Debug {
|
|
logf("behold, the AST: %+v", ast)
|
|
}
|
|
|
|
logf("init...")
|
|
// init and validate the structure of the AST
|
|
data := &interfaces.Data{
|
|
// TODO: add missing fields here if/when needed
|
|
Fs: obj.data.Fs,
|
|
FsURI: obj.data.FsURI,
|
|
Base: output.Base, // new base dir (absolute path)
|
|
Files: files,
|
|
Imports: parentVertex, // the parent vertex that imported me
|
|
Metadata: metadata,
|
|
Modules: obj.data.Modules,
|
|
|
|
LexParser: obj.data.LexParser,
|
|
Downloader: obj.data.Downloader,
|
|
StrInterpolater: obj.data.StrInterpolater,
|
|
//World: obj.data.World, // TODO: do we need this?
|
|
|
|
//Prefix: obj.Prefix, // TODO: add a path on?
|
|
Debug: obj.data.Debug,
|
|
Logf: logf,
|
|
}
|
|
// some of this might happen *after* interpolate in SetScope or Unify...
|
|
if err := ast.Init(data); err != nil {
|
|
return nil, errwrap.Wrapf(err, "could not init and validate AST")
|
|
}
|
|
|
|
logf("interpolating...")
|
|
// interpolate strings and other expansionable nodes in AST
|
|
interpolated, err := ast.Interpolate()
|
|
if err != nil {
|
|
return nil, errwrap.Wrapf(err, "could not interpolate AST from import")
|
|
}
|
|
|
|
logf("building scope...")
|
|
// propagate the scope down through the AST...
|
|
// most importantly, we ensure that the child imports will run!
|
|
// we pass in *our* parent scope, which will include the globals
|
|
if err := interpolated.SetScope(scope); err != nil {
|
|
return nil, errwrap.Wrapf(err, "could not set scope from import")
|
|
}
|
|
|
|
// we DON'T do this here anymore, since Apply() digs into the children!
|
|
//// this nested ast needs to pass the data up into the parent!
|
|
//fileList, err := CollectFiles(interpolated)
|
|
//if err != nil {
|
|
// return nil, errwrap.Wrapf(err, "could not collect files")
|
|
//}
|
|
//obj.importFiles = append(obj.importFiles, fileList...) // save for CollectFiles
|
|
|
|
// is the root of our ast a program?
|
|
prog, ok := interpolated.(*StmtProg)
|
|
if !ok {
|
|
return nil, fmt.Errorf("import did not return a program")
|
|
}
|
|
|
|
// check for unwanted top-level elements in this module/scope
|
|
// XXX: add a test case to test for this in our core modules!
|
|
if err := prog.IsModuleUnsafe(); err != nil {
|
|
return nil, errwrap.Wrapf(err, "module contains unused statements")
|
|
}
|
|
|
|
// when importing a system scope, we only error if there are zero class,
|
|
// function, or variable statements in the scope. We error in this case,
|
|
// because it is non-sensical to import such a scope.
|
|
if prog.scope.IsEmpty() {
|
|
return nil, fmt.Errorf("could not find any non-empty scope")
|
|
}
|
|
if obj.data.Debug {
|
|
obj.data.Logf("imported scope:")
|
|
for k, v := range prog.scope.Variables {
|
|
// print the type of v
|
|
obj.data.Logf("\t%s: %T", k, v)
|
|
}
|
|
}
|
|
|
|
// save a reference to the prog for future usage in Unify/Graph/Etc...
|
|
obj.importProgs = append(obj.importProgs, prog)
|
|
|
|
// collecting these here is more elegant (and possibly more efficient!)
|
|
obj.importFiles = append(obj.importFiles, output.Files...) // save for CollectFiles
|
|
|
|
return prog.scope, nil
|
|
}
|
|
|
|
// SetScope propagates the scope into its list of statements. It does so
|
|
// cleverly by first collecting all bind and func statements and adding those
|
|
// into the scope after checking for any collisions. Finally it pushes the new
|
|
// scope downwards to all child statements. If we support user defined function
|
|
// polymorphism via multiple function definition, then these are built together
|
|
// here. This SetScope is the one which follows the import statements. If it
|
|
// can't follow one (perhaps it wasn't downloaded yet, and is missing) then it
|
|
// leaves some information about these missing imports in the AST and errors, so
|
|
// that a subsequent AST traversal (usually via Apply) can collect this detailed
|
|
// information to be used by the downloader. When it propagates the scope
|
|
// downwards, it first pushes it into all the classes, and then into everything
|
|
// else (including the include stmt's) because the include statements require
|
|
// that the scope already be known so that it can be combined with the include
|
|
// args.
|
|
func (obj *StmtProg) SetScope(scope *interfaces.Scope) error {
|
|
newScope := scope.Copy()
|
|
|
|
// start by looking for any `import` statements to pull into the scope!
|
|
// this will run child lexing/parsing, interpolation, and scope setting
|
|
imports := make(map[string]struct{})
|
|
aliases := make(map[string]struct{})
|
|
|
|
// keep track of new imports, to ensure they don't overwrite each other!
|
|
// this is different from scope shadowing which is allowed in new scopes
|
|
newVariables := make(map[string]string)
|
|
newFunctions := make(map[string]string)
|
|
newClasses := make(map[string]string)
|
|
for _, x := range obj.Body {
|
|
imp, ok := x.(*StmtImport)
|
|
if !ok {
|
|
continue
|
|
}
|
|
// check for duplicates *in this scope*
|
|
if _, exists := imports[imp.Name]; exists {
|
|
return fmt.Errorf("import `%s` already exists in this scope", imp.Name)
|
|
}
|
|
|
|
result, err := langutil.ParseImportName(imp.Name)
|
|
if err != nil {
|
|
return errwrap.Wrapf(err, "import `%s` is not valid", imp.Name)
|
|
}
|
|
alias := result.Alias // this is what we normally call the import
|
|
|
|
if imp.Alias != "" { // this is what the user decided as the name
|
|
alias = imp.Alias // use alias if specified
|
|
}
|
|
if _, exists := aliases[alias]; exists {
|
|
return fmt.Errorf("import alias `%s` already exists in this scope", alias)
|
|
}
|
|
|
|
// run the scope importer...
|
|
importedScope, err := obj.importScope(result, scope)
|
|
if err != nil {
|
|
return errwrap.Wrapf(err, "import scope `%s` failed", imp.Name)
|
|
}
|
|
|
|
// read from stored scope which was previously saved in SetScope
|
|
// add to scope, (overwriting, aka shadowing is ok)
|
|
// rename scope values, adding the alias prefix
|
|
// check that we don't overwrite a new value from another import
|
|
// TODO: do this in a deterministic (sorted) order
|
|
for name, x := range importedScope.Variables {
|
|
newName := alias + interfaces.ModuleSep + name
|
|
if alias == "*" {
|
|
newName = name
|
|
}
|
|
if previous, exists := newVariables[newName]; exists {
|
|
// don't overwrite in same scope
|
|
return fmt.Errorf("can't squash variable `%s` from `%s` by import of `%s`", newName, previous, imp.Name)
|
|
}
|
|
newVariables[newName] = imp.Name
|
|
newScope.Variables[newName] = x // merge
|
|
}
|
|
for name, x := range importedScope.Functions {
|
|
newName := alias + interfaces.ModuleSep + name
|
|
if alias == "*" {
|
|
newName = name
|
|
}
|
|
if previous, exists := newFunctions[newName]; exists {
|
|
// don't overwrite in same scope
|
|
return fmt.Errorf("can't squash function `%s` from `%s` by import of `%s`", newName, previous, imp.Name)
|
|
}
|
|
newFunctions[newName] = imp.Name
|
|
newScope.Functions[newName] = x
|
|
}
|
|
for name, x := range importedScope.Classes {
|
|
newName := alias + interfaces.ModuleSep + name
|
|
if alias == "*" {
|
|
newName = name
|
|
}
|
|
if previous, exists := newClasses[newName]; exists {
|
|
// don't overwrite in same scope
|
|
return fmt.Errorf("can't squash class `%s` from `%s` by import of `%s`", newName, previous, imp.Name)
|
|
}
|
|
newClasses[newName] = imp.Name
|
|
newScope.Classes[newName] = x
|
|
}
|
|
|
|
// everything has been merged, move on to next import...
|
|
imports[imp.Name] = struct{}{} // mark as found in scope
|
|
aliases[alias] = struct{}{}
|
|
}
|
|
|
|
// collect all the bind statements in the first pass
|
|
// this allows them to appear out of order in this scope
|
|
binds := make(map[string]struct{}) // bind existence in this scope
|
|
for _, x := range obj.Body {
|
|
bind, ok := x.(*StmtBind)
|
|
if !ok {
|
|
continue
|
|
}
|
|
// check for duplicates *in this scope*
|
|
if _, exists := binds[bind.Ident]; exists {
|
|
return fmt.Errorf("var `%s` already exists in this scope", bind.Ident)
|
|
}
|
|
|
|
binds[bind.Ident] = struct{}{} // mark as found in scope
|
|
// add to scope, (overwriting, aka shadowing is ok)
|
|
newScope.Variables[bind.Ident] = &ExprTopLevel{
|
|
Definition: &ExprSingleton{
|
|
Definition: bind.Value,
|
|
|
|
mutex: &sync.Mutex{}, // TODO: call Init instead
|
|
},
|
|
CapturedScope: newScope,
|
|
}
|
|
if obj.data.Debug { // TODO: is this message ever useful?
|
|
obj.data.Logf("prog: set scope: bind collect: (%+v): %+v (%T) is %p", bind.Ident, bind.Value, bind.Value, bind.Value)
|
|
}
|
|
}
|
|
|
|
// now collect all the functions, and group by name (if polyfunc is ok)
|
|
functions := make(map[string][]*StmtFunc)
|
|
for _, x := range obj.Body {
|
|
fn, ok := x.(*StmtFunc)
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
_, exists := functions[fn.Name]
|
|
if !exists {
|
|
functions[fn.Name] = []*StmtFunc{} // initialize
|
|
}
|
|
|
|
// check for duplicates *in this scope*
|
|
if exists && !AllowUserDefinedPolyFunc {
|
|
return fmt.Errorf("func `%s` already exists in this scope", fn.Name)
|
|
}
|
|
|
|
// collect functions (if multiple, this is a polyfunc)
|
|
functions[fn.Name] = append(functions[fn.Name], fn)
|
|
}
|
|
|
|
for name, fnList := range functions {
|
|
if obj.data.Debug { // TODO: is this message ever useful?
|
|
obj.data.Logf("prog: set scope: collect: (%+v -> %d): %+v (%T)", name, len(fnList), fnList[0].Func, fnList[0].Func)
|
|
}
|
|
// add to scope, (overwriting, aka shadowing is ok)
|
|
if len(fnList) == 1 {
|
|
fn := fnList[0].Func // local reference to avoid changing it in the loop...
|
|
// add to scope, (overwriting, aka shadowing is ok)
|
|
newScope.Functions[name] = &ExprPoly{ // XXX: is this ExprPoly approach optimal?
|
|
Definition: &ExprTopLevel{
|
|
Definition: fn, // store the *ExprFunc
|
|
CapturedScope: newScope,
|
|
},
|
|
}
|
|
continue
|
|
}
|
|
|
|
// build polyfunc's
|
|
// XXX: not implemented
|
|
return fmt.Errorf("user-defined polyfuncs of length %d are not supported", len(fnList))
|
|
}
|
|
|
|
// now collect any classes
|
|
// TODO: if we ever allow poly classes, then group in lists by name
|
|
classes := make(map[string]struct{})
|
|
for _, x := range obj.Body {
|
|
class, ok := x.(*StmtClass)
|
|
if !ok {
|
|
continue
|
|
}
|
|
// check for duplicates *in this scope*
|
|
if _, exists := classes[class.Name]; exists {
|
|
return fmt.Errorf("class `%s` already exists in this scope", class.Name)
|
|
}
|
|
|
|
classes[class.Name] = struct{}{} // mark as found in scope
|
|
// add to scope, (overwriting, aka shadowing is ok)
|
|
newScope.Classes[class.Name] = class
|
|
}
|
|
|
|
obj.scope = newScope // save a reference in case we're read by an import
|
|
|
|
// This is the legacy variant of this function that doesn't allow
|
|
// out-of-order code. It also returns obscure error messages for some
|
|
// cases, such as double-recursion. It's left here for reference.
|
|
if legacyProgSetScope {
|
|
// first set the scope on the classes, since it gets used in include...
|
|
for _, stmt := range obj.Body {
|
|
//if _, ok := stmt.(*StmtClass); !ok {
|
|
// continue
|
|
//}
|
|
_, ok1 := stmt.(*StmtClass)
|
|
_, ok2 := stmt.(*StmtFunc) // TODO: is this correct?
|
|
_, ok3 := stmt.(*StmtBind) // TODO: is this correct?
|
|
if !ok1 && !ok2 && !ok3 { // if all are false, we skip
|
|
continue
|
|
}
|
|
|
|
if obj.data.Debug {
|
|
obj.data.Logf("prog: set scope: pass 1: %+v", stmt)
|
|
}
|
|
if err := stmt.SetScope(newScope); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// now set the child scopes...
|
|
for _, stmt := range obj.Body {
|
|
// NOTE: We used to skip over *StmtClass here for recursion...
|
|
// Skip over *StmtClass here, since we already did it above...
|
|
if _, ok := stmt.(*StmtClass); ok {
|
|
continue
|
|
}
|
|
if _, ok := stmt.(*StmtFunc); ok { // TODO: is this correct?
|
|
continue
|
|
}
|
|
if _, ok := stmt.(*StmtBind); ok { // TODO: is this correct?
|
|
continue
|
|
}
|
|
|
|
if obj.data.Debug {
|
|
obj.data.Logf("prog: set scope: pass 2: %+v", stmt)
|
|
}
|
|
if err := stmt.SetScope(newScope); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// 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 errwrap.Wrapf(err, "could not generate ordering")
|
|
}
|
|
|
|
// debugging visualizations
|
|
if obj.data.Debug && orderingGraphSingleton {
|
|
obj.data.Logf("running graphviz for ordering graph...")
|
|
if err := orderingGraph.ExecGraphviz("/tmp/graphviz-ordering.dot"); err != nil {
|
|
obj.data.Logf("graphviz: errored: %+v", err)
|
|
}
|
|
// Only generate the top-level one, to prevent overwriting this!
|
|
orderingGraphSingleton = false
|
|
}
|
|
|
|
nodeOrder, err := orderingGraph.TopologicalSort()
|
|
if err != nil {
|
|
// TODO: print the cycle in a prettier way (with names?)
|
|
if obj.data.Debug {
|
|
obj.data.Logf("set scope: not a dag:\n%s", orderingGraph.Sprint())
|
|
}
|
|
return errwrap.Wrapf(err, "recursive reference while setting scope")
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
stmts := []interfaces.Stmt{}
|
|
for _, x := range nodeOrder { // these are in the correct order for SetScope
|
|
stmt, ok := x.(interfaces.Stmt)
|
|
if !ok {
|
|
continue
|
|
}
|
|
if _, ok := x.(*StmtImport); ok { // TODO: should we skip this?
|
|
continue
|
|
}
|
|
if !stmtInList(stmt, obj.Body) {
|
|
// Skip any unwanted additions that we pulled in.
|
|
continue
|
|
}
|
|
stmts = append(stmts, stmt)
|
|
}
|
|
if obj.data.Debug {
|
|
obj.data.Logf("prog: set scope: ordering: %+v", stmts)
|
|
}
|
|
|
|
// Optimization: In addition to importantly skipping the parts of the
|
|
// graph that don't belong in this StmtProg, this also causes
|
|
// un-consumed statements to be skipped. As a result, this simplifies
|
|
// the graph significantly in cases of unused code, because they're not
|
|
// given a chance to SetScope even though they're in the StmtProg list.
|
|
for _, x := range nodeOrder { // these are in the correct order for SetScope
|
|
stmt, ok := x.(interfaces.Stmt)
|
|
if !ok {
|
|
continue
|
|
}
|
|
if _, ok := x.(*StmtImport); ok { // TODO: should we skip this?
|
|
continue
|
|
}
|
|
if !stmtInList(stmt, obj.Body) {
|
|
// Skip any unwanted additions that we pulled in.
|
|
continue
|
|
}
|
|
if obj.data.Debug {
|
|
obj.data.Logf("prog: set scope: order: %+v", stmt)
|
|
}
|
|
if err := stmt.SetScope(newScope); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if obj.data.Debug {
|
|
obj.data.Logf("prog: set scope: finished")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Unify returns the list of invariants that this node produces. It recursively
|
|
// calls Unify on any children elements that exist in the AST, and returns the
|
|
// collection to the caller.
|
|
func (obj *StmtProg) Unify() ([]interfaces.Invariant, error) {
|
|
var invariants []interfaces.Invariant
|
|
|
|
// collect all the invariants of each sub-expression
|
|
for _, x := range obj.Body {
|
|
// skip over *StmtClass here
|
|
if _, ok := x.(*StmtClass); ok {
|
|
continue
|
|
}
|
|
if _, ok := x.(*StmtFunc); ok { // TODO: is this correct?
|
|
continue
|
|
}
|
|
//if _, ok := x.(*StmtBind); ok { // TODO: is this correct?
|
|
// continue
|
|
//}
|
|
|
|
invars, err := x.Unify()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
invariants = append(invariants, invars...)
|
|
}
|
|
|
|
// add invariants from SetScope's imported child programs
|
|
for _, x := range obj.importProgs {
|
|
invars, err := x.Unify()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
invariants = append(invariants, invars...)
|
|
}
|
|
|
|
return invariants, nil
|
|
}
|
|
|
|
// Graph returns the reactive function graph which is expressed by this node. It
|
|
// includes any vertices produced by this node, and the appropriate edges to any
|
|
// vertices that are produced by its children. Nodes which fulfill the Expr
|
|
// interface directly produce vertices (and possible children) where as nodes
|
|
// that fulfill the Stmt interface do not produces vertices, where as their
|
|
// children might.
|
|
func (obj *StmtProg) Graph() (*pgraph.Graph, error) {
|
|
graph, err := pgraph.NewGraph("prog")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// 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.Graph()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
graph.AddGraph(g)
|
|
}
|
|
|
|
// add graphs from SetScope's imported child programs
|
|
//for _, x := range obj.importProgs {
|
|
// g, err := x.Graph(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
|
|
// called by this Output function if they are needed to produce the output.
|
|
func (obj *StmtProg) Output(table map[interfaces.Func]types.Value) (*interfaces.Output, error) {
|
|
resources := []engine.Res{}
|
|
edges := []*interfaces.Edge{}
|
|
|
|
for _, stmt := range obj.Body {
|
|
// skip over *StmtClass here so its Output method can be used...
|
|
if _, ok := stmt.(*StmtClass); ok {
|
|
// don't read output from StmtClass, it
|
|
// gets consumed by StmtInclude instead
|
|
continue
|
|
}
|
|
// skip over StmtFunc, even though it doesn't produce anything!
|
|
if _, ok := stmt.(*StmtFunc); ok {
|
|
continue
|
|
}
|
|
// skip over StmtBind, even though it doesn't produce anything!
|
|
if _, ok := stmt.(*StmtBind); ok {
|
|
continue
|
|
}
|
|
|
|
output, err := stmt.Output(table)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if output != nil {
|
|
resources = append(resources, output.Resources...)
|
|
edges = append(edges, output.Edges...)
|
|
}
|
|
}
|
|
|
|
// nothing to add from SetScope's imported child programs
|
|
|
|
return &interfaces.Output{
|
|
Resources: resources,
|
|
Edges: edges,
|
|
}, nil
|
|
}
|
|
|
|
// IsModuleUnsafe returns whether or not this StmtProg is unsafe to consume as a
|
|
// module scope. IOW, if someone writes a module which is imported and which has
|
|
// statements other than bind, func, class or import, then it is not correct to
|
|
// import, since those other elements wouldn't be used, and might provide a
|
|
// false belief that they'll get included when mgmt imports that module.
|
|
// SetScope should be called before this is used. (TODO: verify this)
|
|
// TODO: return a multierr with all the unsafe elements, to provide better info
|
|
// TODO: technically this could be a method on Stmt, possibly using Apply...
|
|
func (obj *StmtProg) IsModuleUnsafe() error { // TODO: rename this function?
|
|
for _, x := range obj.Body {
|
|
// stmt's allowed: import, bind, func, class
|
|
// stmt's not-allowed: if, include, res, edge
|
|
switch x.(type) {
|
|
case *StmtImport:
|
|
case *StmtBind:
|
|
case *StmtFunc:
|
|
case *StmtClass:
|
|
case *StmtComment: // possibly not even parsed
|
|
// all of these are safe
|
|
default:
|
|
// something else unsafe (unused)
|
|
return fmt.Errorf("found stmt: %s", x.String())
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// StmtFunc represents a user defined function. It binds the specified name to
|
|
// the supplied function in the current scope and irrespective of the order of
|
|
// definition.
|
|
type StmtFunc struct {
|
|
Name string
|
|
//Func *ExprFunc // TODO: should it be this instead?
|
|
Func interfaces.Expr // TODO: is this correct?
|
|
}
|
|
|
|
// String returns a short representation of this statement.
|
|
func (obj *StmtFunc) String() string {
|
|
return fmt.Sprintf("func(%s)", obj.Name)
|
|
}
|
|
|
|
// Apply is a general purpose iterator method that operates on any AST node. It
|
|
// is not used as the primary AST traversal function because it is less readable
|
|
// and easy to reason about than manually implementing traversal for each node.
|
|
// Nevertheless, it is a useful facility for operations that might only apply to
|
|
// a select number of node types, since they won't need extra noop iterators...
|
|
func (obj *StmtFunc) Apply(fn func(interfaces.Node) error) error {
|
|
if err := obj.Func.Apply(fn); err != nil {
|
|
return err
|
|
}
|
|
return fn(obj)
|
|
}
|
|
|
|
// Init initializes this branch of the AST, and returns an error if it fails to
|
|
// validate.
|
|
func (obj *StmtFunc) Init(data *interfaces.Data) error {
|
|
//obj.data = data // TODO: ???
|
|
if err := obj.Func.Init(data); err != nil {
|
|
return err
|
|
}
|
|
// no errors
|
|
return nil
|
|
}
|
|
|
|
// Interpolate returns a new node (or itself) once it has been expanded. This
|
|
// generally increases the size of the AST when it is used. It calls Interpolate
|
|
// on any child elements and builds the new node with those new node contents.
|
|
func (obj *StmtFunc) Interpolate() (interfaces.Stmt, error) {
|
|
interpolated, err := obj.Func.Interpolate()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &StmtFunc{
|
|
Name: obj.Name,
|
|
Func: interpolated,
|
|
}, nil
|
|
}
|
|
|
|
// Copy returns a light copy of this struct. Anything static will not be copied.
|
|
func (obj *StmtFunc) Copy() (interfaces.Stmt, error) {
|
|
copied := false
|
|
fn, err := obj.Func.Copy()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if fn != obj.Func { // must have been copied, or pointer would be same
|
|
copied = true
|
|
}
|
|
|
|
if !copied { // it's static
|
|
return obj, nil
|
|
}
|
|
return &StmtFunc{
|
|
Name: obj.Name,
|
|
Func: fn,
|
|
}, nil
|
|
}
|
|
|
|
// Ordering returns a graph of the scope ordering that represents the data flow.
|
|
// This can be used in SetScope so that it knows the correct order to run it in.
|
|
// We only really care about the consumers here, because the "produces" aspect
|
|
// of this resource is handled by the StmtProg Ordering function. This is
|
|
// because the "prog" allows out-of-order statements, therefore it solves this
|
|
// by running an early (second) loop through the program and peering into this
|
|
// Stmt and extracting the produced name.
|
|
func (obj *StmtFunc) Ordering(produces map[string]interfaces.Node) (*pgraph.Graph, map[interfaces.Node]string, error) {
|
|
graph, err := pgraph.NewGraph("ordering")
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
graph.AddVertex(obj)
|
|
|
|
// additional constraint...
|
|
edge := &pgraph.SimpleEdge{Name: "stmtfuncfunc"}
|
|
graph.AddEdge(obj.Func, obj, edge) // prod -> cons
|
|
|
|
cons := make(map[interfaces.Node]string)
|
|
|
|
g, c, err := obj.Func.Ordering(produces)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
graph.AddGraph(g) // add in the child graph
|
|
|
|
for k, v := range c { // c is consumes
|
|
x, exists := cons[k]
|
|
if exists && v != x {
|
|
return nil, nil, fmt.Errorf("consumed value is different, got `%+v`, expected `%+v`", x, v)
|
|
}
|
|
cons[k] = v // add to map
|
|
|
|
n, exists := produces[v]
|
|
if !exists {
|
|
continue
|
|
}
|
|
edge := &pgraph.SimpleEdge{Name: "stmtfunc"}
|
|
graph.AddEdge(n, k, edge)
|
|
}
|
|
|
|
return graph, cons, nil
|
|
}
|
|
|
|
// SetScope sets the scope of the child expression bound to it. It seems this is
|
|
// necessary in order to reach this, in particular in situations when a bound
|
|
// expression points to a previously bound expression.
|
|
func (obj *StmtFunc) SetScope(scope *interfaces.Scope) error {
|
|
return obj.Func.SetScope(scope, map[string]interfaces.Expr{})
|
|
}
|
|
|
|
// Unify returns the list of invariants that this node produces. It recursively
|
|
// calls Unify on any children elements that exist in the AST, and returns the
|
|
// collection to the caller.
|
|
func (obj *StmtFunc) Unify() ([]interfaces.Invariant, error) {
|
|
if obj.Name == "" {
|
|
return nil, fmt.Errorf("missing function name")
|
|
}
|
|
// I think the invariants should come in from ExprCall instead, because
|
|
// ExprCall operates on an instatiated copy of the contained ExprFunc
|
|
// which will have different pointers than what is seen here.
|
|
//return obj.Func.Unify() // nope!
|
|
return []interfaces.Invariant{}, nil
|
|
}
|
|
|
|
// Graph returns the reactive function graph which is expressed by this node. It
|
|
// includes any vertices produced by this node, and the appropriate edges to any
|
|
// vertices that are produced by its children. Nodes which fulfill the Expr
|
|
// interface directly produce vertices (and possible children) where as nodes
|
|
// that fulfill the Stmt interface do not produces vertices, where as their
|
|
// children might. This particular func statement adds its linked expression to
|
|
// the graph.
|
|
func (obj *StmtFunc) Graph() (*pgraph.Graph, error) {
|
|
//return obj.Func.Graph(nil) // nope!
|
|
return pgraph.NewGraph("stmtfunc") // do this in ExprCall instead
|
|
}
|
|
|
|
// 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) {
|
|
return interfaces.EmptyOutput(), nil
|
|
}
|
|
|
|
// StmtClass represents a user defined class. It's effectively a program body
|
|
// that can optionally take some parameterized inputs.
|
|
// TODO: We don't currently support defining polymorphic classes (eg: different
|
|
// signatures for the same class name) but it might be something to consider.
|
|
type StmtClass struct {
|
|
scope *interfaces.Scope // store for referencing this later
|
|
|
|
Name string
|
|
Args []*interfaces.Arg
|
|
Body interfaces.Stmt // probably a *StmtProg
|
|
}
|
|
|
|
// String returns a short representation of this statement.
|
|
func (obj *StmtClass) String() string {
|
|
return fmt.Sprintf("class(%s)", obj.Name)
|
|
}
|
|
|
|
// Apply is a general purpose iterator method that operates on any AST node. It
|
|
// is not used as the primary AST traversal function because it is less readable
|
|
// and easy to reason about than manually implementing traversal for each node.
|
|
// Nevertheless, it is a useful facility for operations that might only apply to
|
|
// a select number of node types, since they won't need extra noop iterators...
|
|
func (obj *StmtClass) Apply(fn func(interfaces.Node) error) error {
|
|
if err := obj.Body.Apply(fn); err != nil {
|
|
return err
|
|
}
|
|
return fn(obj)
|
|
}
|
|
|
|
// Init initializes this branch of the AST, and returns an error if it fails to
|
|
// validate.
|
|
func (obj *StmtClass) Init(data *interfaces.Data) error {
|
|
return obj.Body.Init(data)
|
|
}
|
|
|
|
// Interpolate returns a new node (aka a copy) once it has been expanded. This
|
|
// generally increases the size of the AST when it is used. It calls Interpolate
|
|
// on any child elements and builds the new node with those new node contents.
|
|
func (obj *StmtClass) Interpolate() (interfaces.Stmt, error) {
|
|
interpolated, err := obj.Body.Interpolate()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
args := obj.Args
|
|
if obj.Args == nil {
|
|
args = []*interfaces.Arg{}
|
|
}
|
|
|
|
return &StmtClass{
|
|
scope: obj.scope,
|
|
Name: obj.Name,
|
|
Args: args, // ensure this has length == 0 instead of nil
|
|
Body: interpolated,
|
|
}, nil
|
|
}
|
|
|
|
// Copy returns a light copy of this struct. Anything static will not be copied.
|
|
func (obj *StmtClass) Copy() (interfaces.Stmt, error) {
|
|
copied := false
|
|
body, err := obj.Body.Copy()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if body != obj.Body { // must have been copied, or pointer would be same
|
|
copied = true
|
|
}
|
|
|
|
args := obj.Args
|
|
if obj.Args == nil {
|
|
args = []*interfaces.Arg{}
|
|
}
|
|
|
|
if !copied { // it's static
|
|
return obj, nil
|
|
}
|
|
return &StmtClass{
|
|
scope: obj.scope,
|
|
Name: obj.Name,
|
|
Args: args, // ensure this has length == 0 instead of nil
|
|
Body: body,
|
|
}, nil
|
|
}
|
|
|
|
// Ordering returns a graph of the scope ordering that represents the data flow.
|
|
// This can be used in SetScope so that it knows the correct order to run it in.
|
|
// We only really care about the consumers here, because the "produces" aspect
|
|
// of this resource is handled by the StmtProg Ordering function. This is
|
|
// because the "prog" allows out-of-order statements, therefore it solves this
|
|
// by running an early (second) loop through the program and peering into this
|
|
// Stmt and extracting the produced name.
|
|
// TODO: Is Ordering in StmtInclude done properly and in sync with this?
|
|
// XXX: do we need to add ordering around named args, eg: obj.Args Name strings?
|
|
func (obj *StmtClass) Ordering(produces map[string]interfaces.Node) (*pgraph.Graph, map[interfaces.Node]string, error) {
|
|
graph, err := pgraph.NewGraph("ordering")
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
graph.AddVertex(obj)
|
|
|
|
// additional constraint...
|
|
edge := &pgraph.SimpleEdge{Name: "stmtclassbody"}
|
|
graph.AddEdge(obj.Body, obj, edge) // prod -> cons
|
|
|
|
cons := make(map[interfaces.Node]string)
|
|
|
|
g, c, err := obj.Body.Ordering(produces)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
graph.AddGraph(g) // add in the child graph
|
|
|
|
for k, v := range c { // c is consumes
|
|
x, exists := cons[k]
|
|
if exists && v != x {
|
|
return nil, nil, fmt.Errorf("consumed value is different, got `%+v`, expected `%+v`", x, v)
|
|
}
|
|
cons[k] = v // add to map
|
|
|
|
n, exists := produces[v]
|
|
if !exists {
|
|
continue
|
|
}
|
|
edge := &pgraph.SimpleEdge{Name: "stmtclass"}
|
|
graph.AddEdge(n, k, edge)
|
|
}
|
|
|
|
return graph, cons, nil
|
|
}
|
|
|
|
// SetScope sets the scope of the child expression bound to it. It seems this is
|
|
// necessary in order to reach this, in particular in situations when a bound
|
|
// expression points to a previously bound expression.
|
|
func (obj *StmtClass) SetScope(scope *interfaces.Scope) error {
|
|
if scope == nil {
|
|
scope = interfaces.EmptyScope()
|
|
}
|
|
obj.scope = scope // store for later
|
|
return nil
|
|
}
|
|
|
|
// Unify returns the list of invariants that this node produces. It recursively
|
|
// calls Unify on any children elements that exist in the AST, and returns the
|
|
// collection to the caller.
|
|
func (obj *StmtClass) Unify() ([]interfaces.Invariant, error) {
|
|
if obj.Name == "" {
|
|
return nil, fmt.Errorf("missing class name")
|
|
}
|
|
|
|
// TODO: do we need to add anything else here because of the obj.Args ?
|
|
return obj.Body.Unify()
|
|
}
|
|
|
|
// Graph returns the reactive function graph which is expressed by this node. It
|
|
// includes any vertices produced by this node, and the appropriate edges to any
|
|
// vertices that are produced by its children. Nodes which fulfill the Expr
|
|
// interface directly produce vertices (and possible children) where as nodes
|
|
// that fulfill the Stmt interface do not produces vertices, where as their
|
|
// children might. This particular func statement adds its linked expression to
|
|
// the graph.
|
|
func (obj *StmtClass) Graph() (*pgraph.Graph, error) {
|
|
return obj.Body.Graph()
|
|
}
|
|
|
|
// 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
|
|
// it can be called from the StmtInclude Output method.
|
|
func (obj *StmtClass) Output(table map[interfaces.Func]types.Value) (*interfaces.Output, error) {
|
|
return obj.Body.Output(table)
|
|
}
|
|
|
|
// StmtInclude causes a user defined class to get used. It's effectively the way
|
|
// to call a class except that it produces output instead of a value. Most of
|
|
// the interesting logic for classes happens here or in StmtProg.
|
|
type StmtInclude struct {
|
|
class *StmtClass // copy of class that we're using
|
|
orig *StmtInclude // original pointer to this
|
|
|
|
Name string
|
|
Args []interfaces.Expr
|
|
}
|
|
|
|
// String returns a short representation of this statement.
|
|
func (obj *StmtInclude) String() string {
|
|
return fmt.Sprintf("include(%s)", obj.Name)
|
|
}
|
|
|
|
// Apply is a general purpose iterator method that operates on any AST node. It
|
|
// is not used as the primary AST traversal function because it is less readable
|
|
// and easy to reason about than manually implementing traversal for each node.
|
|
// Nevertheless, it is a useful facility for operations that might only apply to
|
|
// a select number of node types, since they won't need extra noop iterators...
|
|
func (obj *StmtInclude) Apply(fn func(interfaces.Node) error) error {
|
|
// If the class exists, then descend into it, because at this point, the
|
|
// copy of the original class that is stored here, is the effective
|
|
// class that we care about for type unification, and everything else...
|
|
// It's not clear if this is needed, but it's probably nor harmful atm.
|
|
if obj.class != nil {
|
|
if err := obj.class.Apply(fn); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if obj.Args != nil {
|
|
for _, x := range obj.Args {
|
|
if err := x.Apply(fn); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
return fn(obj)
|
|
}
|
|
|
|
// Init initializes this branch of the AST, and returns an error if it fails to
|
|
// validate.
|
|
func (obj *StmtInclude) Init(data *interfaces.Data) error {
|
|
if obj.Args != nil {
|
|
for _, x := range obj.Args {
|
|
if err := x.Init(data); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Interpolate returns a new node (aka a copy) once it has been expanded. This
|
|
// generally increases the size of the AST when it is used. It calls Interpolate
|
|
// on any child elements and builds the new node with those new node contents.
|
|
func (obj *StmtInclude) Interpolate() (interfaces.Stmt, error) {
|
|
args := []interfaces.Expr{}
|
|
if obj.Args != nil {
|
|
for _, x := range obj.Args {
|
|
interpolated, err := x.Interpolate()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
args = append(args, interpolated)
|
|
}
|
|
}
|
|
|
|
orig := obj
|
|
if obj.orig != nil { // preserve the original pointer (the identifier!)
|
|
orig = obj.orig
|
|
}
|
|
return &StmtInclude{
|
|
//class: obj.class, // TODO: is this necessary?
|
|
orig: orig,
|
|
Name: obj.Name,
|
|
Args: args,
|
|
}, nil
|
|
}
|
|
|
|
// Copy returns a light copy of this struct. Anything static will not be copied.
|
|
func (obj *StmtInclude) Copy() (interfaces.Stmt, error) {
|
|
copied := false
|
|
args := []interfaces.Expr{}
|
|
if obj.Args != nil {
|
|
for _, x := range obj.Args {
|
|
cp, err := x.Copy()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if cp != x { // must have been copied, or pointer would be same
|
|
copied = true
|
|
}
|
|
args = append(args, cp)
|
|
}
|
|
}
|
|
|
|
// TODO: is this necessary? (I doubt it even gets used.)
|
|
orig := obj
|
|
if obj.orig != nil { // preserve the original pointer (the identifier!)
|
|
orig = obj.orig
|
|
copied = true // TODO: is this what we want?
|
|
}
|
|
|
|
if !copied { // it's static
|
|
return obj, nil
|
|
}
|
|
return &StmtInclude{
|
|
//class: obj.class, // TODO: is this necessary?
|
|
orig: orig,
|
|
Name: obj.Name,
|
|
Args: args,
|
|
}, nil
|
|
}
|
|
|
|
// Ordering returns a graph of the scope ordering that represents the data flow.
|
|
// This can be used in SetScope so that it knows the correct order to run it in.
|
|
// TODO: Is Ordering in StmtClass done properly and in sync with this?
|
|
func (obj *StmtInclude) Ordering(produces map[string]interfaces.Node) (*pgraph.Graph, map[interfaces.Node]string, error) {
|
|
graph, err := pgraph.NewGraph("ordering")
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
graph.AddVertex(obj)
|
|
|
|
if obj.Name == "" {
|
|
return nil, nil, fmt.Errorf("missing class name")
|
|
}
|
|
uid := classOrderingPrefix + obj.Name // ordering id
|
|
|
|
cons := make(map[interfaces.Node]string)
|
|
cons[obj] = uid
|
|
|
|
node, exists := produces[uid]
|
|
if exists {
|
|
edge := &pgraph.SimpleEdge{Name: "stmtinclude"}
|
|
graph.AddEdge(node, obj, edge) // prod -> cons
|
|
}
|
|
|
|
for _, node := range obj.Args {
|
|
g, c, err := node.Ordering(produces)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
graph.AddGraph(g) // add in the child graph
|
|
|
|
// additional constraint...
|
|
edge := &pgraph.SimpleEdge{Name: "stmtincludeargs1"}
|
|
graph.AddEdge(node, obj, edge) // prod -> cons
|
|
|
|
for k, v := range c { // c is consumes
|
|
x, exists := cons[k]
|
|
if exists && v != x {
|
|
return nil, nil, fmt.Errorf("consumed value is different, got `%+v`, expected `%+v`", x, v)
|
|
}
|
|
cons[k] = v // add to map
|
|
|
|
n, exists := produces[v]
|
|
if !exists {
|
|
continue
|
|
}
|
|
edge := &pgraph.SimpleEdge{Name: "stmtincludeargs2"}
|
|
graph.AddEdge(n, k, edge)
|
|
}
|
|
}
|
|
|
|
return graph, cons, nil
|
|
}
|
|
|
|
// SetScope stores the scope for use in this statement. Since this is the first
|
|
// location where recursion would play an important role, this also detects and
|
|
// handles the recursion scenario.
|
|
func (obj *StmtInclude) SetScope(scope *interfaces.Scope) error {
|
|
if scope == nil {
|
|
scope = interfaces.EmptyScope()
|
|
}
|
|
|
|
stmt, exists := scope.Classes[obj.Name]
|
|
if !exists {
|
|
return fmt.Errorf("class `%s` does not exist in this scope", obj.Name)
|
|
}
|
|
class, ok := stmt.(*StmtClass)
|
|
if !ok {
|
|
return fmt.Errorf("class scope of `%s` does not contain a class", obj.Name)
|
|
}
|
|
|
|
// is it even possible for the signatures to match?
|
|
if len(class.Args) != len(obj.Args) {
|
|
return fmt.Errorf("class `%s` expected %d args but got %d", obj.Name, len(class.Args), len(obj.Args))
|
|
}
|
|
|
|
if obj.class != nil {
|
|
// possible programming error
|
|
return fmt.Errorf("include already contains a class pointer")
|
|
}
|
|
|
|
// make sure to propagate the scope to our input args!
|
|
if obj.Args != nil {
|
|
for _, x := range obj.Args {
|
|
if err := x.SetScope(scope, map[string]interfaces.Expr{}); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
for i := len(scope.Chain) - 1; i >= 0; i-- { // reverse order
|
|
x, ok := scope.Chain[i].(*StmtInclude)
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
if x == obj.orig { // look for my original self
|
|
// scope chain found!
|
|
obj.class = class // same pointer, don't copy
|
|
return fmt.Errorf("recursive class `%s` found", obj.Name)
|
|
//return nil // if recursion was supported
|
|
}
|
|
}
|
|
|
|
// helper function to keep things more logical
|
|
cp := func(input *StmtClass) (*StmtClass, error) {
|
|
copied, err := input.Copy() // this does a light copy
|
|
if err != nil {
|
|
return nil, errwrap.Wrapf(err, "could not copy class")
|
|
}
|
|
class, ok := copied.(*StmtClass) // convert it back again
|
|
if !ok {
|
|
return nil, fmt.Errorf("copied class named `%s` is not a class", obj.Name)
|
|
}
|
|
return class, nil
|
|
}
|
|
|
|
copied, err := cp(class) // copy it for each use of the include
|
|
if err != nil {
|
|
return errwrap.Wrapf(err, "could not copy class")
|
|
}
|
|
obj.class = copied
|
|
|
|
// We start with the scope that the class had, and we augment it with
|
|
// our parameterized arg variables, which will be needed in that scope.
|
|
newScope := obj.class.scope.Copy()
|
|
// Add our args `include foo(42, "bar", true)` into the class scope.
|
|
for i, arg := range obj.class.Args { // copy
|
|
newScope.Variables[arg.Name] = &ExprTopLevel{
|
|
Definition: &ExprSingleton{
|
|
Definition: obj.Args[i],
|
|
|
|
mutex: &sync.Mutex{}, // TODO: call Init instead
|
|
},
|
|
CapturedScope: newScope,
|
|
}
|
|
|
|
}
|
|
|
|
// recursion detection
|
|
newScope.Chain = append(newScope.Chain, obj.orig) // add stmt to list
|
|
newScope.Classes[obj.Name] = copied // overwrite with new pointer
|
|
|
|
// NOTE: This would overwrite the scope that was previously set here,
|
|
// which would break the scoping rules. Scopes are propagated into
|
|
// class definitions, but not into include definitions. Which is why we
|
|
// need to use the original scope of the class as it was set as the
|
|
// basis for this scope, so that we overwrite it only with the arg
|
|
// changes.
|
|
if err := obj.class.Body.SetScope(newScope); err != nil {
|
|
return err
|
|
}
|
|
|
|
// no errors
|
|
return nil
|
|
}
|
|
|
|
// Unify returns the list of invariants that this node produces. It recursively
|
|
// calls Unify on any children elements that exist in the AST, and returns the
|
|
// collection to the caller.
|
|
func (obj *StmtInclude) Unify() ([]interfaces.Invariant, error) {
|
|
if obj.Name == "" {
|
|
return nil, fmt.Errorf("missing include name")
|
|
}
|
|
if obj.class == nil {
|
|
// possible programming error
|
|
return nil, fmt.Errorf("include doesn't contain a class pointer yet")
|
|
}
|
|
|
|
// is it even possible for the signatures to match?
|
|
if len(obj.class.Args) != len(obj.Args) {
|
|
return nil, fmt.Errorf("class `%s` expected %d args but got %d", obj.Name, len(obj.class.Args), len(obj.Args))
|
|
}
|
|
|
|
var invariants []interfaces.Invariant
|
|
|
|
// do this here because we skip doing it in the StmtProg parent
|
|
invars, err := obj.class.Unify()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
invariants = append(invariants, invars...)
|
|
|
|
// collect all the invariants of each sub-expression
|
|
for i, x := range obj.Args {
|
|
invars, err := x.Unify()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
invariants = append(invariants, invars...)
|
|
|
|
// TODO: are additional invariants required?
|
|
// add invariants between the args and the class
|
|
if typ := obj.class.Args[i].Type; typ != nil {
|
|
invar := &interfaces.EqualsInvariant{
|
|
Expr: obj.Args[i],
|
|
Type: typ, // type of arg
|
|
}
|
|
invariants = append(invariants, invar)
|
|
}
|
|
}
|
|
|
|
return invariants, nil
|
|
}
|
|
|
|
// Graph returns the reactive function graph which is expressed by this node. It
|
|
// includes any vertices produced by this node, and the appropriate edges to any
|
|
// vertices that are produced by its children. Nodes which fulfill the Expr
|
|
// interface directly produce vertices (and possible children) where as nodes
|
|
// that fulfill the Stmt interface do not produces vertices, where as their
|
|
// children might. This particular func statement adds its linked expression to
|
|
// the graph.
|
|
func (obj *StmtInclude) Graph() (*pgraph.Graph, error) {
|
|
graph, err := pgraph.NewGraph("include")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
g, err := obj.class.Graph()
|
|
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
|
|
// called by this Output function if they are needed to produce the output. The
|
|
// ultimate source of this output comes from the previously defined StmtClass
|
|
// which should be found in our scope.
|
|
func (obj *StmtInclude) Output(table map[interfaces.Func]types.Value) (*interfaces.Output, error) {
|
|
return obj.class.Output(table)
|
|
}
|
|
|
|
// StmtImport adds the exported scope definitions of a module into the current
|
|
// scope. It can be used anywhere a statement is allowed, and can even be nested
|
|
// inside a class definition. By convention, it is commonly used at the top of a
|
|
// file. As with any statement, it produces output, but that output is empty. To
|
|
// benefit from its inclusion, reference the scope definitions you want.
|
|
type StmtImport struct {
|
|
Name string
|
|
Alias string
|
|
}
|
|
|
|
// String returns a short representation of this statement.
|
|
func (obj *StmtImport) String() string {
|
|
return fmt.Sprintf("import(%s)", obj.Name)
|
|
}
|
|
|
|
// Apply is a general purpose iterator method that operates on any AST node. It
|
|
// is not used as the primary AST traversal function because it is less readable
|
|
// and easy to reason about than manually implementing traversal for each node.
|
|
// Nevertheless, it is a useful facility for operations that might only apply to
|
|
// a select number of node types, since they won't need extra noop iterators...
|
|
func (obj *StmtImport) Apply(fn func(interfaces.Node) error) error { return fn(obj) }
|
|
|
|
// Init initializes this branch of the AST, and returns an error if it fails to
|
|
// validate.
|
|
func (obj *StmtImport) Init(*interfaces.Data) error { return nil }
|
|
|
|
// Interpolate returns a new node (aka a copy) once it has been expanded. This
|
|
// generally increases the size of the AST when it is used. It calls Interpolate
|
|
// on any child elements and builds the new node with those new node contents.
|
|
func (obj *StmtImport) Interpolate() (interfaces.Stmt, error) {
|
|
return &StmtImport{
|
|
Name: obj.Name,
|
|
Alias: obj.Alias,
|
|
}, nil
|
|
}
|
|
|
|
// Copy returns a light copy of this struct. Anything static will not be copied.
|
|
func (obj *StmtImport) Copy() (interfaces.Stmt, error) {
|
|
return obj, nil // always static
|
|
}
|
|
|
|
// Ordering returns a graph of the scope ordering that represents the data flow.
|
|
// This can be used in SetScope so that it knows the correct order to run it in.
|
|
// Nothing special happens in this method, the import magic happens in StmtProg.
|
|
func (obj *StmtImport) Ordering(produces map[string]interfaces.Node) (*pgraph.Graph, map[interfaces.Node]string, error) {
|
|
graph, err := pgraph.NewGraph("ordering")
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
graph.AddVertex(obj)
|
|
|
|
cons := make(map[interfaces.Node]string)
|
|
return graph, cons, nil
|
|
}
|
|
|
|
// SetScope stores the scope for later use in this resource and its children,
|
|
// which it propagates this downwards to.
|
|
func (obj *StmtImport) SetScope(*interfaces.Scope) error { return nil }
|
|
|
|
// Unify returns the list of invariants that this node produces. It recursively
|
|
// calls Unify on any children elements that exist in the AST, and returns the
|
|
// collection to the caller.
|
|
func (obj *StmtImport) Unify() ([]interfaces.Invariant, error) {
|
|
if obj.Name == "" {
|
|
return nil, fmt.Errorf("missing import name")
|
|
}
|
|
|
|
return []interfaces.Invariant{}, nil
|
|
}
|
|
|
|
// Graph returns the reactive function graph which is expressed by this node. It
|
|
// includes any vertices produced by this node, and the appropriate edges to any
|
|
// vertices that are produced by its children. Nodes which fulfill the Expr
|
|
// interface directly produce vertices (and possible children) where as nodes
|
|
// that fulfill the Stmt interface do not produces vertices, where as their
|
|
// children might. This particular statement just returns an empty graph.
|
|
func (obj *StmtImport) Graph() (*pgraph.Graph, error) {
|
|
return pgraph.NewGraph("import") // empty graph
|
|
}
|
|
|
|
// 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
|
|
// called by this Output function if they are needed to produce the output. This
|
|
// import statement itself produces no output, as it is only used to populate
|
|
// the scope so that others can use that to produce values and output.
|
|
func (obj *StmtImport) Output(map[interfaces.Func]types.Value) (*interfaces.Output, error) {
|
|
return interfaces.EmptyOutput(), nil
|
|
}
|
|
|
|
// StmtComment is a representation of a comment. It is currently unused. It
|
|
// probably makes sense to make a third kind of Node (not a Stmt or an Expr) so
|
|
// that comments can still be part of the AST (for eventual automatic code
|
|
// formatting) but so that they can exist anywhere in the code. Currently these
|
|
// are dropped by the lexer.
|
|
type StmtComment struct {
|
|
Value string
|
|
}
|
|
|
|
// String returns a short representation of this statement.
|
|
func (obj *StmtComment) String() string {
|
|
return fmt.Sprintf("comment(%s)", obj.Value)
|
|
}
|
|
|
|
// Apply is a general purpose iterator method that operates on any AST node. It
|
|
// is not used as the primary AST traversal function because it is less readable
|
|
// and easy to reason about than manually implementing traversal for each node.
|
|
// Nevertheless, it is a useful facility for operations that might only apply to
|
|
// a select number of node types, since they won't need extra noop iterators...
|
|
func (obj *StmtComment) Apply(fn func(interfaces.Node) error) error { return fn(obj) }
|
|
|
|
// Init initializes this branch of the AST, and returns an error if it fails to
|
|
// validate.
|
|
func (obj *StmtComment) Init(*interfaces.Data) error {
|
|
return nil
|
|
}
|
|
|
|
// Interpolate returns a new node (aka a copy) once it has been expanded. This
|
|
// generally increases the size of the AST when it is used. It calls Interpolate
|
|
// on any child elements and builds the new node with those new node contents.
|
|
// Here it simply returns itself, as no interpolation is possible.
|
|
func (obj *StmtComment) Interpolate() (interfaces.Stmt, error) {
|
|
return &StmtComment{
|
|
Value: obj.Value,
|
|
}, nil
|
|
}
|
|
|
|
// Copy returns a light copy of this struct. Anything static will not be copied.
|
|
func (obj *StmtComment) Copy() (interfaces.Stmt, error) {
|
|
return obj, nil // always static
|
|
}
|
|
|
|
// Ordering returns a graph of the scope ordering that represents the data flow.
|
|
// This can be used in SetScope so that it knows the correct order to run it in.
|
|
func (obj *StmtComment) Ordering(produces map[string]interfaces.Node) (*pgraph.Graph, map[interfaces.Node]string, error) {
|
|
graph, err := pgraph.NewGraph("ordering")
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
graph.AddVertex(obj)
|
|
|
|
cons := make(map[interfaces.Node]string)
|
|
return graph, cons, nil
|
|
}
|
|
|
|
// SetScope does nothing for this struct, because it has no child nodes, and it
|
|
// does not need to know about the parent scope.
|
|
func (obj *StmtComment) SetScope(*interfaces.Scope) error { return nil }
|
|
|
|
// Unify returns the list of invariants that this node produces. It recursively
|
|
// calls Unify on any children elements that exist in the AST, and returns the
|
|
// collection to the caller.
|
|
func (obj *StmtComment) Unify() ([]interfaces.Invariant, error) {
|
|
return []interfaces.Invariant{}, nil
|
|
}
|
|
|
|
// Graph returns the reactive function graph which is expressed by this node. It
|
|
// includes any vertices produced by this node, and the appropriate edges to any
|
|
// vertices that are produced by its children. Nodes which fulfill the Expr
|
|
// interface directly produce vertices (and possible children) where as nodes
|
|
// that fulfill the Stmt interface do not produces vertices, where as their
|
|
// children might. This particular graph does nothing clever.
|
|
func (obj *StmtComment) Graph() (*pgraph.Graph, error) {
|
|
return pgraph.NewGraph("comment")
|
|
}
|
|
|
|
// Output for the comment statement produces no output.
|
|
func (obj *StmtComment) Output(map[interfaces.Func]types.Value) (*interfaces.Output, error) {
|
|
return interfaces.EmptyOutput(), nil
|
|
}
|
|
|
|
// ExprBool is a representation of a boolean.
|
|
type ExprBool struct {
|
|
scope *interfaces.Scope // store for referencing this later
|
|
|
|
V bool
|
|
}
|
|
|
|
// String returns a short representation of this expression.
|
|
func (obj *ExprBool) String() string { return fmt.Sprintf("bool(%t)", obj.V) }
|
|
|
|
// Apply is a general purpose iterator method that operates on any AST node. It
|
|
// is not used as the primary AST traversal function because it is less readable
|
|
// and easy to reason about than manually implementing traversal for each node.
|
|
// Nevertheless, it is a useful facility for operations that might only apply to
|
|
// a select number of node types, since they won't need extra noop iterators...
|
|
func (obj *ExprBool) Apply(fn func(interfaces.Node) error) error { return fn(obj) }
|
|
|
|
// Init initializes this branch of the AST, and returns an error if it fails to
|
|
// validate.
|
|
func (obj *ExprBool) Init(*interfaces.Data) error { return nil }
|
|
|
|
// Interpolate returns a new node (aka a copy) once it has been expanded. This
|
|
// generally increases the size of the AST when it is used. It calls Interpolate
|
|
// on any child elements and builds the new node with those new node contents.
|
|
// Here it simply returns itself, as no interpolation is possible.
|
|
func (obj *ExprBool) Interpolate() (interfaces.Expr, error) {
|
|
return &ExprBool{
|
|
scope: obj.scope,
|
|
V: obj.V,
|
|
}, nil
|
|
}
|
|
|
|
// Copy returns a light copy of this struct. Anything static will not be copied.
|
|
func (obj *ExprBool) Copy() (interfaces.Expr, error) {
|
|
return obj, nil // always static
|
|
}
|
|
|
|
// Ordering returns a graph of the scope ordering that represents the data flow.
|
|
// This can be used in SetScope so that it knows the correct order to run it in.
|
|
func (obj *ExprBool) Ordering(produces map[string]interfaces.Node) (*pgraph.Graph, map[interfaces.Node]string, error) {
|
|
graph, err := pgraph.NewGraph("ordering")
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
graph.AddVertex(obj)
|
|
|
|
cons := make(map[interfaces.Node]string)
|
|
return graph, cons, nil
|
|
}
|
|
|
|
// SetScope does nothing for this struct, because it has no child nodes, and it
|
|
// does not need to know about the parent scope. It does however store it for
|
|
// later possible use.
|
|
func (obj *ExprBool) SetScope(scope *interfaces.Scope, sctx map[string]interfaces.Expr) error {
|
|
if scope == nil {
|
|
scope = interfaces.EmptyScope()
|
|
}
|
|
obj.scope = scope
|
|
return nil
|
|
}
|
|
|
|
// SetType will make no changes if called here. It will error if anything other
|
|
// than a Bool is passed in, and doesn't need to be called for this expr to
|
|
// work.
|
|
func (obj *ExprBool) SetType(typ *types.Type) error { return types.TypeBool.Cmp(typ) }
|
|
|
|
// Type returns the type of this expression. This method always returns Bool
|
|
// here.
|
|
func (obj *ExprBool) Type() (*types.Type, error) { return types.TypeBool, nil }
|
|
|
|
// Unify returns the list of invariants that this node produces. It recursively
|
|
// calls Unify on any children elements that exist in the AST, and returns the
|
|
// collection to the caller.
|
|
func (obj *ExprBool) Unify() ([]interfaces.Invariant, error) {
|
|
invariants := []interfaces.Invariant{
|
|
&interfaces.EqualsInvariant{
|
|
Expr: obj,
|
|
Type: types.TypeBool,
|
|
},
|
|
}
|
|
return invariants, nil
|
|
}
|
|
|
|
// Func returns the reactive stream of values that this expression produces.
|
|
func (obj *ExprBool) Func() (interfaces.Func, error) {
|
|
return &structs.ConstFunc{
|
|
Value: &types.BoolValue{V: obj.V},
|
|
}, nil
|
|
}
|
|
|
|
// Graph returns the reactive function graph which is expressed by this node. It
|
|
// includes any vertices produced by this node, and the appropriate edges to any
|
|
// vertices that are produced by its children. Nodes which fulfill the Expr
|
|
// interface directly produce vertices (and possible children) where as nodes
|
|
// 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 *ExprBool) Graph(map[string]interfaces.Func) (*pgraph.Graph, interfaces.Func, error) {
|
|
graph, err := pgraph.NewGraph("bool")
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
function, err := obj.Func()
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
graph.AddVertex(function)
|
|
return graph, function, nil
|
|
}
|
|
|
|
// SetValue for a bool 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 *ExprBool) SetValue(value types.Value) error {
|
|
if err := types.TypeBool.Cmp(value.Type()); err != nil {
|
|
return err
|
|
}
|
|
// XXX: should we compare the incoming value with the stored value?
|
|
obj.V = value.Bool()
|
|
return nil
|
|
}
|
|
|
|
// Value returns the value of this expression in our type system. This will
|
|
// usually only be valid once the engine has run and values have been produced.
|
|
// This might get called speculatively (early) during unification to learn more.
|
|
// This particular value is always known since it is a constant.
|
|
func (obj *ExprBool) Value() (types.Value, error) {
|
|
return &types.BoolValue{
|
|
V: obj.V,
|
|
}, nil
|
|
}
|
|
|
|
// ExprStr is a representation of a string.
|
|
type ExprStr struct {
|
|
data *interfaces.Data
|
|
scope *interfaces.Scope // store for referencing this later
|
|
|
|
V string // value of this string
|
|
}
|
|
|
|
// String returns a short representation of this expression.
|
|
func (obj *ExprStr) String() string { return fmt.Sprintf("str(%s)", strconv.Quote(obj.V)) }
|
|
|
|
// Apply is a general purpose iterator method that operates on any AST node. It
|
|
// is not used as the primary AST traversal function because it is less readable
|
|
// and easy to reason about than manually implementing traversal for each node.
|
|
// Nevertheless, it is a useful facility for operations that might only apply to
|
|
// a select number of node types, since they won't need extra noop iterators...
|
|
func (obj *ExprStr) Apply(fn func(interfaces.Node) error) error { return fn(obj) }
|
|
|
|
// Init initializes this branch of the AST, and returns an error if it fails to
|
|
// validate.
|
|
func (obj *ExprStr) Init(data *interfaces.Data) error {
|
|
obj.data = data
|
|
return nil
|
|
}
|
|
|
|
// Interpolate returns a new node (aka a copy) once it has been expanded. This
|
|
// generally increases the size of the AST when it is used. It calls Interpolate
|
|
// on any child elements and builds the new node with those new node contents.
|
|
// Here it attempts to expand the string if there are any internal variables
|
|
// which need interpolation. If any are found, it returns a larger AST which has
|
|
// a function which returns a string as its root. Otherwise it returns itself.
|
|
func (obj *ExprStr) Interpolate() (interfaces.Expr, error) {
|
|
pos := &interfaces.Pos{
|
|
// column/line number, starting at 1
|
|
//Column: -1, // TODO
|
|
//Line: -1, // TODO
|
|
//Filename: "", // optional source filename, if known
|
|
}
|
|
|
|
data := &interfaces.Data{
|
|
// TODO: add missing fields here if/when needed
|
|
Fs: obj.data.Fs,
|
|
FsURI: obj.data.FsURI,
|
|
Base: obj.data.Base,
|
|
Files: obj.data.Files,
|
|
Imports: obj.data.Imports,
|
|
Metadata: obj.data.Metadata,
|
|
Modules: obj.data.Modules,
|
|
|
|
LexParser: obj.data.LexParser,
|
|
Downloader: obj.data.Downloader,
|
|
StrInterpolater: obj.data.StrInterpolater,
|
|
//World: obj.data.World, // TODO: do we need this?
|
|
|
|
Prefix: obj.data.Prefix,
|
|
Debug: obj.data.Debug,
|
|
Logf: func(format string, v ...interface{}) {
|
|
obj.data.Logf("interpolate: "+format, v...)
|
|
},
|
|
}
|
|
|
|
result, err := obj.data.StrInterpolater(obj.V, pos, data)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if result == nil {
|
|
return &ExprStr{
|
|
data: obj.data,
|
|
scope: obj.scope,
|
|
V: obj.V,
|
|
}, nil
|
|
}
|
|
// we got something, overwrite the existing static str
|
|
return result, nil // replacement
|
|
}
|
|
|
|
// Copy returns a light copy of this struct. Anything static will not be copied.
|
|
func (obj *ExprStr) Copy() (interfaces.Expr, error) {
|
|
return obj, nil // always static
|
|
}
|
|
|
|
// Ordering returns a graph of the scope ordering that represents the data flow.
|
|
// This can be used in SetScope so that it knows the correct order to run it in.
|
|
// This Ordering method runs *after* the Interpolate method, so if this
|
|
// originally would have expanded into a bigger AST, but the time Ordering runs,
|
|
// this is only used on a raw string expression. As a result, it doesn't need to
|
|
// build a map of consumed nodes, because none are consumed. The returned graph
|
|
// is empty!
|
|
func (obj *ExprStr) Ordering(produces map[string]interfaces.Node) (*pgraph.Graph, map[interfaces.Node]string, error) {
|
|
graph, err := pgraph.NewGraph("ordering")
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
graph.AddVertex(obj)
|
|
|
|
cons := make(map[interfaces.Node]string)
|
|
return graph, cons, nil
|
|
}
|
|
|
|
// SetScope does nothing for this struct, because it has no child nodes, and it
|
|
// does not need to know about the parent scope. It does however store it for
|
|
// later possible use.
|
|
func (obj *ExprStr) SetScope(scope *interfaces.Scope, sctx map[string]interfaces.Expr) error {
|
|
if scope == nil {
|
|
scope = interfaces.EmptyScope()
|
|
}
|
|
obj.scope = scope
|
|
return nil
|
|
}
|
|
|
|
// SetType will make no changes if called here. It will error if anything other
|
|
// than an Str is passed in, and doesn't need to be called for this expr to
|
|
// work.
|
|
func (obj *ExprStr) SetType(typ *types.Type) error { return types.TypeStr.Cmp(typ) }
|
|
|
|
// Type returns the type of this expression. This method always returns Str
|
|
// here.
|
|
func (obj *ExprStr) Type() (*types.Type, error) { return types.TypeStr, nil }
|
|
|
|
// Unify returns the list of invariants that this node produces. It recursively
|
|
// calls Unify on any children elements that exist in the AST, and returns the
|
|
// collection to the caller.
|
|
func (obj *ExprStr) Unify() ([]interfaces.Invariant, error) {
|
|
invariants := []interfaces.Invariant{
|
|
&interfaces.EqualsInvariant{
|
|
Expr: obj, // unique id for this expression (a pointer)
|
|
Type: types.TypeStr,
|
|
},
|
|
}
|
|
return invariants, nil
|
|
}
|
|
|
|
// Func returns the reactive stream of values that this expression produces.
|
|
func (obj *ExprStr) Func() (interfaces.Func, error) {
|
|
return &structs.ConstFunc{
|
|
Value: &types.StrValue{V: obj.V},
|
|
}, nil
|
|
}
|
|
|
|
// Graph returns the reactive function graph which is expressed by this node. It
|
|
// includes any vertices produced by this node, and the appropriate edges to any
|
|
// vertices that are produced by its children. Nodes which fulfill the Expr
|
|
// interface directly produce vertices (and possible children) where as nodes
|
|
// 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 *ExprStr) Graph(map[string]interfaces.Func) (*pgraph.Graph, interfaces.Func, error) {
|
|
graph, err := pgraph.NewGraph("str")
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
function, err := obj.Func()
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
graph.AddVertex(function)
|
|
return graph, function, nil
|
|
}
|
|
|
|
// SetValue for an str 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 *ExprStr) SetValue(value types.Value) error {
|
|
if err := types.TypeStr.Cmp(value.Type()); err != nil {
|
|
return err
|
|
}
|
|
obj.V = value.Str()
|
|
return nil
|
|
}
|
|
|
|
// Value returns the value of this expression in our type system. This will
|
|
// usually only be valid once the engine has run and values have been produced.
|
|
// This might get called speculatively (early) during unification to learn more.
|
|
// This particular value is always known since it is a constant.
|
|
func (obj *ExprStr) Value() (types.Value, error) {
|
|
return &types.StrValue{
|
|
V: obj.V,
|
|
}, nil
|
|
}
|
|
|
|
// ExprInt is a representation of an int.
|
|
type ExprInt struct {
|
|
scope *interfaces.Scope // store for referencing this later
|
|
|
|
V int64
|
|
}
|
|
|
|
// String returns a short representation of this expression.
|
|
func (obj *ExprInt) String() string { return fmt.Sprintf("int(%d)", obj.V) }
|
|
|
|
// Apply is a general purpose iterator method that operates on any AST node. It
|
|
// is not used as the primary AST traversal function because it is less readable
|
|
// and easy to reason about than manually implementing traversal for each node.
|
|
// Nevertheless, it is a useful facility for operations that might only apply to
|
|
// a select number of node types, since they won't need extra noop iterators...
|
|
func (obj *ExprInt) Apply(fn func(interfaces.Node) error) error { return fn(obj) }
|
|
|
|
// Init initializes this branch of the AST, and returns an error if it fails to
|
|
// validate.
|
|
func (obj *ExprInt) Init(*interfaces.Data) error { return nil }
|
|
|
|
// Interpolate returns a new node (aka a copy) once it has been expanded. This
|
|
// generally increases the size of the AST when it is used. It calls Interpolate
|
|
// on any child elements and builds the new node with those new node contents.
|
|
// Here it simply returns itself, as no interpolation is possible.
|
|
func (obj *ExprInt) Interpolate() (interfaces.Expr, error) {
|
|
return &ExprInt{
|
|
scope: obj.scope,
|
|
V: obj.V,
|
|
}, nil
|
|
}
|
|
|
|
// Copy returns a light copy of this struct. Anything static will not be copied.
|
|
func (obj *ExprInt) Copy() (interfaces.Expr, error) {
|
|
return obj, nil // always static
|
|
}
|
|
|
|
// Ordering returns a graph of the scope ordering that represents the data flow.
|
|
// This can be used in SetScope so that it knows the correct order to run it in.
|
|
func (obj *ExprInt) Ordering(produces map[string]interfaces.Node) (*pgraph.Graph, map[interfaces.Node]string, error) {
|
|
graph, err := pgraph.NewGraph("ordering")
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
graph.AddVertex(obj)
|
|
|
|
cons := make(map[interfaces.Node]string)
|
|
return graph, cons, nil
|
|
}
|
|
|
|
// SetScope does nothing for this struct, because it has no child nodes, and it
|
|
// does not need to know about the parent scope. It does however store it for
|
|
// later possible use.
|
|
func (obj *ExprInt) SetScope(scope *interfaces.Scope, sctx map[string]interfaces.Expr) error {
|
|
if scope == nil {
|
|
scope = interfaces.EmptyScope()
|
|
}
|
|
obj.scope = scope
|
|
return nil
|
|
}
|
|
|
|
// SetType will make no changes if called here. It will error if anything other
|
|
// than an Int is passed in, and doesn't need to be called for this expr to
|
|
// work.
|
|
func (obj *ExprInt) SetType(typ *types.Type) error { return types.TypeInt.Cmp(typ) }
|
|
|
|
// Type returns the type of this expression. This method always returns Int
|
|
// here.
|
|
func (obj *ExprInt) Type() (*types.Type, error) { return types.TypeInt, nil }
|
|
|
|
// Unify returns the list of invariants that this node produces. It recursively
|
|
// calls Unify on any children elements that exist in the AST, and returns the
|
|
// collection to the caller.
|
|
func (obj *ExprInt) Unify() ([]interfaces.Invariant, error) {
|
|
invariants := []interfaces.Invariant{
|
|
&interfaces.EqualsInvariant{
|
|
Expr: obj,
|
|
Type: types.TypeInt,
|
|
},
|
|
}
|
|
return invariants, nil
|
|
}
|
|
|
|
// Func returns the reactive stream of values that this expression produces.
|
|
func (obj *ExprInt) Func() (interfaces.Func, error) {
|
|
return &structs.ConstFunc{
|
|
Value: &types.IntValue{V: obj.V},
|
|
}, nil
|
|
}
|
|
|
|
// Graph returns the reactive function graph which is expressed by this node. It
|
|
// includes any vertices produced by this node, and the appropriate edges to any
|
|
// vertices that are produced by its children. Nodes which fulfill the Expr
|
|
// interface directly produce vertices (and possible children) where as nodes
|
|
// 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 *ExprInt) Graph(map[string]interfaces.Func) (*pgraph.Graph, interfaces.Func, error) {
|
|
graph, err := pgraph.NewGraph("int")
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
function, err := obj.Func()
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
graph.AddVertex(function)
|
|
return graph, function, nil
|
|
}
|
|
|
|
// SetValue for an int 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 *ExprInt) SetValue(value types.Value) error {
|
|
if err := types.TypeInt.Cmp(value.Type()); err != nil {
|
|
return err
|
|
}
|
|
obj.V = value.Int()
|
|
return nil
|
|
}
|
|
|
|
// Value returns the value of this expression in our type system. This will
|
|
// usually only be valid once the engine has run and values have been produced.
|
|
// This might get called speculatively (early) during unification to learn more.
|
|
// This particular value is always known since it is a constant.
|
|
func (obj *ExprInt) Value() (types.Value, error) {
|
|
return &types.IntValue{
|
|
V: obj.V,
|
|
}, nil
|
|
}
|
|
|
|
// ExprFloat is a representation of a float.
|
|
type ExprFloat struct {
|
|
scope *interfaces.Scope // store for referencing this later
|
|
|
|
V float64
|
|
}
|
|
|
|
// String returns a short representation of this expression.
|
|
func (obj *ExprFloat) String() string {
|
|
return fmt.Sprintf("float(%g)", obj.V) // TODO: %f instead?
|
|
}
|
|
|
|
// Apply is a general purpose iterator method that operates on any AST node. It
|
|
// is not used as the primary AST traversal function because it is less readable
|
|
// and easy to reason about than manually implementing traversal for each node.
|
|
// Nevertheless, it is a useful facility for operations that might only apply to
|
|
// a select number of node types, since they won't need extra noop iterators...
|
|
func (obj *ExprFloat) Apply(fn func(interfaces.Node) error) error { return fn(obj) }
|
|
|
|
// Init initializes this branch of the AST, and returns an error if it fails to
|
|
// validate.
|
|
func (obj *ExprFloat) Init(*interfaces.Data) error { return nil }
|
|
|
|
// Interpolate returns a new node (aka a copy) once it has been expanded. This
|
|
// generally increases the size of the AST when it is used. It calls Interpolate
|
|
// on any child elements and builds the new node with those new node contents.
|
|
// Here it simply returns itself, as no interpolation is possible.
|
|
func (obj *ExprFloat) Interpolate() (interfaces.Expr, error) {
|
|
return &ExprFloat{
|
|
scope: obj.scope,
|
|
V: obj.V,
|
|
}, nil
|
|
}
|
|
|
|
// Copy returns a light copy of this struct. Anything static will not be copied.
|
|
func (obj *ExprFloat) Copy() (interfaces.Expr, error) {
|
|
return obj, nil // always static
|
|
}
|
|
|
|
// Ordering returns a graph of the scope ordering that represents the data flow.
|
|
// This can be used in SetScope so that it knows the correct order to run it in.
|
|
func (obj *ExprFloat) Ordering(produces map[string]interfaces.Node) (*pgraph.Graph, map[interfaces.Node]string, error) {
|
|
graph, err := pgraph.NewGraph("ordering")
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
graph.AddVertex(obj)
|
|
|
|
cons := make(map[interfaces.Node]string)
|
|
return graph, cons, nil
|
|
}
|
|
|
|
// SetScope does nothing for this struct, because it has no child nodes, and it
|
|
// does not need to know about the parent scope. It does however store it for
|
|
// later possible use.
|
|
func (obj *ExprFloat) SetScope(scope *interfaces.Scope, sctx map[string]interfaces.Expr) error {
|
|
if scope == nil {
|
|
scope = interfaces.EmptyScope()
|
|
}
|
|
obj.scope = scope
|
|
return nil
|
|
}
|
|
|
|
// SetType will make no changes if called here. It will error if anything other
|
|
// than a Float is passed in, and doesn't need to be called for this expr to
|
|
// work.
|
|
func (obj *ExprFloat) SetType(typ *types.Type) error { return types.TypeFloat.Cmp(typ) }
|
|
|
|
// Type returns the type of this expression. This method always returns Float
|
|
// here.
|
|
func (obj *ExprFloat) Type() (*types.Type, error) { return types.TypeFloat, nil }
|
|
|
|
// Unify returns the list of invariants that this node produces. It recursively
|
|
// calls Unify on any children elements that exist in the AST, and returns the
|
|
// collection to the caller.
|
|
func (obj *ExprFloat) Unify() ([]interfaces.Invariant, error) {
|
|
invariants := []interfaces.Invariant{
|
|
&interfaces.EqualsInvariant{
|
|
Expr: obj,
|
|
Type: types.TypeFloat,
|
|
},
|
|
}
|
|
return invariants, nil
|
|
}
|
|
|
|
// Func returns the reactive stream of values that this expression produces.
|
|
func (obj *ExprFloat) Func() (interfaces.Func, error) {
|
|
return &structs.ConstFunc{
|
|
Value: &types.FloatValue{V: obj.V},
|
|
}, nil
|
|
}
|
|
|
|
// Graph returns the reactive function graph which is expressed by this node. It
|
|
// includes any vertices produced by this node, and the appropriate edges to any
|
|
// vertices that are produced by its children. Nodes which fulfill the Expr
|
|
// interface directly produce vertices (and possible children) where as nodes
|
|
// 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 *ExprFloat) Graph(map[string]interfaces.Func) (*pgraph.Graph, interfaces.Func, error) {
|
|
graph, err := pgraph.NewGraph("float")
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
function, err := obj.Func()
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
graph.AddVertex(function)
|
|
return graph, function, nil
|
|
}
|
|
|
|
// SetValue for a float 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 *ExprFloat) SetValue(value types.Value) error {
|
|
if err := types.TypeFloat.Cmp(value.Type()); err != nil {
|
|
return err
|
|
}
|
|
obj.V = value.Float()
|
|
return nil
|
|
}
|
|
|
|
// Value returns the value of this expression in our type system. This will
|
|
// usually only be valid once the engine has run and values have been produced.
|
|
// This might get called speculatively (early) during unification to learn more.
|
|
// This particular value is always known since it is a constant.
|
|
func (obj *ExprFloat) Value() (types.Value, error) {
|
|
return &types.FloatValue{
|
|
V: obj.V,
|
|
}, nil
|
|
}
|
|
|
|
// ExprList is a representation of a list.
|
|
type ExprList struct {
|
|
scope *interfaces.Scope // store for referencing this later
|
|
typ *types.Type
|
|
|
|
//Elements []*ExprListElement
|
|
Elements []interfaces.Expr
|
|
}
|
|
|
|
// String returns a short representation of this expression.
|
|
func (obj *ExprList) String() string {
|
|
var s []string
|
|
for _, x := range obj.Elements {
|
|
s = append(s, x.String())
|
|
}
|
|
return fmt.Sprintf("list(%s)", strings.Join(s, ", "))
|
|
}
|
|
|
|
// Apply is a general purpose iterator method that operates on any AST node. It
|
|
// is not used as the primary AST traversal function because it is less readable
|
|
// and easy to reason about than manually implementing traversal for each node.
|
|
// Nevertheless, it is a useful facility for operations that might only apply to
|
|
// a select number of node types, since they won't need extra noop iterators...
|
|
func (obj *ExprList) Apply(fn func(interfaces.Node) error) error {
|
|
for _, x := range obj.Elements {
|
|
if err := x.Apply(fn); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return fn(obj)
|
|
}
|
|
|
|
// Init initializes this branch of the AST, and returns an error if it fails to
|
|
// validate.
|
|
func (obj *ExprList) Init(data *interfaces.Data) error {
|
|
for _, x := range obj.Elements {
|
|
if err := x.Init(data); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Interpolate returns a new node (aka a copy) once it has been expanded. This
|
|
// generally increases the size of the AST when it is used. It calls Interpolate
|
|
// on any child elements and builds the new node with those new node contents.
|
|
func (obj *ExprList) Interpolate() (interfaces.Expr, error) {
|
|
elements := []interfaces.Expr{}
|
|
for _, x := range obj.Elements {
|
|
interpolated, err := x.Interpolate()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
elements = append(elements, interpolated)
|
|
}
|
|
return &ExprList{
|
|
scope: obj.scope,
|
|
typ: obj.typ,
|
|
Elements: elements,
|
|
}, nil
|
|
}
|
|
|
|
// Copy returns a light copy of this struct. Anything static will not be copied.
|
|
func (obj *ExprList) Copy() (interfaces.Expr, error) {
|
|
copied := false
|
|
elements := []interfaces.Expr{}
|
|
for _, x := range obj.Elements {
|
|
cp, err := x.Copy()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if cp != x { // must have been copied, or pointer would be same
|
|
copied = true
|
|
}
|
|
elements = append(elements, cp)
|
|
}
|
|
|
|
if !copied { // it's static
|
|
return obj, nil
|
|
}
|
|
return &ExprList{
|
|
scope: obj.scope,
|
|
typ: obj.typ,
|
|
Elements: elements,
|
|
}, nil
|
|
}
|
|
|
|
// Ordering returns a graph of the scope ordering that represents the data flow.
|
|
// This can be used in SetScope so that it knows the correct order to run it in.
|
|
func (obj *ExprList) Ordering(produces map[string]interfaces.Node) (*pgraph.Graph, map[interfaces.Node]string, error) {
|
|
graph, err := pgraph.NewGraph("ordering")
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
graph.AddVertex(obj)
|
|
|
|
cons := make(map[interfaces.Node]string)
|
|
|
|
for _, node := range obj.Elements {
|
|
g, c, err := node.Ordering(produces)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
graph.AddGraph(g) // add in the child graph
|
|
|
|
// additional constraint...
|
|
edge := &pgraph.SimpleEdge{Name: "exprlistelement"}
|
|
graph.AddEdge(node, obj, edge) // prod -> cons
|
|
|
|
for k, v := range c { // c is consumes
|
|
x, exists := cons[k]
|
|
if exists && v != x {
|
|
return nil, nil, fmt.Errorf("consumed value is different, got `%+v`, expected `%+v`", x, v)
|
|
}
|
|
cons[k] = v // add to map
|
|
|
|
n, exists := produces[v]
|
|
if !exists {
|
|
continue
|
|
}
|
|
edge := &pgraph.SimpleEdge{Name: "exprlist"}
|
|
graph.AddEdge(n, k, edge)
|
|
}
|
|
}
|
|
|
|
return graph, cons, nil
|
|
}
|
|
|
|
// SetScope stores the scope for later use in this resource and its children,
|
|
// which it propagates this downwards to.
|
|
func (obj *ExprList) SetScope(scope *interfaces.Scope, sctx map[string]interfaces.Expr) error {
|
|
if scope == nil {
|
|
scope = interfaces.EmptyScope()
|
|
}
|
|
obj.scope = scope
|
|
|
|
for _, x := range obj.Elements {
|
|
if err := x.SetScope(scope, sctx); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// SetType is used to set the type of this expression once it is known. This
|
|
// usually happens during type unification, but it can also happen during
|
|
// parsing if a type is specified explicitly. Since types are static and don't
|
|
// change on expressions, if you attempt to set a different type than what has
|
|
// previously been set (when not initially known) this will error.
|
|
func (obj *ExprList) SetType(typ *types.Type) error {
|
|
// TODO: should we ensure this is set to a KindList ?
|
|
if obj.typ != nil {
|
|
return obj.typ.Cmp(typ) // if not set, ensure it doesn't change
|
|
}
|
|
obj.typ = typ // set
|
|
return nil
|
|
}
|
|
|
|
// Type returns the type of this expression.
|
|
func (obj *ExprList) Type() (*types.Type, error) {
|
|
var typ *types.Type
|
|
var err error
|
|
for i, expr := range obj.Elements {
|
|
etyp, e := expr.Type()
|
|
if e != nil {
|
|
err = errwrap.Wrapf(e, "list index `%d` did not return a type", i)
|
|
break
|
|
}
|
|
if typ == nil {
|
|
typ = etyp
|
|
}
|
|
if e := typ.Cmp(etyp); e != nil {
|
|
err = errwrap.Wrapf(e, "list elements have different types")
|
|
break
|
|
}
|
|
}
|
|
if err == nil && obj.typ == nil && len(obj.Elements) > 0 {
|
|
return &types.Type{ // speculate!
|
|
Kind: types.KindList,
|
|
Val: typ,
|
|
}, nil
|
|
}
|
|
|
|
if obj.typ == nil {
|
|
if err != nil {
|
|
return nil, errwrap.Wrapf(interfaces.ErrTypeCurrentlyUnknown, err.Error())
|
|
}
|
|
return nil, interfaces.ErrTypeCurrentlyUnknown
|
|
}
|
|
return obj.typ, nil
|
|
}
|
|
|
|
// Unify returns the list of invariants that this node produces. It recursively
|
|
// calls Unify on any children elements that exist in the AST, and returns the
|
|
// collection to the caller.
|
|
func (obj *ExprList) Unify() ([]interfaces.Invariant, error) {
|
|
var invariants []interfaces.Invariant
|
|
|
|
// if this was set explicitly by the parser
|
|
if obj.typ != nil {
|
|
invar := &interfaces.EqualsInvariant{
|
|
Expr: obj,
|
|
Type: obj.typ,
|
|
}
|
|
invariants = append(invariants, invar)
|
|
}
|
|
|
|
// collect all the invariants of each sub-expression
|
|
for _, x := range obj.Elements {
|
|
invars, err := x.Unify()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
invariants = append(invariants, invars...)
|
|
}
|
|
|
|
// each element must be equal to each other
|
|
if len(obj.Elements) > 1 {
|
|
invariant := &interfaces.EqualityInvariantList{
|
|
Exprs: obj.Elements,
|
|
}
|
|
invariants = append(invariants, invariant)
|
|
}
|
|
|
|
// we should be type list of (type of element)
|
|
if len(obj.Elements) > 0 {
|
|
invariant := &interfaces.EqualityWrapListInvariant{
|
|
Expr1: obj, // unique id for this expression (a pointer)
|
|
Expr2Val: obj.Elements[0],
|
|
}
|
|
invariants = append(invariants, invariant)
|
|
}
|
|
|
|
// make sure this empty list gets an element type somehow
|
|
if len(obj.Elements) == 0 {
|
|
invariant := &interfaces.AnyInvariant{
|
|
Expr: obj,
|
|
}
|
|
invariants = append(invariants, invariant)
|
|
|
|
// build a placeholder expr to represent a contained element...
|
|
exprAny := &interfaces.ExprAny{}
|
|
invars, err := exprAny.Unify()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
invariants = append(invariants, invars...)
|
|
|
|
// FIXME: instead of using `ExprAny`, we could actually teach
|
|
// our unification engine to ensure that our expr kind is list,
|
|
// eg:
|
|
//&interfaces.EqualityKindInvariant{
|
|
// Expr1: obj,
|
|
// Kind: types.KindList,
|
|
//}
|
|
invar := &interfaces.EqualityWrapListInvariant{
|
|
Expr1: obj,
|
|
Expr2Val: exprAny, // hack
|
|
}
|
|
invariants = append(invariants, invar)
|
|
}
|
|
|
|
return invariants, nil
|
|
}
|
|
|
|
// Func returns the reactive stream of values that this expression produces.
|
|
func (obj *ExprList) Func() (interfaces.Func, error) {
|
|
typ, err := obj.Type()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// composite func (list, map, struct)
|
|
return &structs.CompositeFunc{
|
|
Type: typ,
|
|
Len: len(obj.Elements),
|
|
}, nil
|
|
}
|
|
|
|
// Graph returns the reactive function graph which is expressed by this node. It
|
|
// includes any vertices produced by this node, and the appropriate edges to any
|
|
// vertices that are produced by its children. Nodes which fulfill the Expr
|
|
// interface directly produce vertices (and possible children) where as nodes
|
|
// 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, and
|
|
// the edges from all of the child graphs to this.
|
|
func (obj *ExprList) Graph(env map[string]interfaces.Func) (*pgraph.Graph, interfaces.Func, error) {
|
|
graph, err := pgraph.NewGraph("list")
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
function, err := obj.Func()
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
graph.AddVertex(function)
|
|
|
|
// each list element needs to point to the final list expression
|
|
for index, x := range obj.Elements { // list elements in order
|
|
g, f, err := x.Graph(env)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
graph.AddGraph(g)
|
|
|
|
fieldName := fmt.Sprintf("%d", index) // argNames as integers!
|
|
edge := &interfaces.FuncEdge{Args: []string{fieldName}}
|
|
graph.AddEdge(f, function, edge) // element -> list
|
|
}
|
|
|
|
return graph, function, nil
|
|
}
|
|
|
|
// SetValue here is a no-op, because algorithmically when this is called from
|
|
// the func engine, the child elements (the list elements) will have had this
|
|
// done to them first, and as such when we try and retrieve the set value from
|
|
// this expression by calling `Value`, it will build it from scratch!
|
|
func (obj *ExprList) SetValue(value types.Value) error {
|
|
if err := obj.typ.Cmp(value.Type()); err != nil {
|
|
return err
|
|
}
|
|
// noop!
|
|
//obj.V = value
|
|
return nil
|
|
}
|
|
|
|
// Value returns the value of this expression in our type system. This will
|
|
// usually only be valid once the engine has run and values have been produced.
|
|
// This might get called speculatively (early) during unification to learn more.
|
|
func (obj *ExprList) Value() (types.Value, error) {
|
|
values := []types.Value{}
|
|
var typ *types.Type
|
|
|
|
for i, expr := range obj.Elements {
|
|
etyp, err := expr.Type()
|
|
if err != nil {
|
|
return nil, errwrap.Wrapf(err, "list index `%d` did not return a type", i)
|
|
}
|
|
if typ == nil {
|
|
typ = etyp
|
|
}
|
|
if err := typ.Cmp(etyp); err != nil {
|
|
return nil, errwrap.Wrapf(err, "list elements have different types")
|
|
}
|
|
|
|
value, err := expr.Value()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if value == nil {
|
|
return nil, fmt.Errorf("value for list index `%d` was nil", i)
|
|
}
|
|
values = append(values, value)
|
|
}
|
|
if len(obj.Elements) > 0 {
|
|
t := &types.Type{
|
|
Kind: types.KindList,
|
|
Val: typ,
|
|
}
|
|
// Run SetType to ensure type is consistent with what we found,
|
|
// which is an easy way to ensure the Cmp passes as expected...
|
|
if err := obj.SetType(t); err != nil {
|
|
return nil, errwrap.Wrapf(err, "type did not match expected!")
|
|
}
|
|
}
|
|
|
|
return &types.ListValue{
|
|
T: obj.typ,
|
|
V: values,
|
|
}, nil
|
|
}
|
|
|
|
// ExprMap is a representation of a (dictionary) map.
|
|
type ExprMap struct {
|
|
scope *interfaces.Scope // store for referencing this later
|
|
typ *types.Type
|
|
|
|
KVs []*ExprMapKV
|
|
}
|
|
|
|
// String returns a short representation of this expression.
|
|
func (obj *ExprMap) String() string {
|
|
var s []string
|
|
for _, x := range obj.KVs {
|
|
s = append(s, fmt.Sprintf("%s: %s", x.Key.String(), x.Val.String()))
|
|
}
|
|
return fmt.Sprintf("map(%s)", strings.Join(s, ", "))
|
|
}
|
|
|
|
// Apply is a general purpose iterator method that operates on any AST node. It
|
|
// is not used as the primary AST traversal function because it is less readable
|
|
// and easy to reason about than manually implementing traversal for each node.
|
|
// Nevertheless, it is a useful facility for operations that might only apply to
|
|
// a select number of node types, since they won't need extra noop iterators...
|
|
func (obj *ExprMap) Apply(fn func(interfaces.Node) error) error {
|
|
for _, x := range obj.KVs {
|
|
if err := x.Key.Apply(fn); err != nil {
|
|
return err
|
|
}
|
|
if err := x.Val.Apply(fn); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return fn(obj)
|
|
}
|
|
|
|
// Init initializes this branch of the AST, and returns an error if it fails to
|
|
// validate.
|
|
func (obj *ExprMap) Init(data *interfaces.Data) error {
|
|
// XXX: Can we check that there aren't any duplicate keys? Can we Cmp?
|
|
for _, x := range obj.KVs {
|
|
if err := x.Key.Init(data); err != nil {
|
|
return err
|
|
}
|
|
if err := x.Val.Init(data); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Interpolate returns a new node (aka a copy) once it has been expanded. This
|
|
// generally increases the size of the AST when it is used. It calls Interpolate
|
|
// on any child elements and builds the new node with those new node contents.
|
|
func (obj *ExprMap) Interpolate() (interfaces.Expr, error) {
|
|
kvs := []*ExprMapKV{}
|
|
for _, x := range obj.KVs {
|
|
interpolatedKey, err := x.Key.Interpolate()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
interpolatedVal, err := x.Val.Interpolate()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
kv := &ExprMapKV{
|
|
Key: interpolatedKey,
|
|
Val: interpolatedVal,
|
|
}
|
|
kvs = append(kvs, kv)
|
|
}
|
|
return &ExprMap{
|
|
scope: obj.scope,
|
|
typ: obj.typ,
|
|
KVs: kvs,
|
|
}, nil
|
|
}
|
|
|
|
// Copy returns a light copy of this struct. Anything static will not be copied.
|
|
func (obj *ExprMap) Copy() (interfaces.Expr, error) {
|
|
copied := false
|
|
kvs := []*ExprMapKV{}
|
|
for _, x := range obj.KVs {
|
|
copiedKV := false
|
|
copyKey, err := x.Key.Copy()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// must have been copied, or pointer would be same
|
|
if copyKey != x.Key {
|
|
copiedKV = true
|
|
}
|
|
copyVal, err := x.Val.Copy()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if copyVal != x.Val {
|
|
copiedKV = true
|
|
}
|
|
kv := &ExprMapKV{
|
|
Key: copyKey,
|
|
Val: copyVal,
|
|
}
|
|
if copiedKV {
|
|
copied = true
|
|
} else {
|
|
kv = x // don't re-package it unnecessarily!
|
|
}
|
|
kvs = append(kvs, kv)
|
|
}
|
|
|
|
if !copied { // it's static
|
|
return obj, nil
|
|
}
|
|
return &ExprMap{
|
|
scope: obj.scope,
|
|
typ: obj.typ,
|
|
KVs: kvs,
|
|
}, nil
|
|
}
|
|
|
|
// Ordering returns a graph of the scope ordering that represents the data flow.
|
|
// This can be used in SetScope so that it knows the correct order to run it in.
|
|
func (obj *ExprMap) Ordering(produces map[string]interfaces.Node) (*pgraph.Graph, map[interfaces.Node]string, error) {
|
|
graph, err := pgraph.NewGraph("ordering")
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
graph.AddVertex(obj)
|
|
|
|
cons := make(map[interfaces.Node]string)
|
|
|
|
for _, node := range obj.KVs {
|
|
g1, c1, err := node.Key.Ordering(produces)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
graph.AddGraph(g1) // add in the child graph
|
|
|
|
// additional constraint...
|
|
edge1 := &pgraph.SimpleEdge{Name: "exprmapkey"}
|
|
graph.AddEdge(node.Key, obj, edge1) // prod -> cons
|
|
|
|
for k, v := range c1 { // c1 is consumes
|
|
x, exists := cons[k]
|
|
if exists && v != x {
|
|
return nil, nil, fmt.Errorf("consumed value is different, got `%+v`, expected `%+v`", x, v)
|
|
}
|
|
cons[k] = v // add to map
|
|
|
|
n, exists := produces[v]
|
|
if !exists {
|
|
continue
|
|
}
|
|
edge := &pgraph.SimpleEdge{Name: "exprmapkey"}
|
|
graph.AddEdge(n, k, edge)
|
|
}
|
|
|
|
g2, c2, err := node.Val.Ordering(produces)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
graph.AddGraph(g2) // add in the child graph
|
|
|
|
// additional constraint...
|
|
edge2 := &pgraph.SimpleEdge{Name: "exprmapval"}
|
|
graph.AddEdge(node.Val, obj, edge2) // prod -> cons
|
|
|
|
for k, v := range c2 { // c2 is consumes
|
|
x, exists := cons[k]
|
|
if exists && v != x {
|
|
return nil, nil, fmt.Errorf("consumed value is different, got `%+v`, expected `%+v`", x, v)
|
|
}
|
|
cons[k] = v // add to map
|
|
|
|
n, exists := produces[v]
|
|
if !exists {
|
|
continue
|
|
}
|
|
edge := &pgraph.SimpleEdge{Name: "exprmapval"}
|
|
graph.AddEdge(n, k, edge)
|
|
}
|
|
}
|
|
|
|
return graph, cons, nil
|
|
}
|
|
|
|
// SetScope stores the scope for later use in this resource and its children,
|
|
// which it propagates this downwards to.
|
|
func (obj *ExprMap) SetScope(scope *interfaces.Scope, sctx map[string]interfaces.Expr) error {
|
|
if scope == nil {
|
|
scope = interfaces.EmptyScope()
|
|
}
|
|
obj.scope = scope
|
|
|
|
for _, x := range obj.KVs {
|
|
if err := x.Key.SetScope(scope, sctx); err != nil {
|
|
return err
|
|
}
|
|
if err := x.Val.SetScope(scope, sctx); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// SetType is used to set the type of this expression once it is known. This
|
|
// usually happens during type unification, but it can also happen during
|
|
// parsing if a type is specified explicitly. Since types are static and don't
|
|
// change on expressions, if you attempt to set a different type than what has
|
|
// previously been set (when not initially known) this will error.
|
|
func (obj *ExprMap) SetType(typ *types.Type) error {
|
|
// TODO: should we ensure this is set to a KindMap ?
|
|
if obj.typ != nil {
|
|
return obj.typ.Cmp(typ) // if not set, ensure it doesn't change
|
|
}
|
|
obj.typ = typ // set
|
|
return nil
|
|
}
|
|
|
|
// Type returns the type of this expression.
|
|
func (obj *ExprMap) Type() (*types.Type, error) {
|
|
var ktyp, vtyp *types.Type
|
|
var err error
|
|
for i, x := range obj.KVs {
|
|
// keys
|
|
kt, e := x.Key.Type()
|
|
if e != nil {
|
|
err = errwrap.Wrapf(e, "map key, index `%d` did not return a type", i)
|
|
break
|
|
}
|
|
if ktyp == nil {
|
|
ktyp = kt
|
|
}
|
|
if e := ktyp.Cmp(kt); e != nil {
|
|
err = errwrap.Wrapf(e, "key elements have different types")
|
|
break
|
|
}
|
|
|
|
// vals
|
|
vt, e := x.Val.Type()
|
|
if e != nil {
|
|
err = errwrap.Wrapf(e, "map val, index `%d` did not return a type", i)
|
|
break
|
|
}
|
|
if vtyp == nil {
|
|
vtyp = vt
|
|
}
|
|
if e := vtyp.Cmp(vt); e != nil {
|
|
err = errwrap.Wrapf(e, "val elements have different types")
|
|
break
|
|
}
|
|
}
|
|
if err == nil && obj.typ == nil && len(obj.KVs) > 0 {
|
|
return &types.Type{ // speculate!
|
|
Kind: types.KindMap,
|
|
Key: ktyp,
|
|
Val: vtyp,
|
|
}, nil
|
|
}
|
|
|
|
if obj.typ == nil {
|
|
if err != nil {
|
|
return nil, errwrap.Wrapf(interfaces.ErrTypeCurrentlyUnknown, err.Error())
|
|
}
|
|
return nil, interfaces.ErrTypeCurrentlyUnknown
|
|
}
|
|
return obj.typ, nil
|
|
}
|
|
|
|
// Unify returns the list of invariants that this node produces. It recursively
|
|
// calls Unify on any children elements that exist in the AST, and returns the
|
|
// collection to the caller.
|
|
func (obj *ExprMap) Unify() ([]interfaces.Invariant, error) {
|
|
var invariants []interfaces.Invariant
|
|
|
|
// if this was set explicitly by the parser
|
|
if obj.typ != nil {
|
|
invar := &interfaces.EqualsInvariant{
|
|
Expr: obj,
|
|
Type: obj.typ,
|
|
}
|
|
invariants = append(invariants, invar)
|
|
}
|
|
|
|
// collect all the invariants of each sub-expression
|
|
for _, x := range obj.KVs {
|
|
keyInvars, err := x.Key.Unify()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
invariants = append(invariants, keyInvars...)
|
|
|
|
valInvars, err := x.Val.Unify()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
invariants = append(invariants, valInvars...)
|
|
}
|
|
|
|
// all keys must have the same type, all vals must have the same type
|
|
if len(obj.KVs) > 1 {
|
|
keyExprs, valExprs := []interfaces.Expr{}, []interfaces.Expr{}
|
|
for i := range obj.KVs {
|
|
keyExprs = append(keyExprs, obj.KVs[i].Key)
|
|
valExprs = append(valExprs, obj.KVs[i].Val)
|
|
}
|
|
|
|
keyInvariant := &interfaces.EqualityInvariantList{
|
|
Exprs: keyExprs,
|
|
}
|
|
invariants = append(invariants, keyInvariant)
|
|
|
|
valInvariant := &interfaces.EqualityInvariantList{
|
|
Exprs: valExprs,
|
|
}
|
|
invariants = append(invariants, valInvariant)
|
|
}
|
|
|
|
// we should be type map of (type of element)
|
|
if len(obj.KVs) > 0 {
|
|
invariant := &interfaces.EqualityWrapMapInvariant{
|
|
Expr1: obj, // unique id for this expression (a pointer)
|
|
Expr2Key: obj.KVs[0].Key,
|
|
Expr2Val: obj.KVs[0].Val,
|
|
}
|
|
invariants = append(invariants, invariant)
|
|
}
|
|
|
|
// make sure this empty map gets a type for its key/value somehow
|
|
if len(obj.KVs) == 0 {
|
|
invariant := &interfaces.AnyInvariant{
|
|
Expr: obj,
|
|
}
|
|
invariants = append(invariants, invariant)
|
|
|
|
// build a placeholder expr to represent a contained key...
|
|
exprAnyKey, exprAnyVal := &interfaces.ExprAny{}, &interfaces.ExprAny{}
|
|
invarsKey, err := exprAnyKey.Unify()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
invariants = append(invariants, invarsKey...)
|
|
invarsVal, err := exprAnyVal.Unify()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
invariants = append(invariants, invarsVal...)
|
|
|
|
// FIXME: instead of using `ExprAny`, we could actually teach
|
|
// our unification engine to ensure that our expr kind is list,
|
|
// eg:
|
|
//&interfaces.EqualityKindInvariant{
|
|
// Expr1: obj,
|
|
// Kind: types.KindMap,
|
|
//}
|
|
invar := &interfaces.EqualityWrapMapInvariant{
|
|
Expr1: obj,
|
|
Expr2Key: exprAnyKey, // hack
|
|
Expr2Val: exprAnyVal, // hack
|
|
}
|
|
invariants = append(invariants, invar)
|
|
}
|
|
|
|
return invariants, nil
|
|
}
|
|
|
|
// Func returns the reactive stream of values that this expression produces.
|
|
func (obj *ExprMap) Func() (interfaces.Func, error) {
|
|
typ, err := obj.Type()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// composite func (list, map, struct)
|
|
return &structs.CompositeFunc{
|
|
Type: typ, // the key/val types are known via this type
|
|
Len: len(obj.KVs),
|
|
}, nil
|
|
}
|
|
|
|
// Graph returns the reactive function graph which is expressed by this node. It
|
|
// includes any vertices produced by this node, and the appropriate edges to any
|
|
// vertices that are produced by its children. Nodes which fulfill the Expr
|
|
// interface directly produce vertices (and possible children) where as nodes
|
|
// 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, and
|
|
// the edges from all of the child graphs to this.
|
|
func (obj *ExprMap) Graph(env map[string]interfaces.Func) (*pgraph.Graph, interfaces.Func, error) {
|
|
graph, err := pgraph.NewGraph("map")
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
function, err := obj.Func()
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
graph.AddVertex(function)
|
|
|
|
// each map key value pair needs to point to the final map expression
|
|
for index, x := range obj.KVs { // map fields in order
|
|
g, f, err := x.Key.Graph(env)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
graph.AddGraph(g)
|
|
|
|
// do the key names ever change? -- yes
|
|
fieldName := fmt.Sprintf("key:%d", index) // stringify map key
|
|
edge := &interfaces.FuncEdge{Args: []string{fieldName}}
|
|
graph.AddEdge(f, function, edge) // key -> map
|
|
}
|
|
|
|
// each map key value pair needs to point to the final map expression
|
|
for index, x := range obj.KVs { // map fields in order
|
|
g, f, err := x.Val.Graph(env)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
graph.AddGraph(g)
|
|
|
|
fieldName := fmt.Sprintf("val:%d", index) // stringify map val
|
|
edge := &interfaces.FuncEdge{Args: []string{fieldName}}
|
|
graph.AddEdge(f, function, edge) // val -> map
|
|
}
|
|
|
|
return graph, function, nil
|
|
}
|
|
|
|
// SetValue here is a no-op, because algorithmically when this is called from
|
|
// the func engine, the child key/value's (the map elements) will have had this
|
|
// done to them first, and as such when we try and retrieve the set value from
|
|
// this expression by calling `Value`, it will build it from scratch!
|
|
func (obj *ExprMap) SetValue(value types.Value) error {
|
|
if err := obj.typ.Cmp(value.Type()); err != nil {
|
|
return err
|
|
}
|
|
// noop!
|
|
//obj.V = value
|
|
return nil
|
|
}
|
|
|
|
// Value returns the value of this expression in our type system. This will
|
|
// usually only be valid once the engine has run and values have been produced.
|
|
// This might get called speculatively (early) during unification to learn more.
|
|
func (obj *ExprMap) Value() (types.Value, error) {
|
|
kvs := make(map[types.Value]types.Value)
|
|
var ktyp, vtyp *types.Type
|
|
|
|
for i, x := range obj.KVs {
|
|
// keys
|
|
kt, err := x.Key.Type()
|
|
if err != nil {
|
|
return nil, errwrap.Wrapf(err, "map key, index `%d` did not return a type", i)
|
|
}
|
|
if ktyp == nil {
|
|
ktyp = kt
|
|
}
|
|
if err := ktyp.Cmp(kt); err != nil {
|
|
return nil, errwrap.Wrapf(err, "key elements have different types")
|
|
}
|
|
|
|
key, err := x.Key.Value()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if key == nil {
|
|
return nil, fmt.Errorf("key for map index `%d` was nil", i)
|
|
}
|
|
|
|
// vals
|
|
vt, err := x.Val.Type()
|
|
if err != nil {
|
|
return nil, errwrap.Wrapf(err, "map val, index `%d` did not return a type", i)
|
|
}
|
|
if vtyp == nil {
|
|
vtyp = vt
|
|
}
|
|
if err := vtyp.Cmp(vt); err != nil {
|
|
return nil, errwrap.Wrapf(err, "val elements have different types")
|
|
}
|
|
|
|
val, err := x.Val.Value()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if val == nil {
|
|
return nil, fmt.Errorf("val for map index `%d` was nil", i)
|
|
}
|
|
|
|
kvs[key] = val // add to map
|
|
}
|
|
if len(obj.KVs) > 0 {
|
|
t := &types.Type{
|
|
Kind: types.KindMap,
|
|
Key: ktyp,
|
|
Val: vtyp,
|
|
}
|
|
// Run SetType to ensure type is consistent with what we found,
|
|
// which is an easy way to ensure the Cmp passes as expected...
|
|
if err := obj.SetType(t); err != nil {
|
|
return nil, errwrap.Wrapf(err, "type did not match expected!")
|
|
}
|
|
}
|
|
|
|
return &types.MapValue{
|
|
T: obj.typ,
|
|
V: kvs,
|
|
}, nil
|
|
}
|
|
|
|
// ExprMapKV represents a key and value pair in a (dictionary) map. This does
|
|
// not satisfy the Expr interface.
|
|
type ExprMapKV struct {
|
|
Key interfaces.Expr // keys can be strings, int's, etc...
|
|
Val interfaces.Expr
|
|
}
|
|
|
|
// ExprStruct is a representation of a struct.
|
|
type ExprStruct struct {
|
|
scope *interfaces.Scope // store for referencing this later
|
|
typ *types.Type
|
|
|
|
Fields []*ExprStructField // the list (fields) are intentionally ordered!
|
|
}
|
|
|
|
// String returns a short representation of this expression.
|
|
func (obj *ExprStruct) String() string {
|
|
var s []string
|
|
for _, x := range obj.Fields {
|
|
s = append(s, fmt.Sprintf("%s: %s", x.Name, x.Value.String()))
|
|
}
|
|
return fmt.Sprintf("struct(%s)", strings.Join(s, "; "))
|
|
}
|
|
|
|
// Apply is a general purpose iterator method that operates on any AST node. It
|
|
// is not used as the primary AST traversal function because it is less readable
|
|
// and easy to reason about than manually implementing traversal for each node.
|
|
// Nevertheless, it is a useful facility for operations that might only apply to
|
|
// a select number of node types, since they won't need extra noop iterators...
|
|
func (obj *ExprStruct) Apply(fn func(interfaces.Node) error) error {
|
|
for _, x := range obj.Fields {
|
|
if err := x.Value.Apply(fn); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return fn(obj)
|
|
}
|
|
|
|
// Init initializes this branch of the AST, and returns an error if it fails to
|
|
// validate.
|
|
func (obj *ExprStruct) Init(data *interfaces.Data) error {
|
|
fields := make(map[string]struct{})
|
|
for _, x := range obj.Fields {
|
|
// Validate field names and ensure no duplicates!
|
|
if _, exists := fields[x.Name]; exists {
|
|
return fmt.Errorf("duplicate struct field name of: `%s`", x.Name)
|
|
}
|
|
fields[x.Name] = struct{}{}
|
|
|
|
if err := x.Value.Init(data); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Interpolate returns a new node (aka a copy) once it has been expanded. This
|
|
// generally increases the size of the AST when it is used. It calls Interpolate
|
|
// on any child elements and builds the new node with those new node contents.
|
|
func (obj *ExprStruct) Interpolate() (interfaces.Expr, error) {
|
|
fields := []*ExprStructField{}
|
|
for _, x := range obj.Fields {
|
|
interpolated, err := x.Value.Interpolate()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
field := &ExprStructField{
|
|
Name: x.Name, // don't interpolate the key
|
|
Value: interpolated,
|
|
}
|
|
fields = append(fields, field)
|
|
}
|
|
return &ExprStruct{
|
|
scope: obj.scope,
|
|
typ: obj.typ,
|
|
Fields: fields,
|
|
}, nil
|
|
}
|
|
|
|
// Copy returns a light copy of this struct. Anything static will not be copied.
|
|
func (obj *ExprStruct) Copy() (interfaces.Expr, error) {
|
|
copied := false
|
|
fields := []*ExprStructField{}
|
|
for _, x := range obj.Fields {
|
|
cp, err := x.Value.Copy()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// must have been copied, or pointer would be same
|
|
if cp != x.Value {
|
|
copied = true
|
|
}
|
|
|
|
field := &ExprStructField{
|
|
Name: x.Name,
|
|
Value: cp,
|
|
}
|
|
fields = append(fields, field)
|
|
}
|
|
|
|
if !copied { // it's static
|
|
return obj, nil
|
|
}
|
|
return &ExprStruct{
|
|
scope: obj.scope,
|
|
typ: obj.typ,
|
|
Fields: fields,
|
|
}, nil
|
|
}
|
|
|
|
// Ordering returns a graph of the scope ordering that represents the data flow.
|
|
// This can be used in SetScope so that it knows the correct order to run it in.
|
|
func (obj *ExprStruct) Ordering(produces map[string]interfaces.Node) (*pgraph.Graph, map[interfaces.Node]string, error) {
|
|
graph, err := pgraph.NewGraph("ordering")
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
graph.AddVertex(obj)
|
|
|
|
cons := make(map[interfaces.Node]string)
|
|
|
|
for _, node := range obj.Fields {
|
|
g, c, err := node.Value.Ordering(produces)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
graph.AddGraph(g) // add in the child graph
|
|
|
|
// additional constraint...
|
|
edge := &pgraph.SimpleEdge{Name: "exprstructfield"}
|
|
graph.AddEdge(node.Value, obj, edge) // prod -> cons
|
|
|
|
for k, v := range c { // c is consumes
|
|
x, exists := cons[k]
|
|
if exists && v != x {
|
|
return nil, nil, fmt.Errorf("consumed value is different, got `%+v`, expected `%+v`", x, v)
|
|
}
|
|
cons[k] = v // add to map
|
|
|
|
n, exists := produces[v]
|
|
if !exists {
|
|
continue
|
|
}
|
|
edge := &pgraph.SimpleEdge{Name: "exprstruct"}
|
|
graph.AddEdge(n, k, edge)
|
|
}
|
|
}
|
|
|
|
return graph, cons, nil
|
|
}
|
|
|
|
// SetScope stores the scope for later use in this resource and its children,
|
|
// which it propagates this downwards to.
|
|
func (obj *ExprStruct) SetScope(scope *interfaces.Scope, sctx map[string]interfaces.Expr) error {
|
|
if scope == nil {
|
|
scope = interfaces.EmptyScope()
|
|
}
|
|
obj.scope = scope
|
|
|
|
for _, x := range obj.Fields {
|
|
if err := x.Value.SetScope(scope, sctx); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// SetType is used to set the type of this expression once it is known. This
|
|
// usually happens during type unification, but it can also happen during
|
|
// parsing if a type is specified explicitly. Since types are static and don't
|
|
// change on expressions, if you attempt to set a different type than what has
|
|
// previously been set (when not initially known) this will error.
|
|
func (obj *ExprStruct) SetType(typ *types.Type) error {
|
|
// TODO: should we ensure this is set to a KindStruct ?
|
|
if obj.typ != nil {
|
|
return obj.typ.Cmp(typ) // if not set, ensure it doesn't change
|
|
}
|
|
obj.typ = typ // set
|
|
return nil
|
|
}
|
|
|
|
// Type returns the type of this expression.
|
|
func (obj *ExprStruct) Type() (*types.Type, error) {
|
|
var m = make(map[string]*types.Type)
|
|
ord := []string{}
|
|
var err error
|
|
for i, x := range obj.Fields {
|
|
// vals
|
|
t, e := x.Value.Type()
|
|
if e != nil {
|
|
err = errwrap.Wrapf(e, "field val, index `%d` did not return a type", i)
|
|
break
|
|
}
|
|
if _, exists := m[x.Name]; exists {
|
|
err = fmt.Errorf("struct type field index `%d` already exists", i)
|
|
break
|
|
}
|
|
m[x.Name] = t
|
|
ord = append(ord, x.Name)
|
|
}
|
|
if err == nil && obj.typ == nil && len(obj.Fields) > 0 {
|
|
return &types.Type{ // speculate!
|
|
Kind: types.KindStruct,
|
|
Map: m,
|
|
Ord: ord, // assume same order as fields
|
|
}, nil
|
|
}
|
|
|
|
if obj.typ == nil {
|
|
if err != nil {
|
|
return nil, errwrap.Wrapf(interfaces.ErrTypeCurrentlyUnknown, err.Error())
|
|
}
|
|
return nil, interfaces.ErrTypeCurrentlyUnknown
|
|
}
|
|
return obj.typ, nil
|
|
}
|
|
|
|
// Unify returns the list of invariants that this node produces. It recursively
|
|
// calls Unify on any children elements that exist in the AST, and returns the
|
|
// collection to the caller.
|
|
func (obj *ExprStruct) Unify() ([]interfaces.Invariant, error) {
|
|
var invariants []interfaces.Invariant
|
|
|
|
// if this was set explicitly by the parser
|
|
if obj.typ != nil {
|
|
invar := &interfaces.EqualsInvariant{
|
|
Expr: obj,
|
|
Type: obj.typ,
|
|
}
|
|
invariants = append(invariants, invar)
|
|
}
|
|
|
|
// collect all the invariants of each sub-expression
|
|
for _, x := range obj.Fields {
|
|
invars, err := x.Value.Unify()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
invariants = append(invariants, invars...)
|
|
}
|
|
|
|
// build the reference to ourself if we have undetermined field types
|
|
mapped := make(map[string]interfaces.Expr)
|
|
ordered := []string{}
|
|
for _, x := range obj.Fields {
|
|
mapped[x.Name] = x.Value
|
|
ordered = append(ordered, x.Name)
|
|
}
|
|
invariant := &interfaces.EqualityWrapStructInvariant{
|
|
Expr1: obj, // unique id for this expression (a pointer)
|
|
Expr2Map: mapped,
|
|
Expr2Ord: ordered,
|
|
}
|
|
invariants = append(invariants, invariant)
|
|
|
|
return invariants, nil
|
|
}
|
|
|
|
// Func returns the reactive stream of values that this expression produces.
|
|
func (obj *ExprStruct) Func() (interfaces.Func, error) {
|
|
typ, err := obj.Type()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// composite func (list, map, struct)
|
|
return &structs.CompositeFunc{
|
|
Type: typ,
|
|
}, nil
|
|
}
|
|
|
|
// Graph returns the reactive function graph which is expressed by this node. It
|
|
// includes any vertices produced by this node, and the appropriate edges to any
|
|
// vertices that are produced by its children. Nodes which fulfill the Expr
|
|
// interface directly produce vertices (and possible children) where as nodes
|
|
// 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, and
|
|
// the edges from all of the child graphs to this.
|
|
func (obj *ExprStruct) Graph(env map[string]interfaces.Func) (*pgraph.Graph, interfaces.Func, error) {
|
|
graph, err := pgraph.NewGraph("struct")
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
function, err := obj.Func()
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
graph.AddVertex(function)
|
|
|
|
// each struct field needs to point to the final struct expression
|
|
for _, x := range obj.Fields { // struct fields in order
|
|
g, f, err := x.Value.Graph(env)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
graph.AddGraph(g)
|
|
|
|
fieldName := x.Name
|
|
edge := &interfaces.FuncEdge{Args: []string{fieldName}}
|
|
graph.AddEdge(f, function, edge) // field -> struct
|
|
}
|
|
|
|
return graph, function, nil
|
|
}
|
|
|
|
// SetValue here is a no-op, because algorithmically when this is called from
|
|
// the func engine, the child fields (the struct elements) will have had this
|
|
// done to them first, and as such when we try and retrieve the set value from
|
|
// this expression by calling `Value`, it will build it from scratch!
|
|
func (obj *ExprStruct) SetValue(value types.Value) error {
|
|
if err := obj.typ.Cmp(value.Type()); err != nil {
|
|
return err
|
|
}
|
|
// noop!
|
|
//obj.V = value
|
|
return nil
|
|
}
|
|
|
|
// Value returns the value of this expression in our type system. This will
|
|
// usually only be valid once the engine has run and values have been produced.
|
|
// This might get called speculatively (early) during unification to learn more.
|
|
func (obj *ExprStruct) Value() (types.Value, error) {
|
|
fields := make(map[string]types.Value)
|
|
typ := &types.Type{
|
|
Kind: types.KindStruct,
|
|
Map: make(map[string]*types.Type),
|
|
//Ord: obj.typ.Ord, // assume same order
|
|
}
|
|
ord := []string{} // can't use obj.typ b/c it can be nil during speculation
|
|
|
|
for i, x := range obj.Fields {
|
|
// vals
|
|
t, err := x.Value.Type()
|
|
if err != nil {
|
|
return nil, errwrap.Wrapf(err, "field val, index `%d` did not return a type", i)
|
|
}
|
|
if _, exists := typ.Map[x.Name]; exists {
|
|
return nil, fmt.Errorf("struct type field index `%d` already exists", i)
|
|
}
|
|
typ.Map[x.Name] = t
|
|
|
|
val, err := x.Value.Value()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if val == nil {
|
|
return nil, fmt.Errorf("val for field index `%d` was nil", i)
|
|
}
|
|
|
|
if _, exists := fields[x.Name]; exists {
|
|
return nil, fmt.Errorf("struct field index `%d` already exists", i)
|
|
}
|
|
fields[x.Name] = val // add to map
|
|
ord = append(ord, x.Name)
|
|
}
|
|
typ.Ord = ord
|
|
if len(obj.Fields) > 0 {
|
|
// Run SetType to ensure type is consistent with what we found,
|
|
// which is an easy way to ensure the Cmp passes as expected...
|
|
if err := obj.SetType(typ); err != nil {
|
|
return nil, errwrap.Wrapf(err, "type did not match expected!")
|
|
}
|
|
}
|
|
|
|
return &types.StructValue{
|
|
T: obj.typ,
|
|
V: fields,
|
|
}, nil
|
|
}
|
|
|
|
// ExprStructField represents a name value pair in a struct field. This does not
|
|
// satisfy the Expr interface.
|
|
type ExprStructField struct {
|
|
Name string
|
|
Value interfaces.Expr
|
|
}
|
|
|
|
// ExprFunc is a representation of a function value. This is not a function
|
|
// 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
|
|
typ *types.Type
|
|
|
|
// Title is a friendly-name to use for identifying the function. It can
|
|
// be used in debugging and error-handling. It is not required. It is
|
|
// *not* called Name, because that could get confused with the Name
|
|
// field in ExprCall and similar nodes.
|
|
Title string
|
|
|
|
// Args are the list of args that were used when defining the function.
|
|
// This can include a string name and a type, however the type might be
|
|
// absent here.
|
|
Args []*interfaces.Arg
|
|
|
|
// One ExprParam is created for each parameter, and the ExprVars which
|
|
// refer to those parameters are set to point to the corresponding
|
|
// ExprParam.
|
|
params []*ExprParam
|
|
|
|
// Return is the return type of the function if it was defined.
|
|
Return *types.Type // return type if specified
|
|
// Body is the contents of the function. It can be any expression.
|
|
Body interfaces.Expr
|
|
|
|
// Function is the built implementation of the function interface as
|
|
// represented by the top-level function API.
|
|
Function func() interfaces.Func // store like this to build on demand!
|
|
function interfaces.Func // store the built version here...
|
|
|
|
// Values represents a list of simple functions. This means this can be
|
|
// polymorphic if more than one was specified!
|
|
Values []*types.FuncValue
|
|
|
|
// XXX: is this necessary?
|
|
//V func(interfaces.Txn, []pgraph.Vertex) (pgraph.Vertex, error)
|
|
}
|
|
|
|
// String returns a short representation of this expression.
|
|
func (obj *ExprFunc) String() string {
|
|
if len(obj.Values) == 1 {
|
|
if obj.Title != "" {
|
|
return fmt.Sprintf("func() { <built-in:%s (simple)> }", obj.Title)
|
|
}
|
|
return "func() { <built-in (simple)> }"
|
|
} else if len(obj.Values) > 0 {
|
|
if obj.Title != "" {
|
|
return fmt.Sprintf("func() { <built-in:%s (simple, poly)> }", obj.Title)
|
|
}
|
|
return "func() { <built-in (simple, poly)> }"
|
|
}
|
|
if obj.Function != nil {
|
|
if obj.Title != "" {
|
|
return fmt.Sprintf("func() { <built-in:%s> }", obj.Title)
|
|
}
|
|
return "func() { <built-in> }"
|
|
}
|
|
if obj.Body == nil {
|
|
panic("function expression was not built correctly")
|
|
}
|
|
|
|
var a []string
|
|
for _, x := range obj.Args {
|
|
a = append(a, fmt.Sprintf("%s", x.String()))
|
|
}
|
|
args := strings.Join(a, ", ")
|
|
s := fmt.Sprintf("func(%s)", args)
|
|
if obj.Title != "" {
|
|
s = fmt.Sprintf("func:%s(%s)", obj.Title, args) // overwrite!
|
|
}
|
|
if obj.Return != nil {
|
|
s += fmt.Sprintf(" %s", obj.Return.String())
|
|
}
|
|
s += fmt.Sprintf(" { %s }", obj.Body.String())
|
|
return s
|
|
}
|
|
|
|
// Apply is a general purpose iterator method that operates on any AST node. It
|
|
// is not used as the primary AST traversal function because it is less readable
|
|
// and easy to reason about than manually implementing traversal for each node.
|
|
// Nevertheless, it is a useful facility for operations that might only apply to
|
|
// a select number of node types, since they won't need extra noop iterators...
|
|
func (obj *ExprFunc) Apply(fn func(interfaces.Node) error) error {
|
|
if obj.Body != nil {
|
|
if err := obj.Body.Apply(fn); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return fn(obj)
|
|
}
|
|
|
|
// Init initializes this branch of the AST, and returns an error if it fails to
|
|
// validate.
|
|
func (obj *ExprFunc) Init(data *interfaces.Data) error {
|
|
obj.data = data // TODO: why is this sometimes nil?
|
|
|
|
// validate that we're using *only* one correct representation
|
|
a := obj.Body != nil
|
|
b := obj.Function != nil
|
|
c := len(obj.Values) > 0
|
|
if (a && b || b && c) || !a && !b && !c {
|
|
return fmt.Errorf("function expression was not built correctly")
|
|
}
|
|
|
|
if obj.Body != nil {
|
|
if err := obj.Body.Init(data); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if obj.Function != nil {
|
|
if obj.function != nil { // check for double Init!
|
|
// programming error!
|
|
return fmt.Errorf("func is being re-built")
|
|
}
|
|
obj.function = obj.Function() // build it
|
|
// pass in some data to the function
|
|
// TODO: do we want to pass in the full obj.data instead ?
|
|
if dataFunc, ok := obj.function.(interfaces.DataFunc); ok {
|
|
dataFunc.SetData(&interfaces.FuncData{
|
|
Fs: obj.data.Fs,
|
|
FsURI: obj.data.FsURI,
|
|
Base: obj.data.Base,
|
|
})
|
|
}
|
|
}
|
|
|
|
if len(obj.Values) > 0 {
|
|
typs := []*types.Type{}
|
|
for _, f := range obj.Values {
|
|
if f.T == nil {
|
|
return fmt.Errorf("func contains a nil type signature")
|
|
}
|
|
typs = append(typs, f.T)
|
|
}
|
|
if err := langutil.HasDuplicateTypes(typs); err != nil {
|
|
return errwrap.Wrapf(err, "func list contains a duplicate signature")
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Interpolate returns a new node (aka a copy) once it has been expanded. This
|
|
// generally increases the size of the AST when it is used. It calls Interpolate
|
|
// on any child elements and builds the new node with those new node contents.
|
|
// Here it simply returns itself, as no interpolation is possible.
|
|
func (obj *ExprFunc) Interpolate() (interfaces.Expr, error) {
|
|
var body interfaces.Expr
|
|
if obj.Body != nil {
|
|
var err error
|
|
body, err = obj.Body.Interpolate()
|
|
if err != nil {
|
|
return nil, errwrap.Wrapf(err, "could not interpolate Body")
|
|
}
|
|
}
|
|
|
|
args := obj.Args
|
|
if obj.Args == nil {
|
|
args = []*interfaces.Arg{}
|
|
}
|
|
|
|
return &ExprFunc{
|
|
data: obj.data,
|
|
scope: obj.scope,
|
|
typ: obj.typ,
|
|
Title: obj.Title,
|
|
Args: args,
|
|
Return: obj.Return,
|
|
Body: body,
|
|
Function: obj.Function,
|
|
function: obj.function,
|
|
Values: obj.Values,
|
|
//V: obj.V,
|
|
}, nil
|
|
}
|
|
|
|
// Copy returns a light copy of this struct. Anything static will not be copied.
|
|
// All the constants aren't copied, because we don't want to duplicate them
|
|
// unnecessarily in the function graph. For example, an static integer will not
|
|
// ever change, where as a function value (expr) might get used with two
|
|
// different signatures depending on the caller.
|
|
func (obj *ExprFunc) Copy() (interfaces.Expr, error) {
|
|
// I think we want to copy anything in the Expr tree that has at least
|
|
// one input... Eg: we DON'T want to copy an ExprStr but we DO want to
|
|
// copy an ExprVar because it gets an input edge.
|
|
copied := false
|
|
var body interfaces.Expr
|
|
if obj.Body != nil {
|
|
var err error
|
|
//body, err = obj.Body.Interpolate() // an inefficient copy works!
|
|
body, err = obj.Body.Copy()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// must have been copied, or pointer would be same
|
|
if body != obj.Body {
|
|
copied = true
|
|
}
|
|
}
|
|
|
|
var function interfaces.Func
|
|
if obj.Function != nil {
|
|
function = obj.Function() // force re-build a new pointer here!
|
|
// restore the type we previously set in SetType()
|
|
if obj.typ != nil {
|
|
polyFn, ok := function.(interfaces.PolyFunc) // is it statically polymorphic?
|
|
if ok {
|
|
newTyp, err := polyFn.Build(obj.typ)
|
|
if err != nil {
|
|
return nil, errwrap.Wrapf(err, "could not build expr func")
|
|
}
|
|
// Cmp doesn't compare arg names. Check it's compatible...
|
|
if err := obj.typ.Cmp(newTyp); err != nil {
|
|
return nil, errwrap.Wrapf(err, "incompatible type")
|
|
}
|
|
}
|
|
}
|
|
// pass in some data to the function
|
|
// TODO: do we want to pass in the full obj.data instead ?
|
|
if dataFunc, ok := function.(interfaces.DataFunc); ok {
|
|
dataFunc.SetData(&interfaces.FuncData{
|
|
Fs: obj.data.Fs,
|
|
FsURI: obj.data.FsURI,
|
|
Base: obj.data.Base,
|
|
})
|
|
}
|
|
copied = true
|
|
}
|
|
|
|
if len(obj.Values) > 0 {
|
|
// copied = true // XXX: add this if anyone isn't static?
|
|
}
|
|
|
|
// We wan't to allow static functions, although we have to be careful...
|
|
// Doing this for static functions causes us to hit a strange case in
|
|
// the SetScope function for ExprCall... Investigate if we find a bug...
|
|
if !copied { // it's static
|
|
return obj, nil
|
|
}
|
|
return &ExprFunc{
|
|
data: obj.data,
|
|
scope: obj.scope, // TODO: copy?
|
|
typ: obj.typ,
|
|
Title: obj.Title,
|
|
Args: obj.Args,
|
|
Return: obj.Return,
|
|
Body: body, // definitely copy
|
|
Function: obj.Function,
|
|
function: function,
|
|
Values: obj.Values, // XXX: do we need to force rebuild these?
|
|
//V: obj.V,
|
|
}, nil
|
|
}
|
|
|
|
// Ordering returns a graph of the scope ordering that represents the data flow.
|
|
// This can be used in SetScope so that it knows the correct order to run it in.
|
|
// XXX: do we need to add ordering around named args, eg: obj.Args Name strings?
|
|
func (obj *ExprFunc) Ordering(produces map[string]interfaces.Node) (*pgraph.Graph, map[interfaces.Node]string, error) {
|
|
graph, err := pgraph.NewGraph("ordering")
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
graph.AddVertex(obj)
|
|
|
|
cons := make(map[interfaces.Node]string)
|
|
|
|
// TODO: do we need ordering for other aspects of ExprFunc ?
|
|
if obj.Body != nil {
|
|
g, c, err := obj.Body.Ordering(produces)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
graph.AddGraph(g) // add in the child graph
|
|
|
|
// additional constraint...
|
|
edge := &pgraph.SimpleEdge{Name: "exprfuncbody"}
|
|
graph.AddEdge(obj.Body, obj, edge) // prod -> cons
|
|
|
|
for k, v := range c { // c is consumes
|
|
x, exists := cons[k]
|
|
if exists && v != x {
|
|
return nil, nil, fmt.Errorf("consumed value is different, got `%+v`, expected `%+v`", x, v)
|
|
}
|
|
cons[k] = v // add to map
|
|
|
|
n, exists := produces[v]
|
|
if !exists {
|
|
continue
|
|
}
|
|
edge := &pgraph.SimpleEdge{Name: "exprfunc"}
|
|
graph.AddEdge(n, k, edge)
|
|
}
|
|
}
|
|
|
|
return graph, cons, nil
|
|
}
|
|
|
|
// SetScope stores the scope for later use in this resource and its children,
|
|
// which it propagates this downwards to.
|
|
func (obj *ExprFunc) SetScope(scope *interfaces.Scope, sctx map[string]interfaces.Expr) error {
|
|
if scope == nil {
|
|
scope = interfaces.EmptyScope()
|
|
}
|
|
obj.scope = scope // store for later
|
|
|
|
if obj.Body != nil {
|
|
sctxBody := make(map[string]interfaces.Expr)
|
|
for k, v := range sctx {
|
|
sctxBody[k] = v
|
|
}
|
|
|
|
// add the parameters to the context (sctx) for the body
|
|
// make a list as long as obj.Args
|
|
obj.params = make([]*ExprParam, len(obj.Args))
|
|
for i, arg := range obj.Args {
|
|
param := &ExprParam{Name: arg.Name, Typ: arg.Type}
|
|
obj.params[i] = param
|
|
sctxBody[arg.Name] = param
|
|
}
|
|
|
|
if err := obj.Body.SetScope(scope, sctxBody); err != nil {
|
|
return errwrap.Wrapf(err, "failed to set scope on function body")
|
|
}
|
|
}
|
|
|
|
if obj.Function != nil {
|
|
// 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
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// SetType is used to set the type of this expression once it is known. This
|
|
// usually happens during type unification, but it can also happen during
|
|
// parsing if a type is specified explicitly. Since types are static and don't
|
|
// change on expressions, if you attempt to set a different type than what has
|
|
// previously been set (when not initially known) this will error.
|
|
func (obj *ExprFunc) SetType(typ *types.Type) error {
|
|
if obj.Body != nil {
|
|
// FIXME: check that it's compatible with Args/Body/Return
|
|
}
|
|
|
|
// TODO: should we ensure this is set to a KindFunc ?
|
|
if obj.Function != nil {
|
|
polyFn, ok := obj.function.(interfaces.PolyFunc) // is it statically polymorphic?
|
|
if ok {
|
|
newTyp, err := polyFn.Build(typ)
|
|
if err != nil {
|
|
return errwrap.Wrapf(err, "could not build expr func")
|
|
}
|
|
// Cmp doesn't compare arg names.
|
|
typ = newTyp // check it's compatible down below...
|
|
} else {
|
|
// Even if it's not polymorphic, we'd like to use the
|
|
// real arg names of that function, in case they don't
|
|
// get passed through type unification somehow...
|
|
// (There can be an AST bug that this would prevent.)
|
|
sig := obj.function.Info().Sig
|
|
if sig == nil {
|
|
return fmt.Errorf("could not read nil expr func sig")
|
|
}
|
|
typ = sig // check it's compatible down below...
|
|
}
|
|
}
|
|
|
|
if len(obj.Values) > 0 {
|
|
// search for the compatible type
|
|
_, err := langutil.FnMatch(typ, obj.Values)
|
|
if err != nil {
|
|
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()
|
|
//fn.T = typ.Copy() // overwrites any contained "variant" type
|
|
}
|
|
|
|
if obj.typ != nil {
|
|
return obj.typ.Cmp(typ) // if not set, ensure it doesn't change
|
|
}
|
|
obj.typ = typ // set
|
|
return nil
|
|
}
|
|
|
|
// Type returns the type of this expression. It will attempt to speculate on the
|
|
// type if it can be determined statically before type unification.
|
|
func (obj *ExprFunc) Type() (*types.Type, error) {
|
|
if len(obj.Values) == 1 {
|
|
// speculative, type is known statically
|
|
if typ := obj.Values[0].Type(); !typ.HasVariant() && obj.typ == nil {
|
|
return typ, nil
|
|
}
|
|
|
|
if obj.typ == nil {
|
|
return nil, interfaces.ErrTypeCurrentlyUnknown
|
|
}
|
|
return obj.typ, nil
|
|
|
|
} else if len(obj.Values) > 0 {
|
|
// there's nothing we can do to speculate at this time
|
|
if obj.typ == nil {
|
|
return nil, interfaces.ErrTypeCurrentlyUnknown
|
|
}
|
|
return obj.typ, nil
|
|
}
|
|
|
|
if obj.Function != nil {
|
|
if obj.function == nil {
|
|
// TODO: should we return ErrTypeCurrentlyUnknown instead?
|
|
panic("unexpected empty function")
|
|
//return nil, interfaces.ErrTypeCurrentlyUnknown
|
|
}
|
|
sig := obj.function.Info().Sig
|
|
if sig != nil && !sig.HasVariant() && obj.typ == nil { // type is now known statically
|
|
return sig, nil
|
|
}
|
|
|
|
if obj.typ == nil {
|
|
return nil, interfaces.ErrTypeCurrentlyUnknown
|
|
}
|
|
return obj.typ, nil
|
|
}
|
|
|
|
var m = make(map[string]*types.Type)
|
|
ord := []string{}
|
|
var err error
|
|
for i, arg := range obj.Args {
|
|
if _, exists := m[arg.Name]; exists {
|
|
err = fmt.Errorf("func arg index `%d` already exists", i)
|
|
break
|
|
}
|
|
if arg.Type == nil {
|
|
err = fmt.Errorf("func arg type `%s` at index `%d` is unknown", arg.Name, i)
|
|
break
|
|
}
|
|
m[arg.Name] = arg.Type
|
|
ord = append(ord, arg.Name)
|
|
}
|
|
|
|
rtyp, e := obj.Body.Type()
|
|
if e != nil {
|
|
// TODO: do we want to include this speculative snippet below?
|
|
// function return type cannot be determined...
|
|
if obj.Return == nil {
|
|
e := errwrap.Wrapf(e, "body/return type is unknown")
|
|
err = errwrap.Append(err, e)
|
|
} else {
|
|
// probably unnecessary except for speculative execution
|
|
// because there is an invariant to determine this type!
|
|
rtyp = obj.Return // bonus, happens to be known
|
|
}
|
|
}
|
|
|
|
if err == nil && obj.typ == nil { // type is now known statically
|
|
return &types.Type{
|
|
Kind: types.KindFunc,
|
|
Map: m,
|
|
Ord: ord,
|
|
Out: rtyp,
|
|
}, nil
|
|
}
|
|
|
|
if obj.typ == nil {
|
|
if err != nil {
|
|
return nil, errwrap.Wrapf(interfaces.ErrTypeCurrentlyUnknown, err.Error())
|
|
}
|
|
return nil, interfaces.ErrTypeCurrentlyUnknown
|
|
}
|
|
return obj.typ, nil
|
|
}
|
|
|
|
// Unify returns the list of invariants that this node produces. It recursively
|
|
// calls Unify on any children elements that exist in the AST, and returns the
|
|
// collection to the caller.
|
|
func (obj *ExprFunc) Unify() ([]interfaces.Invariant, error) {
|
|
var invariants []interfaces.Invariant
|
|
|
|
// if this was set explicitly by the parser
|
|
if obj.typ != nil {
|
|
invar := &interfaces.EqualsInvariant{
|
|
Expr: obj,
|
|
Type: obj.typ,
|
|
}
|
|
invariants = append(invariants, invar)
|
|
}
|
|
|
|
// if we know the type statically...
|
|
// TODO: is this redundant, or do we need something similar elsewhere?
|
|
if typ, err := obj.Type(); err == nil {
|
|
invar := &interfaces.EqualsInvariant{
|
|
Expr: obj,
|
|
Type: typ,
|
|
}
|
|
invariants = append(invariants, invar)
|
|
}
|
|
|
|
// collect all the invariants of the body
|
|
if obj.Body != nil {
|
|
expr2Ord := []string{}
|
|
expr2Map := map[string]interfaces.Expr{}
|
|
for i, arg := range obj.Args {
|
|
expr2Ord = append(expr2Ord, arg.Name)
|
|
expr2Map[arg.Name] = obj.params[i]
|
|
}
|
|
funcInvariant := &interfaces.EqualityWrapFuncInvariant{
|
|
Expr1: obj,
|
|
Expr2Map: expr2Map,
|
|
Expr2Ord: expr2Ord,
|
|
Expr2Out: obj.Body,
|
|
}
|
|
invariants = append(invariants, funcInvariant)
|
|
|
|
invars, err := obj.Body.Unify()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
invariants = append(invariants, invars...)
|
|
}
|
|
|
|
// return type must be equal to the body expression
|
|
if obj.Body != nil && obj.Return != nil {
|
|
invar := &interfaces.EqualsInvariant{
|
|
Expr: obj.Body,
|
|
Type: obj.Return,
|
|
}
|
|
invariants = append(invariants, invar)
|
|
}
|
|
|
|
// TODO: should we try and add invariants for obj.Args?
|
|
|
|
// We don't want to Unify against the *original* ExprFunc pointer, since
|
|
// we want the copy of it which is what ExprCall points to. We don't
|
|
// call this Unify() method from anywhere except from when it's within
|
|
// an ExprCall. We don't call it from StmtFunc which is just a container
|
|
// for it. This is basically used as a helper function! By the time this
|
|
// is called, we've already made an obj.function which is the copied,
|
|
// instantiated version of obj.Function that we are going to use.
|
|
if obj.Function != nil {
|
|
fn := obj.function // instantiated copy of obj.Function
|
|
polyFn, ok := fn.(interfaces.PolyFunc) // is it statically polymorphic?
|
|
if ok {
|
|
// We just run the Unify() method of the ExprFunc if it
|
|
// happens to have one. Get the list of Invariants, and
|
|
// return them directly.
|
|
invars, err := polyFn.Unify(obj)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
invariants = append(invariants, invars...)
|
|
}
|
|
|
|
// It's okay to attempt to get a static signature too, if it's
|
|
// nil or has a variant (polymorphic funcs) then it's ignored.
|
|
sig := fn.Info().Sig
|
|
if sig != nil && !sig.HasVariant() {
|
|
invar := &interfaces.EqualsInvariant{
|
|
Expr: obj,
|
|
Type: sig,
|
|
}
|
|
invariants = append(invariants, invar)
|
|
}
|
|
}
|
|
|
|
//if len(obj.Values) > 0
|
|
ors := []interfaces.Invariant{} // solve only one from this list
|
|
once := false
|
|
for _, fn := range obj.Values {
|
|
typ := fn.Type()
|
|
if typ.Kind != types.KindFunc {
|
|
// programming error
|
|
return nil, fmt.Errorf("overloaded value was not of kind func")
|
|
}
|
|
|
|
// NOTE: if we have more than one possibility here, *and* at
|
|
// least one of them contains a variant, *and* at least one does
|
|
// not, then we *can't* use any of these until the unification
|
|
// engine supports variants, because instead of an "OR" between
|
|
// multiple possibilities, this will look like fewer
|
|
// possibilities exist, and that the answer must be one of them!
|
|
// TODO: Previously, we just skipped all of these invariants! If
|
|
// we get examples that don't work well, just abandon this part.
|
|
if !typ.HasVariant() {
|
|
invar := &interfaces.EqualsInvariant{
|
|
Expr: obj,
|
|
Type: typ,
|
|
}
|
|
ors = append(ors, invar) // one solution added!
|
|
} else if !once {
|
|
// Add at *most* only one any invariant in an exclusive
|
|
// set, otherwise two or more possibilities will have
|
|
// equivalent answers.
|
|
anyInvar := &interfaces.AnyInvariant{
|
|
Expr: obj,
|
|
}
|
|
ors = append(ors, anyInvar)
|
|
once = true
|
|
}
|
|
|
|
} // end results loop
|
|
if len(ors) > 0 {
|
|
var invar interfaces.Invariant = &interfaces.ExclusiveInvariant{
|
|
Invariants: ors, // one and only one of these should be true
|
|
}
|
|
if len(ors) == 1 {
|
|
invar = ors[0] // there should only be one
|
|
}
|
|
invariants = append(invariants, invar)
|
|
}
|
|
|
|
return invariants, nil
|
|
}
|
|
|
|
// Graph returns the reactive function graph which is expressed by this node. It
|
|
// includes any vertices produced by this node, and the appropriate edges to any
|
|
// vertices that are produced by its children. Nodes which fulfill the Expr
|
|
// interface directly produce vertices (and possible children) where as nodes
|
|
// 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) {
|
|
// 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 funcValueFunc interfaces.Func
|
|
if obj.Body != nil {
|
|
funcValueFunc = structs.FuncValueToConstFunc(&full.FuncValue{
|
|
V: func(innerTxn interfaces.Txn, 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]
|
|
}
|
|
|
|
// Create a subgraph from the lambda's body, instantiating the
|
|
// lambda's parameters with the args and the other variables
|
|
// with the nodes in the captured environment.
|
|
subgraph, bodyFunc, err := obj.Body.Graph(extendedEnv)
|
|
if err != nil {
|
|
return nil, errwrap.Wrapf(err, "could not create the lambda body's subgraph")
|
|
}
|
|
|
|
innerTxn.AddGraph(subgraph)
|
|
|
|
return bodyFunc, nil
|
|
},
|
|
T: obj.typ,
|
|
})
|
|
} else if obj.Function != nil {
|
|
// obj.function is a node which transforms input values into
|
|
// an output value, but we need to construct a node which takes no
|
|
// inputs and produces a FuncValue, so we need to wrap it.
|
|
|
|
funcValueFunc = structs.FuncValueToConstFunc(&full.FuncValue{
|
|
V: func(txn interfaces.Txn, args []interfaces.Func) (interfaces.Func, error) {
|
|
// Copy obj.function so that the underlying ExprFunc.function gets
|
|
// refreshed with a new ExprFunc.Function() call. Otherwise, multiple
|
|
// calls to this function will share the same Func.
|
|
exprCopy, err := obj.Copy()
|
|
if err != nil {
|
|
return nil, errwrap.Wrapf(err, "could not copy expression")
|
|
}
|
|
funcExprCopy, ok := exprCopy.(*ExprFunc)
|
|
if !ok {
|
|
// programming error
|
|
return nil, errwrap.Wrapf(err, "ExprFunc.Copy() does not produce an ExprFunc")
|
|
}
|
|
valueTransformingFunc := funcExprCopy.function
|
|
txn.AddVertex(valueTransformingFunc)
|
|
for i, arg := range args {
|
|
argName := obj.typ.Ord[i]
|
|
txn.AddEdge(arg, valueTransformingFunc, &interfaces.FuncEdge{
|
|
Args: []string{argName},
|
|
})
|
|
}
|
|
return valueTransformingFunc, nil
|
|
},
|
|
T: obj.typ,
|
|
})
|
|
} else /* len(obj.Values) > 0 */ {
|
|
index, err := langutil.FnMatch(obj.typ, obj.Values)
|
|
if err != nil {
|
|
// programming error
|
|
// since type checking succeeded at this point, there should only be one match
|
|
return nil, nil, errwrap.Wrapf(err, "multiple matches found")
|
|
}
|
|
simpleFn := obj.Values[index]
|
|
simpleFn.T = obj.typ
|
|
|
|
funcValueFunc = structs.SimpleFnToConstFunc(fmt.Sprintf("title: %s", obj.Title), simpleFn)
|
|
}
|
|
|
|
outerGraph, err := pgraph.NewGraph("ExprFunc")
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
outerGraph.AddVertex(funcValueFunc)
|
|
return outerGraph, funcValueFunc, 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 {
|
|
// 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.(*full.FuncValue)
|
|
//if !worked {
|
|
// return fmt.Errorf("expected a FuncValue")
|
|
//}
|
|
//obj.V = funcValue.V
|
|
return nil
|
|
}
|
|
|
|
// Value returns the value of this expression in our type system. This will
|
|
// usually only be valid once the engine has run and values have been produced.
|
|
// This might get called speculatively (early) during unification to learn more.
|
|
// This particular value is always known since it is a constant.
|
|
func (obj *ExprFunc) Value() (types.Value, error) {
|
|
panic("ExprFunc does not store its latest value because resources don't yet have function fields.")
|
|
//// TODO: implement speculative value lookup (if not already sufficient)
|
|
//return &full.FuncValue{
|
|
// V: obj.V,
|
|
// T: obj.typ,
|
|
//}, nil
|
|
}
|
|
|
|
// ExprCall is a representation of a function call. This does not represent the
|
|
// declaration or implementation of a new function value. This struct has an
|
|
// analogous symmetry with ExprVar.
|
|
type ExprCall struct {
|
|
data *interfaces.Data
|
|
scope *interfaces.Scope // store for referencing this later
|
|
typ *types.Type
|
|
|
|
expr interfaces.Expr // copy of what we're calling
|
|
orig *ExprCall // original pointer to this
|
|
|
|
V types.Value // stored result (set with SetValue)
|
|
|
|
// Name of the function to be called. We look for it in the scope.
|
|
Name string
|
|
// Args are the list of inputs to this function.
|
|
Args []interfaces.Expr // list of args in parsed order
|
|
// Var specifies whether the function being called is a lambda in a var.
|
|
Var bool
|
|
}
|
|
|
|
// String returns a short representation of this expression.
|
|
func (obj *ExprCall) String() string {
|
|
var s []string
|
|
for _, x := range obj.Args {
|
|
s = append(s, fmt.Sprintf("%s", x.String()))
|
|
}
|
|
return fmt.Sprintf("call:%s(%s)", obj.Name, strings.Join(s, ", "))
|
|
}
|
|
|
|
// Apply is a general purpose iterator method that operates on any AST node. It
|
|
// is not used as the primary AST traversal function because it is less readable
|
|
// and easy to reason about than manually implementing traversal for each node.
|
|
// Nevertheless, it is a useful facility for operations that might only apply to
|
|
// a select number of node types, since they won't need extra noop iterators...
|
|
func (obj *ExprCall) Apply(fn func(interfaces.Node) error) error {
|
|
for _, x := range obj.Args {
|
|
if err := x.Apply(fn); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return fn(obj)
|
|
}
|
|
|
|
// Init initializes this branch of the AST, and returns an error if it fails to
|
|
// validate.
|
|
func (obj *ExprCall) Init(data *interfaces.Data) error {
|
|
obj.data = data
|
|
for _, x := range obj.Args {
|
|
if err := x.Init(data); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Interpolate returns a new node (aka a copy) once it has been expanded. This
|
|
// generally increases the size of the AST when it is used. It calls Interpolate
|
|
// on any child elements and builds the new node with those new node contents.
|
|
func (obj *ExprCall) Interpolate() (interfaces.Expr, error) {
|
|
args := []interfaces.Expr{}
|
|
for _, x := range obj.Args {
|
|
interpolated, err := x.Interpolate()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
args = append(args, interpolated)
|
|
}
|
|
|
|
orig := obj
|
|
if obj.orig != nil { // preserve the original pointer (the identifier!)
|
|
orig = obj.orig
|
|
}
|
|
|
|
return &ExprCall{
|
|
data: obj.data,
|
|
scope: obj.scope,
|
|
typ: obj.typ,
|
|
// XXX: Copy copies this, do we want to here as well? (or maybe
|
|
// we want to do it here, but not in Copy?)
|
|
expr: obj.expr,
|
|
orig: orig,
|
|
V: obj.V,
|
|
Name: obj.Name,
|
|
Args: args,
|
|
Var: obj.Var,
|
|
}, nil
|
|
}
|
|
|
|
// Copy returns a light copy of this struct. Anything static will not be copied.
|
|
func (obj *ExprCall) Copy() (interfaces.Expr, error) {
|
|
copied := false
|
|
copiedArgs := false
|
|
args := []interfaces.Expr{}
|
|
for _, x := range obj.Args {
|
|
cp, err := x.Copy()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if cp != x { // must have been copied, or pointer would be same
|
|
copiedArgs = true
|
|
}
|
|
args = append(args, cp)
|
|
}
|
|
if copiedArgs {
|
|
copied = true
|
|
} else {
|
|
args = obj.Args // don't re-package it unnecessarily!
|
|
}
|
|
|
|
var err error
|
|
var expr interfaces.Expr
|
|
if obj.expr != nil {
|
|
expr, err = obj.expr.Copy()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if expr != obj.expr {
|
|
copied = true
|
|
}
|
|
}
|
|
|
|
// TODO: is this necessary? (I doubt it even gets used.)
|
|
orig := obj
|
|
if obj.orig != nil { // preserve the original pointer (the identifier!)
|
|
orig = obj.orig
|
|
copied = true // TODO: is this what we want?
|
|
}
|
|
|
|
// FIXME: do we want to allow a static ExprCall ?
|
|
if !copied { // it's static
|
|
return obj, nil
|
|
}
|
|
return &ExprCall{
|
|
data: obj.data,
|
|
scope: obj.scope,
|
|
typ: obj.typ,
|
|
expr: expr, // it seems that we need to copy this for it to work
|
|
orig: orig,
|
|
V: obj.V,
|
|
Name: obj.Name,
|
|
Args: args,
|
|
Var: obj.Var,
|
|
}, nil
|
|
}
|
|
|
|
// Ordering returns a graph of the scope ordering that represents the data flow.
|
|
// This can be used in SetScope so that it knows the correct order to run it in.
|
|
func (obj *ExprCall) Ordering(produces map[string]interfaces.Node) (*pgraph.Graph, map[interfaces.Node]string, error) {
|
|
graph, err := pgraph.NewGraph("ordering")
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
graph.AddVertex(obj)
|
|
|
|
if obj.Name == "" {
|
|
return nil, nil, fmt.Errorf("missing call name")
|
|
}
|
|
uid := funcOrderingPrefix + obj.Name // ordering id
|
|
if obj.Var { // lambda
|
|
uid = varOrderingPrefix + obj.Name // ordering id
|
|
}
|
|
|
|
cons := make(map[interfaces.Node]string)
|
|
cons[obj] = uid
|
|
|
|
node, exists := produces[uid]
|
|
if exists {
|
|
edge := &pgraph.SimpleEdge{Name: "exprcallname"}
|
|
graph.AddEdge(node, obj, edge) // prod -> cons
|
|
}
|
|
|
|
for _, node := range obj.Args {
|
|
g, c, err := node.Ordering(produces)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
graph.AddGraph(g) // add in the child graph
|
|
|
|
// additional constraint...
|
|
edge := &pgraph.SimpleEdge{Name: "exprcallargs1"}
|
|
graph.AddEdge(node, obj, edge) // prod -> cons
|
|
|
|
for k, v := range c { // c is consumes
|
|
x, exists := cons[k]
|
|
if exists && v != x {
|
|
return nil, nil, fmt.Errorf("consumed value is different, got `%+v`, expected `%+v`", x, v)
|
|
}
|
|
cons[k] = v // add to map
|
|
|
|
n, exists := produces[v]
|
|
if !exists {
|
|
continue
|
|
}
|
|
edge := &pgraph.SimpleEdge{Name: "exprcallargs2"}
|
|
graph.AddEdge(n, k, edge)
|
|
}
|
|
}
|
|
|
|
return graph, cons, nil
|
|
}
|
|
|
|
// SetScope stores the scope for later use in this resource and its children,
|
|
// which it propagates this downwards to. This particular function has been
|
|
// heavily optimized to work correctly with calling functions with the correct
|
|
// args. Edit cautiously and with extensive testing.
|
|
func (obj *ExprCall) SetScope(scope *interfaces.Scope, sctx map[string]interfaces.Expr) error {
|
|
if scope == nil {
|
|
scope = interfaces.EmptyScope()
|
|
}
|
|
obj.scope = scope
|
|
if obj.data.Debug {
|
|
obj.data.Logf("call: %s(%t): scope: variables: %+v", obj.Name, obj.Var, obj.scope.Variables)
|
|
obj.data.Logf("call: %s(%t): scope: functions: %+v", obj.Name, obj.Var, obj.scope.Functions)
|
|
}
|
|
|
|
// scope-check the arguments
|
|
for _, x := range obj.Args {
|
|
if err := x.SetScope(scope, sctx); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
var prefixedName string
|
|
var target interfaces.Expr
|
|
if obj.Var {
|
|
// The call looks like $f().
|
|
prefixedName = interfaces.VarPrefix + obj.Name
|
|
if f, exists := sctx[obj.Name]; exists {
|
|
// $f refers to a parameter bound by an enclosing lambda definition.
|
|
target = f
|
|
} else {
|
|
f, exists := obj.scope.Variables[obj.Name]
|
|
if !exists {
|
|
return fmt.Errorf("func `%s` does not exist in this scope", prefixedName)
|
|
}
|
|
target = f
|
|
}
|
|
} else {
|
|
// The call looks like f().
|
|
prefixedName = obj.Name
|
|
f, exists := obj.scope.Functions[obj.Name]
|
|
if !exists {
|
|
return fmt.Errorf("func `%s` does not exist in this scope", prefixedName)
|
|
}
|
|
target = f
|
|
}
|
|
|
|
if polymorphicTarget, isPolymorphic := target.(*ExprPoly); isPolymorphic {
|
|
// This function call refers to a polymorphic function
|
|
// expression. Those expressions can be instantiated at
|
|
// different types in different parts of the program, so that
|
|
// the definition we found has a "polymorphic" type.
|
|
//
|
|
// This particular call is one of the parts of the program which
|
|
// uses the polymorphic expression as a single, "monomorphic"
|
|
// type. We make a copy of the definition, and later each copy
|
|
// will be type-checked separately.
|
|
monomorphicTarget, err := polymorphicTarget.Definition.Copy()
|
|
if err != nil {
|
|
return errwrap.Wrapf(err, "could not copy the function definition `%s`", prefixedName)
|
|
}
|
|
|
|
// This call now has the only reference to monomorphicTarget, so
|
|
// it is our responsibility to scope-check it.
|
|
if err := monomorphicTarget.SetScope(scope, sctx); err != nil {
|
|
return errwrap.Wrapf(err, "scope-checking the function definition `%s`", prefixedName)
|
|
}
|
|
|
|
if obj.data.Debug {
|
|
obj.data.Logf("call $%s(): set scope: func pointer: %p (polymorphic) -> %p (copy)", prefixedName, &polymorphicTarget, &monomorphicTarget)
|
|
}
|
|
|
|
obj.expr = monomorphicTarget
|
|
} else {
|
|
// This call refers to a monomorphic expression which has
|
|
// already been scope-checked, so we don't need to scope-check
|
|
// it again.
|
|
obj.expr = target
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// SetType is used to set the type of this expression once it is known. This
|
|
// usually happens during type unification, but it can also happen during
|
|
// parsing if a type is specified explicitly. Since types are static and don't
|
|
// change on expressions, if you attempt to set a different type than what has
|
|
// previously been set (when not initially known) this will error. Remember that
|
|
// for this function expression, the type is the *return type* of the function,
|
|
// not the full type of the function signature.
|
|
func (obj *ExprCall) SetType(typ *types.Type) error {
|
|
if obj.typ != nil {
|
|
return obj.typ.Cmp(typ) // if not set, ensure it doesn't change
|
|
}
|
|
obj.typ = typ // set
|
|
return nil
|
|
}
|
|
|
|
// Type returns the type of this expression, which is the return type of the
|
|
// function call.
|
|
func (obj *ExprCall) Type() (*types.Type, error) {
|
|
if obj.expr == nil {
|
|
// possible programming error
|
|
return nil, fmt.Errorf("call doesn't contain an expr pointer yet")
|
|
}
|
|
|
|
// function specific code follows...
|
|
fn, isFn := obj.expr.(*ExprFunc)
|
|
if !isFn {
|
|
if obj.typ == nil {
|
|
return nil, interfaces.ErrTypeCurrentlyUnknown
|
|
}
|
|
return obj.typ, nil
|
|
}
|
|
|
|
sig, err := fn.Type()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if typ := sig.Out; typ != nil && !typ.HasVariant() && obj.typ == nil {
|
|
return typ, nil // speculate!
|
|
}
|
|
|
|
// speculate if a partial return type is known
|
|
if fn.Body != nil {
|
|
if fn.Return != nil && obj.typ == nil {
|
|
return fn.Return, nil
|
|
}
|
|
|
|
if typ, err := fn.Body.Type(); err == nil && obj.typ == nil {
|
|
return typ, nil
|
|
}
|
|
}
|
|
|
|
if fn.Function != nil {
|
|
// is it statically polymorphic or not?
|
|
_, isPoly := fn.function.(interfaces.PolyFunc)
|
|
if !isPoly && obj.typ == nil {
|
|
if info := fn.function.Info(); info != nil {
|
|
if sig := info.Sig; sig != nil {
|
|
if typ := sig.Out; typ != nil && !typ.HasVariant() {
|
|
return typ, nil // speculate!
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// TODO: we could also check if a truly polymorphic type has
|
|
// consistent return values across all possibilities available
|
|
}
|
|
|
|
//if len(fn.Values) > 0
|
|
// check to see if we have a unique return type
|
|
for _, fn := range fn.Values {
|
|
typ := fn.Type()
|
|
if typ == nil || typ.Out == nil {
|
|
continue // skip, not available yet
|
|
}
|
|
if obj.typ == nil {
|
|
return typ, nil
|
|
}
|
|
}
|
|
|
|
if obj.typ == nil {
|
|
return nil, interfaces.ErrTypeCurrentlyUnknown
|
|
}
|
|
return obj.typ, nil
|
|
}
|
|
|
|
// Unify returns the list of invariants that this node produces. It recursively
|
|
// calls Unify on any children elements that exist in the AST, and returns the
|
|
// collection to the caller.
|
|
func (obj *ExprCall) Unify() ([]interfaces.Invariant, error) {
|
|
if obj.expr == nil {
|
|
// possible programming error
|
|
return nil, fmt.Errorf("call doesn't contain an expr pointer yet")
|
|
}
|
|
|
|
var invariants []interfaces.Invariant
|
|
|
|
// if this was set explicitly by the parser
|
|
if obj.typ != nil {
|
|
invar := &interfaces.EqualsInvariant{
|
|
Expr: obj,
|
|
Type: obj.typ,
|
|
}
|
|
invariants = append(invariants, invar)
|
|
}
|
|
|
|
//if obj.typ != nil { // XXX: i think this is probably incorrect...
|
|
// invar := &interfaces.EqualsInvariant{
|
|
// Expr: obj.expr,
|
|
// Type: obj.typ,
|
|
// }
|
|
// invariants = append(invariants, invar)
|
|
//}
|
|
|
|
// collect all the invariants of each sub-expression
|
|
for _, x := range obj.Args {
|
|
invars, err := x.Unify()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
invariants = append(invariants, invars...)
|
|
}
|
|
|
|
// I think I need to associate the func call with the actual func
|
|
// expression somehow... This is because when I try to do unification in
|
|
// a function like printf, I need to be able to know which args (values)
|
|
// this particular version of the function I'm calling is associated
|
|
// with. So I need to know the linkage. It has to be added here, since
|
|
// ExprFunc doesn't know who's calling it. And why would it even want to
|
|
// know who's calling it?
|
|
argsCopy := []interfaces.Expr{}
|
|
for _, arg := range obj.Args {
|
|
argsCopy = append(argsCopy, arg)
|
|
}
|
|
invar := &interfaces.CallFuncArgsValueInvariant{
|
|
Expr: obj,
|
|
Func: trueCallee(obj.expr),
|
|
Args: argsCopy,
|
|
}
|
|
invariants = append(invariants, invar)
|
|
|
|
// add the invariants from the actual function that we'll be using...
|
|
// don't add them from the pre-copied function, which is never used...
|
|
invars, err := obj.expr.Unify()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
invariants = append(invariants, invars...)
|
|
|
|
anyInvar := &interfaces.AnyInvariant{ // TODO: maybe this isn't needed?
|
|
Expr: obj.expr,
|
|
}
|
|
invariants = append(invariants, anyInvar)
|
|
|
|
// our type should equal the return type of the called function, and our
|
|
// argument types should be equal to the types of the parameters of the
|
|
// function
|
|
// arg0, arg1, arg2
|
|
expr2Ord := []string{}
|
|
expr2Map := map[string]interfaces.Expr{}
|
|
for i, argExpr := range obj.Args {
|
|
argName := fmt.Sprintf("arg%d", i)
|
|
expr2Ord = append(expr2Ord, argName)
|
|
expr2Map[argName] = argExpr
|
|
}
|
|
funcInvar := &interfaces.EqualityWrapFuncInvariant{
|
|
Expr1: obj.expr,
|
|
Expr2Map: expr2Map,
|
|
Expr2Ord: expr2Ord,
|
|
Expr2Out: obj,
|
|
}
|
|
invariants = append(invariants, funcInvar)
|
|
|
|
// function specific code follows...
|
|
fn, isFn := obj.expr.(*ExprFunc)
|
|
if !isFn {
|
|
return invariants, nil
|
|
}
|
|
|
|
// if we know the return type, it should match our type
|
|
if fn.Body != nil && fn.Return != nil {
|
|
invar := &interfaces.EqualsInvariant{
|
|
Expr: obj, // return type from calling the function
|
|
Type: fn.Return, // specified return type
|
|
}
|
|
invariants = append(invariants, invar)
|
|
}
|
|
|
|
// If ExprFunc is built from mcl code. Note: Unify on fn.Body is called
|
|
// from within StmtBind or StmtFunc, depending on whether it's a lambda.
|
|
// Instead, we'll block it there, and run it from here instead...
|
|
if fn.Body != nil {
|
|
if i, j := len(obj.Args), len(fn.Args); i != j {
|
|
return nil, fmt.Errorf("func `%s` is being called with %d args, but expected %d args", obj.Name, i, j)
|
|
}
|
|
|
|
// do the specified args match any specified arg types?
|
|
for i, x := range fn.Args {
|
|
if x.Type == nil { // unknown type
|
|
continue
|
|
}
|
|
invar := &interfaces.EqualsInvariant{
|
|
Expr: obj.Args[i],
|
|
Type: x.Type,
|
|
}
|
|
invariants = append(invariants, invar)
|
|
}
|
|
|
|
// do the variables in the body match the arg types ?
|
|
// XXX: test this section to ensure it's the right scope (should
|
|
// it be getScope(fn) ?) and is it what we want...
|
|
for _, x := range fn.Args {
|
|
expr, exists := obj.scope.Variables[x.Name] // XXX: test!
|
|
if !exists || x.Type == nil {
|
|
continue
|
|
}
|
|
invar := &interfaces.EqualsInvariant{
|
|
Expr: expr,
|
|
Type: x.Type,
|
|
}
|
|
invariants = append(invariants, invar)
|
|
}
|
|
|
|
// build the reference to ourself if we have undetermined field types
|
|
mapped := make(map[string]interfaces.Expr)
|
|
ordered := []string{}
|
|
for i, x := range fn.Args {
|
|
mapped[x.Name] = obj.Args[i]
|
|
ordered = append(ordered, x.Name)
|
|
}
|
|
|
|
// determine the type of the function itself
|
|
invariant := &interfaces.EqualityWrapFuncInvariant{
|
|
Expr1: fn, // unique id for this expression (a pointer)
|
|
Expr2Map: mapped,
|
|
Expr2Ord: ordered,
|
|
Expr2Out: fn.Body,
|
|
}
|
|
invariants = append(invariants, invariant)
|
|
|
|
//if fn.Return != nil {
|
|
// invariant := &interfaces.EqualityWrapFuncInvariant{
|
|
// Expr1: fn, // unique id for this expression (a pointer)
|
|
// Expr2Map: mapped,
|
|
// Expr2Ord: ordered,
|
|
// Expr2Out: fn.Return, // XXX: ???
|
|
// }
|
|
// invariants = append(invariants, invariant)
|
|
//}
|
|
|
|
// TODO: Do we need to add an EqualityWrapCallInvariant here?
|
|
|
|
// the return type of this call expr, should match the body type
|
|
invar := &interfaces.EqualityInvariant{
|
|
Expr1: obj,
|
|
Expr2: fn.Body,
|
|
}
|
|
invariants = append(invariants, invar)
|
|
|
|
//if fn.Return != nil {
|
|
// invar := &interfaces.EqualityInvariant{
|
|
// Expr1: obj,
|
|
// Expr2: fn.Return, XXX: ???
|
|
// }
|
|
// invariants = append(invariants, invar)
|
|
//}
|
|
|
|
return invariants, nil
|
|
}
|
|
|
|
//if fn.Function != nil ...
|
|
|
|
var results []*types.Type
|
|
|
|
argGen := func(x int) (string, error) {
|
|
// assume (incorrectly?) for now...
|
|
return util.NumToAlpha(x), nil
|
|
}
|
|
if fn.Function != nil {
|
|
namedArgsFn, ok := fn.function.(interfaces.NamedArgsFunc) // are the args named?
|
|
if ok {
|
|
argGen = namedArgsFn.ArgGen // func(int) string
|
|
}
|
|
}
|
|
|
|
// build partial type and partial input values to aid in filtering...
|
|
argNames := []string{}
|
|
mapped := make(map[string]*types.Type)
|
|
partialValues := []types.Value{}
|
|
for i := range obj.Args {
|
|
name, err := argGen(i) // get the Nth arg name
|
|
if err != nil {
|
|
return nil, errwrap.Wrapf(err, "error getting arg name #%d for func `%s`", i, obj.Name)
|
|
}
|
|
if name == "" {
|
|
// possible programming error
|
|
return nil, fmt.Errorf("can't get arg name #%d for func `%s`", i, obj.Name)
|
|
}
|
|
argNames = append(argNames, name)
|
|
mapped[name] = nil // unknown type
|
|
partialValues = append(partialValues, nil) // XXX: is this safe?
|
|
|
|
// optimization: if zeroth arg is a static string, specify this!
|
|
// TODO: this is a more specialized version of the next check...
|
|
if x, ok := obj.Args[0].(*ExprStr); i == 0 && ok { // is static?
|
|
mapped[name], _ = x.Type()
|
|
partialValues[i], _ = x.Value() // store value
|
|
}
|
|
|
|
// optimization: if type is already known, specify it now!
|
|
if t, err := obj.Args[i].Type(); err == nil { // is known?
|
|
mapped[name] = t
|
|
// if value is completely static, pass it in now!
|
|
if v, err := obj.Args[i].Value(); err == nil {
|
|
partialValues[i] = v // store value
|
|
}
|
|
}
|
|
}
|
|
out, err := obj.Type() // do we know the return type yet?
|
|
if err != nil {
|
|
out = nil // just to make sure...
|
|
}
|
|
// partial type can have some type components that are nil!
|
|
// this means they are not yet known at this time...
|
|
partialType := &types.Type{
|
|
Kind: types.KindFunc,
|
|
Map: mapped,
|
|
Ord: argNames,
|
|
Out: out, // possibly nil
|
|
}
|
|
|
|
var polyFn interfaces.PolyFunc
|
|
var ok bool
|
|
if fn.Function != nil {
|
|
polyFn, ok = fn.function.(interfaces.PolyFunc) // is it statically polymorphic?
|
|
}
|
|
|
|
if fn.Function != nil && ok {
|
|
// We just run the Unify() method of the ExprFunc if it happens
|
|
// to have one. Get the list of Invariants, and return them
|
|
// directly. We want to run this unification inside of ExprCall
|
|
// and not in ExprFunc, because it's only in ExprCall that we
|
|
// have the instantiated copy of the ExprFunc that we actually
|
|
// build and unify against and which has the correct pointer
|
|
// now, where as the ExprFunc pointer isn't what we unify with!
|
|
invars, err := polyFn.Unify(obj.expr)
|
|
if err != nil {
|
|
return nil, errwrap.Wrapf(err, "polymorphic unification for func `%s` could not be done", obj.Name)
|
|
}
|
|
invariants = append(invariants, invars...)
|
|
|
|
} else if fn.Function != nil && !ok {
|
|
sig := fn.function.Info().Sig
|
|
if sig == nil {
|
|
// this can happen if it's incorrectly implemented
|
|
return nil, errwrap.Wrapf(err, "unification for func `%s` returned nil signature", obj.Name)
|
|
}
|
|
results = []*types.Type{sig} // only one (non-polymorphic)
|
|
}
|
|
|
|
// if len(fn.Values) > 0
|
|
for _, f := range fn.Values {
|
|
// FIXME: can we filter based on partialValues too?
|
|
// TODO: if status is "both", should we skip as too difficult?
|
|
_, err := f.T.ComplexCmp(partialType)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
results = append(results, f.T)
|
|
}
|
|
|
|
// build invariants from a list of possible types
|
|
ors := []interfaces.Invariant{} // solve only one from this list
|
|
// each of these is a different possible signature
|
|
|
|
for _, typ := range results {
|
|
if typ.Kind != types.KindFunc {
|
|
panic("overloaded result was not of kind func")
|
|
}
|
|
|
|
// XXX: how do we deal with template returning a variant?
|
|
// XXX: i think we need more invariant types, and if it's
|
|
// going to be a variant, just return no results, and the
|
|
// defaults from the engine should just match it anyways!
|
|
if typ.HasVariant() { // XXX: ¯\_(ツ)_/¯
|
|
//continue // XXX: alternate strategy...
|
|
//return nil, fmt.Errorf("variant type not yet supported, got: %+v", typ) // XXX: old strategy
|
|
}
|
|
if typ.Kind == types.KindVariant { // XXX: ¯\_(ツ)_/¯
|
|
// XXX: maybe needed to avoid an oversimplified exclusive!
|
|
anyInvar := &interfaces.AnyInvariant{
|
|
Expr: fn, // TODO: fn or obj ?
|
|
}
|
|
ors = append(ors, anyInvar)
|
|
continue // can't deal with raw variant a.t.m.
|
|
}
|
|
|
|
if i, j := len(typ.Ord), len(obj.Args); i != j {
|
|
continue // this signature won't work for us, skip!
|
|
}
|
|
|
|
// what would a set of invariants for this sig look like?
|
|
var invars []interfaces.Invariant
|
|
|
|
// use Map and Ord for Input (Kind == Function)
|
|
for i, x := range typ.Ord {
|
|
if typ.Map[x].HasVariant() { // XXX: ¯\_(ツ)_/¯
|
|
// TODO: maybe this isn't needed?
|
|
invar := &interfaces.AnyInvariant{
|
|
Expr: obj.Args[i],
|
|
}
|
|
invars = append(invars, invar)
|
|
continue
|
|
}
|
|
invar := &interfaces.EqualsInvariant{
|
|
Expr: obj.Args[i],
|
|
Type: typ.Map[x], // type of arg
|
|
}
|
|
invars = append(invars, invar)
|
|
}
|
|
if typ.Out != nil {
|
|
// this expression should equal the output type of the function
|
|
if typ.Out.HasVariant() { // XXX: ¯\_(ツ)_/¯
|
|
// TODO: maybe this isn't needed?
|
|
invar := &interfaces.AnyInvariant{
|
|
Expr: obj,
|
|
}
|
|
invars = append(invars, invar)
|
|
} else {
|
|
invar := &interfaces.EqualsInvariant{
|
|
Expr: obj,
|
|
Type: typ.Out,
|
|
}
|
|
invars = append(invars, invar)
|
|
}
|
|
}
|
|
|
|
// add more invariants to link the partials...
|
|
mapped := make(map[string]interfaces.Expr)
|
|
ordered := []string{}
|
|
for pos, x := range obj.Args {
|
|
name := argNames[pos]
|
|
mapped[name] = x
|
|
ordered = append(ordered, name)
|
|
}
|
|
|
|
if !typ.HasVariant() { // XXX: ¯\_(ツ)_/¯
|
|
funcInvariant := &interfaces.EqualsInvariant{
|
|
Expr: fn,
|
|
Type: typ,
|
|
}
|
|
invars = append(invars, funcInvariant)
|
|
} else {
|
|
// XXX: maybe needed to avoid an oversimplified exclusive!
|
|
anyInvar := &interfaces.AnyInvariant{
|
|
Expr: fn, // TODO: fn or obj ?
|
|
}
|
|
invars = append(invars, anyInvar)
|
|
}
|
|
// Note: The usage of this invariant is different from the other
|
|
// wrap* invariants, because in this case, the expression type
|
|
// is the return type which is produced, where as the entire
|
|
// function itself has its own type which includes the types of
|
|
// the input arguments...
|
|
invar := &interfaces.EqualityWrapFuncInvariant{
|
|
Expr1: fn,
|
|
Expr2Map: mapped,
|
|
Expr2Ord: ordered,
|
|
Expr2Out: obj, // type of expression is return type of function
|
|
}
|
|
invars = append(invars, invar)
|
|
|
|
// all of these need to be true together
|
|
and := &interfaces.ConjunctionInvariant{
|
|
Invariants: invars,
|
|
}
|
|
|
|
ors = append(ors, and) // one solution added!
|
|
} // end results loop
|
|
|
|
// don't error here, we might not want to add any invariants!
|
|
//if len(results) == 0 {
|
|
// return nil, fmt.Errorf("can't find any valid signatures that match func `%s`", obj.Name)
|
|
//}
|
|
if len(ors) > 0 {
|
|
var invar interfaces.Invariant = &interfaces.ExclusiveInvariant{
|
|
Invariants: ors, // one and only one of these should be true
|
|
}
|
|
if len(ors) == 1 {
|
|
invar = ors[0] // there should only be one
|
|
}
|
|
invariants = append(invariants, invar)
|
|
}
|
|
|
|
return invariants, nil
|
|
}
|
|
|
|
// Graph returns the reactive function graph which is expressed by this node. It
|
|
// includes any vertices produced by this node, and the appropriate edges to any
|
|
// vertices that are produced by its children. Nodes which fulfill the Expr
|
|
// interface directly produce vertices (and possible children) where as nodes
|
|
// 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, 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) {
|
|
if obj.expr == nil {
|
|
// possible programming error
|
|
return nil, nil, fmt.Errorf("call doesn't contain an expr pointer yet")
|
|
}
|
|
|
|
graph, err := pgraph.NewGraph("call")
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
ftyp, err := obj.expr.Type()
|
|
if err != nil {
|
|
return nil, nil, errwrap.Wrapf(err, "could not get the type of the function")
|
|
}
|
|
|
|
// Find the vertex which produces the FuncValue.
|
|
var funcValueFunc interfaces.Func
|
|
if _, isParam := obj.expr.(*ExprParam); isParam {
|
|
// The function being called is a parameter from the surrounding function.
|
|
// We should be able to find this parameter in the environment.
|
|
paramFunc, exists := env[obj.Name]
|
|
if !exists {
|
|
return nil, nil, fmt.Errorf("param `%s` is not in the environment", obj.Name)
|
|
}
|
|
graph.AddVertex(paramFunc)
|
|
funcValueFunc = paramFunc
|
|
} else {
|
|
// The function being called is a top-level definition. The parameters which
|
|
// are visible at this use site must not be visible at the definition site,
|
|
// so we pass an empty environment.
|
|
emptyEnv := map[string]interfaces.Func{}
|
|
exprGraph, topLevelFunc, err := obj.expr.Graph(emptyEnv)
|
|
if err != nil {
|
|
return nil, nil, errwrap.Wrapf(err, "could not get the graph for the expr pointer")
|
|
}
|
|
graph.AddGraph(exprGraph)
|
|
funcValueFunc = topLevelFunc
|
|
}
|
|
|
|
// 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.Graph(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 a vertex for the call itself.
|
|
edgeName := structs.CallFuncArgNameFunction
|
|
callFunc := &structs.CallFunc{
|
|
Type: obj.typ,
|
|
FuncType: ftyp,
|
|
EdgeName: edgeName,
|
|
ArgVertices: argFuncs,
|
|
}
|
|
graph.AddVertex(callFunc)
|
|
graph.AddEdge(funcValueFunc, callFunc, &interfaces.FuncEdge{
|
|
Args: []string{edgeName},
|
|
})
|
|
|
|
return graph, callFunc, nil
|
|
}
|
|
|
|
// SetValue here is used to store the result of the last computation of this
|
|
// expression node after it has received all the required input values. This
|
|
// value is cached and can be retrieved by calling Value.
|
|
func (obj *ExprCall) SetValue(value types.Value) error {
|
|
if err := obj.typ.Cmp(value.Type()); err != nil {
|
|
return err
|
|
}
|
|
obj.V = value
|
|
return nil
|
|
}
|
|
|
|
// Value returns the value of this expression in our type system. This will
|
|
// usually only be valid once the engine has run and values have been produced.
|
|
// This might get called speculatively (early) during unification to learn more.
|
|
// It is often unlikely that this kind of speculative execution finds something.
|
|
// This particular implementation of the function returns the previously stored
|
|
// and cached value as received by SetValue.
|
|
func (obj *ExprCall) Value() (types.Value, error) {
|
|
if obj.V == nil {
|
|
return nil, fmt.Errorf("func value does not yet exist")
|
|
}
|
|
return obj.V, nil
|
|
}
|
|
|
|
// ExprVar is a representation of a variable lookup. It returns the expression
|
|
// that that variable refers to.
|
|
type ExprVar struct {
|
|
scope *interfaces.Scope // store for referencing this later
|
|
typ *types.Type
|
|
|
|
Name string // name of the variable
|
|
}
|
|
|
|
// String returns a short representation of this expression.
|
|
func (obj *ExprVar) String() string { return fmt.Sprintf("var(%s)", obj.Name) }
|
|
|
|
// Apply is a general purpose iterator method that operates on any AST node. It
|
|
// is not used as the primary AST traversal function because it is less readable
|
|
// and easy to reason about than manually implementing traversal for each node.
|
|
// Nevertheless, it is a useful facility for operations that might only apply to
|
|
// a select number of node types, since they won't need extra noop iterators...
|
|
func (obj *ExprVar) Apply(fn func(interfaces.Node) error) error { return fn(obj) }
|
|
|
|
// Init initializes this branch of the AST, and returns an error if it fails to
|
|
// validate.
|
|
func (obj *ExprVar) Init(*interfaces.Data) error {
|
|
return langutil.ValidateVarName(obj.Name)
|
|
}
|
|
|
|
// Interpolate returns a new node (aka a copy) once it has been expanded. This
|
|
// generally increases the size of the AST when it is used. It calls Interpolate
|
|
// on any child elements and builds the new node with those new node contents.
|
|
// Here it returns itself, since variable names cannot be interpolated. We don't
|
|
// support variable, variables or anything crazy like that.
|
|
func (obj *ExprVar) Interpolate() (interfaces.Expr, error) {
|
|
return &ExprVar{
|
|
scope: obj.scope,
|
|
typ: obj.typ,
|
|
Name: obj.Name,
|
|
}, nil
|
|
}
|
|
|
|
// Copy returns a light copy of this struct. Anything static will not be copied.
|
|
// This intentionally returns a copy, because if a function (usually a lambda)
|
|
// that is used more than once, contains this variable, we will want each
|
|
// instantiation of it to be unique, otherwise they will be the same pointer,
|
|
// and they won't be able to have different values.
|
|
func (obj *ExprVar) Copy() (interfaces.Expr, error) {
|
|
return &ExprVar{
|
|
scope: obj.scope,
|
|
typ: obj.typ,
|
|
Name: obj.Name,
|
|
}, nil
|
|
}
|
|
|
|
// Ordering returns a graph of the scope ordering that represents the data flow.
|
|
// This can be used in SetScope so that it knows the correct order to run it in.
|
|
func (obj *ExprVar) Ordering(produces map[string]interfaces.Node) (*pgraph.Graph, map[interfaces.Node]string, error) {
|
|
graph, err := pgraph.NewGraph("ordering")
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
graph.AddVertex(obj)
|
|
|
|
if obj.Name == "" {
|
|
return nil, nil, fmt.Errorf("missing var name")
|
|
}
|
|
uid := varOrderingPrefix + obj.Name // ordering id
|
|
|
|
cons := make(map[interfaces.Node]string)
|
|
cons[obj] = uid
|
|
|
|
node, exists := produces[uid]
|
|
if exists {
|
|
edge := &pgraph.SimpleEdge{Name: "exprvar"}
|
|
graph.AddEdge(node, obj, edge) // prod -> cons
|
|
}
|
|
|
|
return graph, cons, nil
|
|
}
|
|
|
|
// SetScope stores the scope for use in this resource.
|
|
func (obj *ExprVar) SetScope(scope *interfaces.Scope, sctx map[string]interfaces.Expr) error {
|
|
obj.scope = interfaces.EmptyScope()
|
|
if scope != nil {
|
|
obj.scope = scope.Copy()
|
|
}
|
|
|
|
if monomorphicTarget, exists := sctx[obj.Name]; exists {
|
|
// This ExprVar refers to a parameter bound by an enclosing
|
|
// lambda definition.
|
|
obj.scope.Variables[obj.Name] = monomorphicTarget
|
|
|
|
// There is no need to scope-check the target, it's just a
|
|
// an ExprParam with no internal references.
|
|
return nil
|
|
}
|
|
|
|
target, exists := obj.scope.Variables[obj.Name]
|
|
if !exists {
|
|
return fmt.Errorf("variable %s not in scope", obj.Name)
|
|
}
|
|
|
|
obj.scope.Variables[obj.Name] = target
|
|
|
|
// This ExprVar refers to a top-level definition which has already been
|
|
// scope-checked, so we don't need to scope-check it again.
|
|
return nil
|
|
}
|
|
|
|
// SetType is used to set the type of this expression once it is known. This
|
|
// usually happens during type unification, but it can also happen during
|
|
// parsing if a type is specified explicitly. Since types are static and don't
|
|
// change on expressions, if you attempt to set a different type than what has
|
|
// previously been set (when not initially known) this will error.
|
|
func (obj *ExprVar) SetType(typ *types.Type) error {
|
|
if obj.typ != nil {
|
|
return obj.typ.Cmp(typ) // if not set, ensure it doesn't change
|
|
}
|
|
obj.typ = typ // set
|
|
return nil
|
|
}
|
|
|
|
// Type returns the type of this expression.
|
|
func (obj *ExprVar) Type() (*types.Type, error) {
|
|
// TODO: should this look more like Type() in ExprCall or vice-versa?
|
|
|
|
// Return the type if it is already known statically... It is useful for
|
|
// type unification to have some extra info early.
|
|
expr, exists := obj.scope.Variables[obj.Name]
|
|
// If !exists, just ignore the error for now since this is speculation!
|
|
// This logic simplifies down to just this!
|
|
if exists && obj.typ == nil {
|
|
return expr.Type()
|
|
}
|
|
|
|
if obj.typ == nil {
|
|
return nil, interfaces.ErrTypeCurrentlyUnknown
|
|
}
|
|
return obj.typ, nil
|
|
}
|
|
|
|
// Unify returns the list of invariants that this node produces. It recursively
|
|
// calls Unify on any children elements that exist in the AST, and returns the
|
|
// collection to the caller.
|
|
func (obj *ExprVar) Unify() ([]interfaces.Invariant, error) {
|
|
var invariants []interfaces.Invariant
|
|
|
|
// lookup value from scope
|
|
expr, exists := obj.scope.Variables[obj.Name]
|
|
if !exists {
|
|
return nil, fmt.Errorf("var `%s` does not exist in this scope", obj.Name)
|
|
}
|
|
|
|
// if this was set explicitly by the parser
|
|
if obj.typ != nil {
|
|
invar := &interfaces.EqualsInvariant{
|
|
Expr: obj,
|
|
Type: obj.typ,
|
|
}
|
|
invariants = append(invariants, invar)
|
|
}
|
|
|
|
invars, err := expr.Unify()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
invariants = append(invariants, invars...)
|
|
|
|
// this expression's type must be the type of what the var is bound to!
|
|
// TODO: does this always cause an identical duplicate invariant?
|
|
invar := &interfaces.EqualityInvariant{
|
|
Expr1: obj,
|
|
Expr2: expr,
|
|
}
|
|
invariants = append(invariants, invar)
|
|
|
|
return invariants, nil
|
|
}
|
|
|
|
// Graph returns the reactive function graph which is expressed by this node. It
|
|
// includes any vertices produced by this node, and the appropriate edges to any
|
|
// vertices that are produced by its children. Nodes which fulfill the Expr
|
|
// interface directly produce vertices (and possible children) where as nodes
|
|
// 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, and
|
|
// the edges from all of the child graphs to this. The child graph in this case
|
|
// is the graph which is obtained from the bound expression. The edge points
|
|
// from that expression to this vertex. The function used for this vertex is a
|
|
// simple placeholder which sucks incoming values in and passes them on. This is
|
|
// important for filling the logical requirements of the graph type checker, and
|
|
// 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) {
|
|
// Delegate to the targetExpr.
|
|
targetExpr := obj.scope.Variables[obj.Name]
|
|
if _, isParam := targetExpr.(*ExprParam); isParam {
|
|
// The variable points to a function parameter. We should be able to find
|
|
// this parameter in the environment.
|
|
targetFunc, exists := env[obj.Name]
|
|
if !exists {
|
|
return nil, nil, fmt.Errorf("param `%s` is not in the environment", obj.Name)
|
|
}
|
|
|
|
graph, err := pgraph.NewGraph("ExprParam")
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
graph.AddVertex(targetFunc)
|
|
return graph, targetFunc, nil
|
|
}
|
|
|
|
// The variable points to a top-level expression.
|
|
return targetExpr.Graph(env)
|
|
}
|
|
|
|
// SetValue here is a no-op, because algorithmically when this is called from
|
|
// the func engine, the child fields (the dest lookup expr) will have had this
|
|
// done to them first, and as such when we try and retrieve the set value from
|
|
// this expression by calling `Value`, it will build it from scratch!
|
|
func (obj *ExprVar) SetValue(value types.Value) error {
|
|
if err := obj.typ.Cmp(value.Type()); err != nil {
|
|
return err
|
|
}
|
|
// noop!
|
|
//obj.V = value
|
|
return nil
|
|
}
|
|
|
|
// Value returns the value of this expression in our type system. This will
|
|
// usually only be valid once the engine has run and values have been produced.
|
|
// This might get called speculatively (early) during unification to learn more.
|
|
// This returns the value this variable points to. It is able to do so because
|
|
// it can lookup in the previous set scope which expression this points to, and
|
|
// then it can call Value on that expression.
|
|
func (obj *ExprVar) Value() (types.Value, error) {
|
|
expr, exists := obj.scope.Variables[obj.Name]
|
|
if !exists {
|
|
return nil, fmt.Errorf("var `%s` does not exist in scope", obj.Name)
|
|
}
|
|
return expr.Value() // recurse
|
|
}
|
|
|
|
// ExprParam represents a parameter to a function.
|
|
type ExprParam struct {
|
|
Name string // name of the parameter
|
|
Typ *types.Type
|
|
}
|
|
|
|
// String returns a short representation of this expression.
|
|
func (obj *ExprParam) String() string {
|
|
return fmt.Sprintf("param(%s)", obj.Name)
|
|
}
|
|
|
|
// Apply is a general purpose iterator method that operates on any AST node. It
|
|
// is not used as the primary AST traversal function because it is less readable
|
|
// and easy to reason about than manually implementing traversal for each node.
|
|
// Nevertheless, it is a useful facility for operations that might only apply to
|
|
// a select number of node types, since they won't need extra noop iterators...
|
|
func (obj *ExprParam) Apply(fn func(interfaces.Node) error) error { return fn(obj) }
|
|
|
|
// Init initializes this branch of the AST, and returns an error if it fails to
|
|
// validate.
|
|
func (obj *ExprParam) Init(*interfaces.Data) error {
|
|
return langutil.ValidateVarName(obj.Name)
|
|
}
|
|
|
|
// Interpolate returns a new node (aka a copy) once it has been expanded. This
|
|
// generally increases the size of the AST when it is used. It calls Interpolate
|
|
// on any child elements and builds the new node with those new node contents.
|
|
func (obj *ExprParam) Interpolate() (interfaces.Expr, error) {
|
|
return &ExprParam{
|
|
Name: obj.Name,
|
|
Typ: obj.Typ,
|
|
}, nil
|
|
}
|
|
|
|
// Copy returns a light copy of this struct. Anything static will not be copied.
|
|
// This intentionally returns a copy, because if a function (usually a lambda)
|
|
// that is used more than once, contains this variable, we will want each
|
|
// instantiation of it to be unique, otherwise they will be the same pointer,
|
|
// and they won't be able to have different values.
|
|
func (obj *ExprParam) Copy() (interfaces.Expr, error) {
|
|
return &ExprParam{
|
|
Name: obj.Name,
|
|
Typ: obj.Typ,
|
|
}, nil
|
|
}
|
|
|
|
// Ordering returns a graph of the scope ordering that represents the data flow.
|
|
// This can be used in SetScope so that it knows the correct order to run it in.
|
|
func (obj *ExprParam) Ordering(produces map[string]interfaces.Node) (*pgraph.Graph, map[interfaces.Node]string, error) {
|
|
graph, err := pgraph.NewGraph("ordering")
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
graph.AddVertex(obj)
|
|
|
|
if obj.Name == "" {
|
|
return nil, nil, fmt.Errorf("missing param name")
|
|
}
|
|
uid := paramOrderingPrefix + obj.Name // ordering id
|
|
|
|
cons := make(map[interfaces.Node]string)
|
|
cons[obj] = uid
|
|
|
|
node, exists := produces[uid]
|
|
if exists {
|
|
edge := &pgraph.SimpleEdge{Name: "exprparam"}
|
|
graph.AddEdge(node, obj, edge) // prod -> cons
|
|
}
|
|
|
|
return graph, cons, nil
|
|
}
|
|
|
|
// SetScope stores the scope for use in this resource.
|
|
func (obj *ExprParam) SetScope(scope *interfaces.Scope, sctx map[string]interfaces.Expr) error {
|
|
// ExprParam doesn't have a scope, because it is the node to which a VarExpr
|
|
// can point to, it doesn't point to anything itself.
|
|
return nil
|
|
}
|
|
|
|
// SetType is used to set the type of this expression once it is known. This
|
|
// usually happens during type unification, but it can also happen during
|
|
// parsing if a type is specified explicitly. Since types are static and don't
|
|
// change on expressions, if you attempt to set a different type than what has
|
|
// previously been set (when not initially known) this will error.
|
|
func (obj *ExprParam) SetType(typ *types.Type) error {
|
|
if obj.Typ != nil {
|
|
return obj.Typ.Cmp(typ) // if not set, ensure it doesn't change
|
|
}
|
|
obj.Typ = typ // set
|
|
return nil
|
|
}
|
|
|
|
// Type returns the type of this expression.
|
|
func (obj *ExprParam) Type() (*types.Type, error) {
|
|
// Return the type if it is already known statically... It is useful for
|
|
// type unification to have some extra info early.
|
|
if obj.Typ == nil {
|
|
return nil, interfaces.ErrTypeCurrentlyUnknown
|
|
}
|
|
return obj.Typ, nil
|
|
}
|
|
|
|
// Unify returns the list of invariants that this node produces. It recursively
|
|
// calls Unify on any children elements that exist in the AST, and returns the
|
|
// collection to the caller.
|
|
func (obj *ExprParam) Unify() ([]interfaces.Invariant, error) {
|
|
var invariants []interfaces.Invariant
|
|
|
|
// if this was set explicitly by the parser
|
|
if obj.Typ != nil {
|
|
invar := &interfaces.EqualsInvariant{
|
|
Expr: obj,
|
|
Type: obj.Typ,
|
|
}
|
|
invariants = append(invariants, invar)
|
|
}
|
|
|
|
return invariants, nil
|
|
}
|
|
|
|
// Graph returns the reactive function graph which is expressed by this node. It
|
|
// includes any vertices produced by this node, and the appropriate edges to any
|
|
// vertices that are produced by its children. Nodes which fulfill the Expr
|
|
// interface directly produce vertices (and possible children) where as nodes
|
|
// that fulfill the Stmt interface do not produces vertices, where as their
|
|
// children might.
|
|
func (obj *ExprParam) Graph(env map[string]interfaces.Func) (*pgraph.Graph, interfaces.Func, error) {
|
|
panic("ExprParam.Graph(): should not happen, ExprVar.Graph() should handle the case where the ExprVar points to an ExprParam")
|
|
}
|
|
|
|
// SetValue here is a no-op, because algorithmically when this is called from
|
|
// the func engine, the child fields (the dest lookup expr) will have had this
|
|
// done to them first, and as such when we try and retrieve the set value from
|
|
// this expression by calling `Value`, it will build it from scratch!
|
|
func (obj *ExprParam) SetValue(value types.Value) error {
|
|
// ignored, as we don't support ExprParam.Value()
|
|
return nil
|
|
}
|
|
|
|
// Value returns the value of this expression in our type system. This will
|
|
// usually only be valid once the engine has run and values have been produced.
|
|
// This might get called speculatively (early) during unification to learn more.
|
|
func (obj *ExprParam) Value() (types.Value, error) {
|
|
return nil, fmt.Errorf("no value for ExprParam")
|
|
}
|
|
|
|
// ExprPoly is a polymorphic expression that is a definition that can be used in
|
|
// multiple places with different types. We must copy the definition at each
|
|
// call site in order for the type checker to find a different type at each call
|
|
// site. We create this copy inside SetScope, at which point we also recursively
|
|
// call SetScope on the copy. We must be careful to use the scope captured at
|
|
// the definition site, not the scope which is available at the call site.
|
|
type ExprPoly struct {
|
|
Definition interfaces.Expr // The definition.
|
|
}
|
|
|
|
// String returns a short representation of this expression.
|
|
func (obj *ExprPoly) String() string {
|
|
return fmt.Sprintf("polymorphic(%s)", obj.Definition.String())
|
|
}
|
|
|
|
// Apply is a general purpose iterator method that operates on any AST node. It
|
|
// is not used as the primary AST traversal function because it is less readable
|
|
// and easy to reason about than manually implementing traversal for each node.
|
|
// Nevertheless, it is a useful facility for operations that might only apply to
|
|
// a select number of node types, since they won't need extra noop iterators...
|
|
func (obj *ExprPoly) Apply(fn func(interfaces.Node) error) error {
|
|
if err := obj.Definition.Apply(fn); err != nil {
|
|
return err
|
|
}
|
|
return fn(obj)
|
|
}
|
|
|
|
// Init initializes this branch of the AST, and returns an error if it fails to
|
|
// validate.
|
|
func (obj *ExprPoly) Init(data *interfaces.Data) error {
|
|
return obj.Definition.Init(data)
|
|
}
|
|
|
|
// Interpolate returns a new node (aka a copy) once it has been expanded. This
|
|
// generally increases the size of the AST when it is used. It calls Interpolate
|
|
// on any child elements and builds the new node with those new node contents.
|
|
func (obj *ExprPoly) Interpolate() (interfaces.Expr, error) {
|
|
definition, err := obj.Definition.Interpolate()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &ExprPoly{
|
|
Definition: definition,
|
|
}, nil
|
|
}
|
|
|
|
// Copy returns a light copy of this struct. Anything static will not be copied.
|
|
// This implementation intentionally does not copy anything, because the
|
|
// Definition is already intended to be copied at each use site.
|
|
func (obj *ExprPoly) Copy() (interfaces.Expr, error) {
|
|
return obj, nil
|
|
}
|
|
|
|
// Ordering returns a graph of the scope ordering that represents the data flow.
|
|
// This can be used in SetScope so that it knows the correct order to run it in.
|
|
func (obj *ExprPoly) Ordering(produces map[string]interfaces.Node) (*pgraph.Graph, map[interfaces.Node]string, error) {
|
|
return obj.Definition.Ordering(produces)
|
|
}
|
|
|
|
// SetScope stores the scope for use in this resource.
|
|
func (obj *ExprPoly) SetScope(scope *interfaces.Scope, sctx map[string]interfaces.Expr) error {
|
|
panic("ExprPoly.SetScope(): should not happen, ExprVar should replace ExprPoly with a copy of its definition before calling SetScope")
|
|
}
|
|
|
|
// SetType is used to set the type of this expression once it is known. This
|
|
// usually happens during type unification, but it can also happen during
|
|
// parsing if a type is specified explicitly. Since types are static and don't
|
|
// change on expressions, if you attempt to set a different type than what has
|
|
// previously been set (when not initially known) this will error.
|
|
func (obj *ExprPoly) SetType(typ *types.Type) error {
|
|
panic("ExprPoly.SetType(): should not happen, all ExprPoly expressions should be gone by the time type-checking starts")
|
|
}
|
|
|
|
// Type returns the type of this expression.
|
|
func (obj *ExprPoly) Type() (*types.Type, error) {
|
|
return nil, interfaces.ErrTypeCurrentlyUnknown
|
|
}
|
|
|
|
// Unify returns the list of invariants that this node produces. It recursively
|
|
// calls Unify on any children elements that exist in the AST, and returns the
|
|
// collection to the caller.
|
|
func (obj *ExprPoly) Unify() ([]interfaces.Invariant, error) {
|
|
panic("ExprPoly.Unify(): should not happen, all ExprPoly expressions should be gone by the time type-checking starts")
|
|
}
|
|
|
|
// Graph returns the reactive function graph which is expressed by this node. It
|
|
// includes any vertices produced by this node, and the appropriate edges to any
|
|
// vertices that are produced by its children. Nodes which fulfill the Expr
|
|
// interface directly produce vertices (and possible children) where as nodes
|
|
// that fulfill the Stmt interface do not produces vertices, where as their
|
|
// children might.
|
|
func (obj *ExprPoly) Graph(env map[string]interfaces.Func) (*pgraph.Graph, interfaces.Func, error) {
|
|
panic("ExprPoly.Unify(): should not happen, all ExprPoly expressions should be gone by the time type-checking starts")
|
|
}
|
|
|
|
// SetValue here is a no-op, because algorithmically when this is called from
|
|
// the func engine, the child fields (the dest lookup expr) will have had this
|
|
// done to them first, and as such when we try and retrieve the set value from
|
|
// this expression by calling `Value`, it will build it from scratch!
|
|
func (obj *ExprPoly) SetValue(value types.Value) error {
|
|
// ignored, as we don't support ExprPoly.Value()
|
|
return nil
|
|
}
|
|
|
|
// Value returns the value of this expression in our type system. This will
|
|
// usually only be valid once the engine has run and values have been produced.
|
|
// This might get called speculatively (early) during unification to learn more.
|
|
func (obj *ExprPoly) Value() (types.Value, error) {
|
|
return nil, fmt.Errorf("no value for ExprPoly")
|
|
}
|
|
|
|
// ExprTopLevel is intended to wrap top-level definitions. It captures the
|
|
// variables which are in scope at the the top-level, so that when use sites
|
|
// call ExprTopLevel.SetScope() with the variables which are in scope at the use
|
|
// site, ExprTopLevel can automatically correct this by using the variables
|
|
// which are in scope at the definition site.
|
|
type ExprTopLevel struct {
|
|
Definition interfaces.Expr // The definition.
|
|
CapturedScope *interfaces.Scope // The scope at the definition site.
|
|
}
|
|
|
|
// String returns a short representation of this expression.
|
|
func (obj *ExprTopLevel) String() string {
|
|
return fmt.Sprintf("topLevel(%s)", obj.Definition.String())
|
|
}
|
|
|
|
// Apply is a general purpose iterator method that operates on any AST node. It
|
|
// is not used as the primary AST traversal function because it is less readable
|
|
// and easy to reason about than manually implementing traversal for each node.
|
|
// Nevertheless, it is a useful facility for operations that might only apply to
|
|
// a select number of node types, since they won't need extra noop iterators...
|
|
func (obj *ExprTopLevel) Apply(fn func(interfaces.Node) error) error {
|
|
if err := obj.Definition.Apply(fn); err != nil {
|
|
return err
|
|
}
|
|
return fn(obj)
|
|
}
|
|
|
|
// Init initializes this branch of the AST, and returns an error if it fails to
|
|
// validate.
|
|
func (obj *ExprTopLevel) Init(data *interfaces.Data) error {
|
|
return obj.Definition.Init(data)
|
|
}
|
|
|
|
// Interpolate returns a new node (aka a copy) once it has been expanded. This
|
|
// generally increases the size of the AST when it is used. It calls Interpolate
|
|
// on any child elements and builds the new node with those new node contents.
|
|
func (obj *ExprTopLevel) Interpolate() (interfaces.Expr, error) {
|
|
definition, err := obj.Definition.Interpolate()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &ExprTopLevel{
|
|
Definition: definition,
|
|
CapturedScope: obj.CapturedScope,
|
|
}, nil
|
|
}
|
|
|
|
// Copy returns a light copy of this struct. Anything static will not be copied.
|
|
func (obj *ExprTopLevel) Copy() (interfaces.Expr, error) {
|
|
definition, err := obj.Definition.Copy()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &ExprTopLevel{
|
|
Definition: definition,
|
|
CapturedScope: obj.CapturedScope,
|
|
}, nil
|
|
}
|
|
|
|
// Ordering returns a graph of the scope ordering that represents the data flow.
|
|
// This can be used in SetScope so that it knows the correct order to run it in.
|
|
func (obj *ExprTopLevel) Ordering(produces map[string]interfaces.Node) (*pgraph.Graph, map[interfaces.Node]string, error) {
|
|
graph, err := pgraph.NewGraph("ordering")
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
graph.AddVertex(obj)
|
|
|
|
// Additional constraint: We know the Definition has to be satisfied before
|
|
// this ExprTopLevel expression itself can be used, since ExprTopLevel
|
|
// delegates to the Definition.
|
|
edge := &pgraph.SimpleEdge{Name: "exprtoplevel"}
|
|
graph.AddEdge(obj.Definition, obj, edge) // prod -> cons
|
|
|
|
cons := make(map[interfaces.Node]string)
|
|
|
|
g, c, err := obj.Definition.Ordering(produces)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
graph.AddGraph(g) // add in the child graph
|
|
|
|
for k, v := range c { // c is consumes
|
|
x, exists := cons[k]
|
|
if exists && v != x {
|
|
return nil, nil, fmt.Errorf("consumed value is different, got `%+v`, expected `%+v`", x, v)
|
|
}
|
|
cons[k] = v // add to map
|
|
|
|
n, exists := produces[v]
|
|
if !exists {
|
|
continue
|
|
}
|
|
edge := &pgraph.SimpleEdge{Name: "exprtopleveldefinition"}
|
|
graph.AddEdge(n, k, edge)
|
|
}
|
|
|
|
return graph, cons, nil
|
|
}
|
|
|
|
// SetScope stores the scope for use in this resource.
|
|
func (obj *ExprTopLevel) SetScope(scope *interfaces.Scope, sctx map[string]interfaces.Expr) error {
|
|
// Use the scope captured at the definition site. The parameters from
|
|
// functions enclosing the use site are not visible at the top-level either,
|
|
// so we must clear sctx.
|
|
return obj.Definition.SetScope(obj.CapturedScope, make(map[string]interfaces.Expr))
|
|
}
|
|
|
|
// SetType is used to set the type of this expression once it is known. This
|
|
// usually happens during type unification, but it can also happen during
|
|
// parsing if a type is specified explicitly. Since types are static and don't
|
|
// change on expressions, if you attempt to set a different type than what has
|
|
// previously been set (when not initially known) this will error.
|
|
func (obj *ExprTopLevel) SetType(typ *types.Type) error {
|
|
return obj.Definition.SetType(typ)
|
|
}
|
|
|
|
// Type returns the type of this expression.
|
|
func (obj *ExprTopLevel) Type() (*types.Type, error) {
|
|
return obj.Definition.Type()
|
|
}
|
|
|
|
// Unify returns the list of invariants that this node produces. It recursively
|
|
// calls Unify on any children elements that exist in the AST, and returns the
|
|
// collection to the caller.
|
|
func (obj *ExprTopLevel) Unify() ([]interfaces.Invariant, error) {
|
|
var invariants []interfaces.Invariant
|
|
var invar interfaces.Invariant
|
|
|
|
invars, err := obj.Definition.Unify()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
invariants = append(invariants, invars...)
|
|
|
|
invar = &interfaces.EqualityInvariant{
|
|
Expr1: obj,
|
|
Expr2: obj.Definition,
|
|
}
|
|
invariants = append(invariants, invar)
|
|
|
|
// We don't want this to have it's SetType run in the unified solution.
|
|
invar = &interfaces.SkipInvariant{
|
|
Expr: obj,
|
|
}
|
|
invariants = append(invariants, invar)
|
|
|
|
return invariants, nil
|
|
}
|
|
|
|
// Graph returns the reactive function graph which is expressed by this node. It
|
|
// includes any vertices produced by this node, and the appropriate edges to any
|
|
// vertices that are produced by its children. Nodes which fulfill the Expr
|
|
// interface directly produce vertices (and possible children) where as nodes
|
|
// that fulfill the Stmt interface do not produces vertices, where as their
|
|
// children might.
|
|
func (obj *ExprTopLevel) Graph(env map[string]interfaces.Func) (*pgraph.Graph, interfaces.Func, error) {
|
|
return obj.Definition.Graph(env)
|
|
}
|
|
|
|
// SetValue here is a no-op, because algorithmically when this is called from
|
|
// the func engine, the child fields (the dest lookup expr) will have had this
|
|
// done to them first, and as such when we try and retrieve the set value from
|
|
// this expression by calling `Value`, it will build it from scratch!
|
|
func (obj *ExprTopLevel) SetValue(value types.Value) error {
|
|
return obj.Definition.SetValue(value)
|
|
}
|
|
|
|
// Value returns the value of this expression in our type system. This will
|
|
// usually only be valid once the engine has run and values have been produced.
|
|
// This might get called speculatively (early) during unification to learn more.
|
|
func (obj *ExprTopLevel) Value() (types.Value, error) {
|
|
return obj.Definition.Value()
|
|
}
|
|
|
|
// ExprSingleton is intended to wrap top-level variable definitions. It ensures
|
|
// that a single Func is created even if multiple use sites call
|
|
// ExprSingleton.Graph().
|
|
type ExprSingleton struct {
|
|
Definition interfaces.Expr
|
|
|
|
singletonGraph *pgraph.Graph
|
|
singletonExpr interfaces.Func
|
|
mutex *sync.Mutex // protects singletonGraph and singletonExpr
|
|
}
|
|
|
|
// String returns a short representation of this expression.
|
|
func (obj *ExprSingleton) String() string {
|
|
return fmt.Sprintf("singleton(%s)", obj.Definition.String())
|
|
}
|
|
|
|
// Apply is a general purpose iterator method that operates on any AST node. It
|
|
// is not used as the primary AST traversal function because it is less readable
|
|
// and easy to reason about than manually implementing traversal for each node.
|
|
// Nevertheless, it is a useful facility for operations that might only apply to
|
|
// a select number of node types, since they won't need extra noop iterators...
|
|
func (obj *ExprSingleton) Apply(fn func(interfaces.Node) error) error {
|
|
if err := obj.Definition.Apply(fn); err != nil {
|
|
return err
|
|
}
|
|
return fn(obj)
|
|
}
|
|
|
|
// Init initializes this branch of the AST, and returns an error if it fails to
|
|
// validate.
|
|
func (obj *ExprSingleton) Init(data *interfaces.Data) error {
|
|
obj.mutex = &sync.Mutex{}
|
|
return obj.Definition.Init(data)
|
|
}
|
|
|
|
// Interpolate returns a new node (aka a copy) once it has been expanded. This
|
|
// generally increases the size of the AST when it is used. It calls Interpolate
|
|
// on any child elements and builds the new node with those new node contents.
|
|
func (obj *ExprSingleton) Interpolate() (interfaces.Expr, error) {
|
|
definition, err := obj.Definition.Interpolate()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &ExprSingleton{
|
|
Definition: definition,
|
|
singletonGraph: nil, // each copy should have its own Graph
|
|
singletonExpr: nil, // each copy should have its own Func
|
|
mutex: &sync.Mutex{},
|
|
}, nil
|
|
}
|
|
|
|
// Copy returns a light copy of this struct. Anything static will not be copied.
|
|
func (obj *ExprSingleton) Copy() (interfaces.Expr, error) {
|
|
definition, err := obj.Definition.Copy()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &ExprSingleton{
|
|
Definition: definition,
|
|
singletonGraph: nil, // each copy should have its own Graph
|
|
singletonExpr: nil, // each copy should have its own Func
|
|
mutex: &sync.Mutex{},
|
|
}, nil
|
|
}
|
|
|
|
// Ordering returns a graph of the scope ordering that represents the data flow.
|
|
// This can be used in SetScope so that it knows the correct order to run it in.
|
|
func (obj *ExprSingleton) Ordering(produces map[string]interfaces.Node) (*pgraph.Graph, map[interfaces.Node]string, error) {
|
|
graph, err := pgraph.NewGraph("ordering")
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
graph.AddVertex(obj)
|
|
|
|
// Additional constraint: We know the Definition has to be satisfied before
|
|
// this ExprSingleton expression itself can be used, since ExprSingleton
|
|
// delegates to the Definition.
|
|
edge := &pgraph.SimpleEdge{Name: "exprsingleton"}
|
|
graph.AddEdge(obj.Definition, obj, edge) // prod -> cons
|
|
|
|
cons := make(map[interfaces.Node]string)
|
|
|
|
g, c, err := obj.Definition.Ordering(produces)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
graph.AddGraph(g) // add in the child graph
|
|
|
|
for k, v := range c { // c is consumes
|
|
x, exists := cons[k]
|
|
if exists && v != x {
|
|
return nil, nil, fmt.Errorf("consumed value is different, got `%+v`, expected `%+v`", x, v)
|
|
}
|
|
cons[k] = v // add to map
|
|
|
|
n, exists := produces[v]
|
|
if !exists {
|
|
continue
|
|
}
|
|
edge := &pgraph.SimpleEdge{Name: "exprsingletondefinition"}
|
|
graph.AddEdge(n, k, edge)
|
|
}
|
|
|
|
return graph, cons, nil
|
|
}
|
|
|
|
// SetScope stores the scope for use in this resource.
|
|
func (obj *ExprSingleton) SetScope(scope *interfaces.Scope, sctx map[string]interfaces.Expr) error {
|
|
return obj.Definition.SetScope(scope, sctx)
|
|
}
|
|
|
|
// SetType is used to set the type of this expression once it is known. This
|
|
// usually happens during type unification, but it can also happen during
|
|
// parsing if a type is specified explicitly. Since types are static and don't
|
|
// change on expressions, if you attempt to set a different type than what has
|
|
// previously been set (when not initially known) this will error.
|
|
func (obj *ExprSingleton) SetType(typ *types.Type) error {
|
|
return obj.Definition.SetType(typ)
|
|
}
|
|
|
|
// Type returns the type of this expression.
|
|
func (obj *ExprSingleton) Type() (*types.Type, error) {
|
|
return obj.Definition.Type()
|
|
}
|
|
|
|
// Unify returns the list of invariants that this node produces. It recursively
|
|
// calls Unify on any children elements that exist in the AST, and returns the
|
|
// collection to the caller.
|
|
func (obj *ExprSingleton) Unify() ([]interfaces.Invariant, error) {
|
|
var invariants []interfaces.Invariant
|
|
var invar interfaces.Invariant
|
|
|
|
invars, err := obj.Definition.Unify()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
invariants = append(invariants, invars...)
|
|
|
|
invar = &interfaces.EqualityInvariant{
|
|
Expr1: obj,
|
|
Expr2: obj.Definition,
|
|
}
|
|
invariants = append(invariants, invar)
|
|
|
|
// We don't want this to have it's SetType run in the unified solution.
|
|
invar = &interfaces.SkipInvariant{
|
|
Expr: obj,
|
|
}
|
|
invariants = append(invariants, invar)
|
|
|
|
return invariants, nil
|
|
}
|
|
|
|
// Graph returns the reactive function graph which is expressed by this node. It
|
|
// includes any vertices produced by this node, and the appropriate edges to any
|
|
// vertices that are produced by its children. Nodes which fulfill the Expr
|
|
// interface directly produce vertices (and possible children) where as nodes
|
|
// that fulfill the Stmt interface do not produces vertices, where as their
|
|
// children might.
|
|
func (obj *ExprSingleton) Graph(env map[string]interfaces.Func) (*pgraph.Graph, interfaces.Func, error) {
|
|
obj.mutex.Lock()
|
|
defer obj.mutex.Unlock()
|
|
|
|
if obj.singletonExpr == nil {
|
|
g, f, err := obj.Definition.Graph(env)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
obj.singletonGraph = g
|
|
obj.singletonExpr = f
|
|
return g, f, nil
|
|
}
|
|
|
|
return obj.singletonGraph, obj.singletonExpr, nil
|
|
}
|
|
|
|
// SetValue here is a no-op, because algorithmically when this is called from
|
|
// the func engine, the child fields (the dest lookup expr) will have had this
|
|
// done to them first, and as such when we try and retrieve the set value from
|
|
// this expression by calling `Value`, it will build it from scratch!
|
|
func (obj *ExprSingleton) SetValue(value types.Value) error {
|
|
return obj.Definition.SetValue(value)
|
|
}
|
|
|
|
// Value returns the value of this expression in our type system. This will
|
|
// usually only be valid once the engine has run and values have been produced.
|
|
// This might get called speculatively (early) during unification to learn more.
|
|
func (obj *ExprSingleton) Value() (types.Value, error) {
|
|
return obj.Definition.Value()
|
|
}
|
|
|
|
// ExprIf represents an if expression which *must* have both branches, and which
|
|
// returns a value. As a result, it has a type. This is different from a StmtIf,
|
|
// which does not need to have both branches, and which does not return a value.
|
|
type ExprIf struct {
|
|
scope *interfaces.Scope // store for referencing this later
|
|
typ *types.Type
|
|
|
|
Condition interfaces.Expr
|
|
ThenBranch interfaces.Expr // could be an ExprBranch
|
|
ElseBranch interfaces.Expr // could be an ExprBranch
|
|
}
|
|
|
|
// String returns a short representation of this expression.
|
|
func (obj *ExprIf) String() string {
|
|
condition := obj.Condition.String()
|
|
thenBranch := obj.ThenBranch.String()
|
|
elseBranch := obj.ElseBranch.String()
|
|
return fmt.Sprintf("if( %s ) { %s } else { %s }", condition, thenBranch, elseBranch)
|
|
}
|
|
|
|
// Apply is a general purpose iterator method that operates on any AST node. It
|
|
// is not used as the primary AST traversal function because it is less readable
|
|
// and easy to reason about than manually implementing traversal for each node.
|
|
// Nevertheless, it is a useful facility for operations that might only apply to
|
|
// a select number of node types, since they won't need extra noop iterators...
|
|
func (obj *ExprIf) Apply(fn func(interfaces.Node) error) error {
|
|
if err := obj.Condition.Apply(fn); err != nil {
|
|
return err
|
|
}
|
|
if err := obj.ThenBranch.Apply(fn); err != nil {
|
|
return err
|
|
}
|
|
if err := obj.ElseBranch.Apply(fn); err != nil {
|
|
return err
|
|
}
|
|
return fn(obj)
|
|
}
|
|
|
|
// Init initializes this branch of the AST, and returns an error if it fails to
|
|
// validate.
|
|
func (obj *ExprIf) Init(data *interfaces.Data) error {
|
|
if err := obj.Condition.Init(data); err != nil {
|
|
return err
|
|
}
|
|
if err := obj.ThenBranch.Init(data); err != nil {
|
|
return err
|
|
}
|
|
if err := obj.ElseBranch.Init(data); err != nil {
|
|
return err
|
|
}
|
|
|
|
// no errors
|
|
return nil
|
|
}
|
|
|
|
// Interpolate returns a new node (aka a copy) once it has been expanded. This
|
|
// generally increases the size of the AST when it is used. It calls Interpolate
|
|
// on any child elements and builds the new node with those new node contents.
|
|
func (obj *ExprIf) Interpolate() (interfaces.Expr, error) {
|
|
condition, err := obj.Condition.Interpolate()
|
|
if err != nil {
|
|
return nil, errwrap.Wrapf(err, "could not interpolate Condition")
|
|
}
|
|
thenBranch, err := obj.ThenBranch.Interpolate()
|
|
if err != nil {
|
|
return nil, errwrap.Wrapf(err, "could not interpolate ThenBranch")
|
|
}
|
|
elseBranch, err := obj.ElseBranch.Interpolate()
|
|
if err != nil {
|
|
return nil, errwrap.Wrapf(err, "could not interpolate ElseBranch")
|
|
}
|
|
return &ExprIf{
|
|
scope: obj.scope,
|
|
typ: obj.typ,
|
|
Condition: condition,
|
|
ThenBranch: thenBranch,
|
|
ElseBranch: elseBranch,
|
|
}, nil
|
|
}
|
|
|
|
// Copy returns a light copy of this struct. Anything static will not be copied.
|
|
func (obj *ExprIf) Copy() (interfaces.Expr, error) {
|
|
copied := false
|
|
condition, err := obj.Condition.Copy()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// must have been copied, or pointer would be same
|
|
if condition != obj.Condition {
|
|
copied = true
|
|
}
|
|
thenBranch, err := obj.ThenBranch.Copy()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if thenBranch != obj.ThenBranch {
|
|
copied = true
|
|
}
|
|
elseBranch, err := obj.ElseBranch.Copy()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if elseBranch != obj.ElseBranch {
|
|
copied = true
|
|
}
|
|
|
|
if !copied { // it's static
|
|
return obj, nil
|
|
}
|
|
return &ExprIf{
|
|
scope: obj.scope,
|
|
typ: obj.typ,
|
|
Condition: condition,
|
|
ThenBranch: thenBranch,
|
|
ElseBranch: elseBranch,
|
|
}, nil
|
|
}
|
|
|
|
// Ordering returns a graph of the scope ordering that represents the data flow.
|
|
// This can be used in SetScope so that it knows the correct order to run it in.
|
|
func (obj *ExprIf) Ordering(produces map[string]interfaces.Node) (*pgraph.Graph, map[interfaces.Node]string, error) {
|
|
graph, err := pgraph.NewGraph("ordering")
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
graph.AddVertex(obj)
|
|
|
|
// Additional constraints: We know the condition has to be satisfied
|
|
// before this if expression itself can be used, since we depend on that
|
|
// value.
|
|
edge := &pgraph.SimpleEdge{Name: "exprif"}
|
|
graph.AddEdge(obj.Condition, obj, edge) // prod -> cons
|
|
|
|
cons := make(map[interfaces.Node]string)
|
|
|
|
g, c, err := obj.Condition.Ordering(produces)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
graph.AddGraph(g) // add in the child graph
|
|
|
|
for k, v := range c { // c is consumes
|
|
x, exists := cons[k]
|
|
if exists && v != x {
|
|
return nil, nil, fmt.Errorf("consumed value is different, got `%+v`, expected `%+v`", x, v)
|
|
}
|
|
cons[k] = v // add to map
|
|
|
|
n, exists := produces[v]
|
|
if !exists {
|
|
continue
|
|
}
|
|
edge := &pgraph.SimpleEdge{Name: "exprifcondition"}
|
|
graph.AddEdge(n, k, edge)
|
|
}
|
|
|
|
// don't put obj.Condition here because this adds an extra edge to it!
|
|
nodes := []interfaces.Expr{obj.ThenBranch, obj.ElseBranch}
|
|
|
|
for _, node := range nodes { // "dry"
|
|
g, c, err := node.Ordering(produces)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
graph.AddGraph(g) // add in the child graph
|
|
|
|
// additional constraints...
|
|
edge1 := &pgraph.SimpleEdge{Name: "exprifbranch1"}
|
|
graph.AddEdge(obj.Condition, node, edge1) // prod -> cons
|
|
edge2 := &pgraph.SimpleEdge{Name: "exprifbranchcondition"}
|
|
graph.AddEdge(node, obj, edge2) // prod -> cons
|
|
|
|
for k, v := range c { // c is consumes
|
|
x, exists := cons[k]
|
|
if exists && v != x {
|
|
return nil, nil, fmt.Errorf("consumed value is different, got `%+v`, expected `%+v`", x, v)
|
|
}
|
|
cons[k] = v // add to map
|
|
|
|
n, exists := produces[v]
|
|
if !exists {
|
|
continue
|
|
}
|
|
edge := &pgraph.SimpleEdge{Name: "exprifbranch2"}
|
|
graph.AddEdge(n, k, edge)
|
|
}
|
|
}
|
|
|
|
return graph, cons, nil
|
|
}
|
|
|
|
// SetScope stores the scope for later use in this resource and its children,
|
|
// which it propagates this downwards to.
|
|
func (obj *ExprIf) SetScope(scope *interfaces.Scope, sctx map[string]interfaces.Expr) error {
|
|
if scope == nil {
|
|
scope = interfaces.EmptyScope()
|
|
}
|
|
obj.scope = scope
|
|
if err := obj.ThenBranch.SetScope(scope, sctx); err != nil {
|
|
return err
|
|
}
|
|
if err := obj.ElseBranch.SetScope(scope, sctx); err != nil {
|
|
return err
|
|
}
|
|
return obj.Condition.SetScope(scope, sctx)
|
|
}
|
|
|
|
// SetType is used to set the type of this expression once it is known. This
|
|
// usually happens during type unification, but it can also happen during
|
|
// parsing if a type is specified explicitly. Since types are static and don't
|
|
// change on expressions, if you attempt to set a different type than what has
|
|
// previously been set (when not initially known) this will error.
|
|
func (obj *ExprIf) SetType(typ *types.Type) error {
|
|
if obj.typ != nil {
|
|
return obj.typ.Cmp(typ) // if not set, ensure it doesn't change
|
|
}
|
|
obj.typ = typ // set
|
|
return nil
|
|
}
|
|
|
|
// Type returns the type of this expression.
|
|
func (obj *ExprIf) Type() (*types.Type, error) {
|
|
if obj.typ != nil {
|
|
return obj.typ, nil
|
|
}
|
|
|
|
var typ *types.Type
|
|
testAndSet := func(t *types.Type) error {
|
|
if t == nil {
|
|
return nil // skip
|
|
}
|
|
if typ == nil {
|
|
return nil // it's ok
|
|
}
|
|
|
|
if typ.Cmp(t) != nil {
|
|
return fmt.Errorf("inconsistent branch")
|
|
}
|
|
typ = t // save
|
|
|
|
return nil
|
|
}
|
|
|
|
if obj.ThenBranch != nil {
|
|
if t, err := obj.ThenBranch.Type(); err != nil {
|
|
if err := testAndSet(t); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
}
|
|
if obj.ElseBranch != nil {
|
|
if t, err := obj.ElseBranch.Type(); err != nil {
|
|
if err := testAndSet(t); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
}
|
|
|
|
if typ != nil {
|
|
return typ, nil
|
|
}
|
|
return nil, interfaces.ErrTypeCurrentlyUnknown
|
|
}
|
|
|
|
// Unify returns the list of invariants that this node produces. It recursively
|
|
// calls Unify on any children elements that exist in the AST, and returns the
|
|
// collection to the caller.
|
|
func (obj *ExprIf) Unify() ([]interfaces.Invariant, error) {
|
|
var invariants []interfaces.Invariant
|
|
|
|
// if this was set explicitly by the parser
|
|
if obj.typ != nil {
|
|
invar := &interfaces.EqualsInvariant{
|
|
Expr: obj,
|
|
Type: obj.typ,
|
|
}
|
|
invariants = append(invariants, invar)
|
|
}
|
|
|
|
// conditional expression might have some children invariants to share
|
|
condition, err := obj.Condition.Unify()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
invariants = append(invariants, condition...)
|
|
|
|
// the condition must ultimately be a boolean
|
|
conditionInvar := &interfaces.EqualsInvariant{
|
|
Expr: obj.Condition,
|
|
Type: types.TypeBool,
|
|
}
|
|
invariants = append(invariants, conditionInvar)
|
|
|
|
// recurse into the two branches
|
|
thenBranch, err := obj.ThenBranch.Unify()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
invariants = append(invariants, thenBranch...)
|
|
|
|
elseBranch, err := obj.ElseBranch.Unify()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
invariants = append(invariants, elseBranch...)
|
|
|
|
// the two branches must be equally typed
|
|
branchesInvar := &interfaces.EqualityInvariant{
|
|
Expr1: obj.ThenBranch,
|
|
Expr2: obj.ElseBranch,
|
|
}
|
|
invariants = append(invariants, branchesInvar)
|
|
|
|
// the two branches must match the type of the whole expression
|
|
thenInvar := &interfaces.EqualityInvariant{
|
|
Expr1: obj,
|
|
Expr2: obj.ThenBranch,
|
|
}
|
|
invariants = append(invariants, thenInvar)
|
|
elseInvar := &interfaces.EqualityInvariant{
|
|
Expr1: obj,
|
|
Expr2: obj.ElseBranch,
|
|
}
|
|
invariants = append(invariants, elseInvar)
|
|
|
|
return invariants, nil
|
|
}
|
|
|
|
// Func returns a function which returns the correct branch based on the ever
|
|
// changing conditional boolean input.
|
|
func (obj *ExprIf) Func() (interfaces.Func, error) {
|
|
typ, err := obj.Type()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &structs.IfFunc{
|
|
Type: typ, // this is the output type of the expression
|
|
}, nil
|
|
}
|
|
|
|
// Graph returns the reactive function graph which is expressed by this node. It
|
|
// includes any vertices produced by this node, and the appropriate edges to any
|
|
// vertices that are produced by its children. Nodes which fulfill the Expr
|
|
// interface directly produce vertices (and possible children) where as nodes
|
|
// that fulfill the Stmt interface do not produces vertices, where as their
|
|
// children might. This particular if expression doesn't do anything clever here
|
|
// other than adding in both branches of the graph. Since we're functional, this
|
|
// shouldn't have any ill effects.
|
|
// XXX: is this completely true if we're running technically impure, but safe
|
|
// built-in functions on both branches? Can we turn off half of this?
|
|
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
|
|
}
|
|
function, err := obj.Func()
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
exprs := map[string]interfaces.Expr{
|
|
"c": obj.Condition,
|
|
"a": obj.ThenBranch,
|
|
"b": obj.ElseBranch,
|
|
}
|
|
for _, argName := range []string{"c", "a", "b"} { // deterministic order
|
|
x := exprs[argName]
|
|
g, f, err := x.Graph(env)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
graph.AddGraph(g)
|
|
|
|
edge := &interfaces.FuncEdge{Args: []string{argName}}
|
|
graph.AddEdge(f, function, edge) // branch -> if
|
|
}
|
|
|
|
return graph, function, nil
|
|
}
|
|
|
|
// SetValue here is a no-op, because algorithmically when this is called from
|
|
// the func engine, the child fields (the branches expr's) will have had this
|
|
// done to them first, and as such when we try and retrieve the set value from
|
|
// this expression by calling `Value`, it will build it from scratch!
|
|
func (obj *ExprIf) SetValue(value types.Value) error {
|
|
if err := obj.typ.Cmp(value.Type()); err != nil {
|
|
return err
|
|
}
|
|
// noop!
|
|
//obj.V = value
|
|
return nil
|
|
}
|
|
|
|
// Value returns the value of this expression in our type system. This will
|
|
// usually only be valid once the engine has run and values have been produced.
|
|
// This might get called speculatively (early) during unification to learn more.
|
|
// This particular expression evaluates the condition and returns the correct
|
|
// branch's value accordingly.
|
|
func (obj *ExprIf) Value() (types.Value, error) {
|
|
boolValue, err := obj.Condition.Value()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if boolValue.Bool() { // must not panic
|
|
return obj.ThenBranch.Value()
|
|
}
|
|
return obj.ElseBranch.Value()
|
|
}
|
|
|
|
// getScope pulls the local stored scope out of an Expr, without needing to add
|
|
// a similarly named method to the Expr interface. This is private and not part
|
|
// of the interface, because it is only used internally.
|
|
// TODO: we could extend this to include Stmt's if it was ever useful
|
|
func getScope(node interfaces.Expr) (*interfaces.Scope, error) {
|
|
//if _, ok := node.(interfaces.Expr); !ok {
|
|
// return nil, fmt.Errorf("unexpected: %+v", node)
|
|
//}
|
|
|
|
switch expr := node.(type) {
|
|
case *ExprBool:
|
|
return expr.scope, nil
|
|
case *ExprStr:
|
|
return expr.scope, nil
|
|
case *ExprInt:
|
|
return expr.scope, nil
|
|
case *ExprFloat:
|
|
return expr.scope, nil
|
|
case *ExprList:
|
|
return expr.scope, nil
|
|
case *ExprMap:
|
|
return expr.scope, nil
|
|
case *ExprStruct:
|
|
return expr.scope, nil
|
|
case *ExprFunc:
|
|
return expr.scope, nil
|
|
case *ExprCall:
|
|
return expr.scope, nil
|
|
case *ExprVar:
|
|
return expr.scope, nil
|
|
case *ExprIf:
|
|
return expr.scope, nil
|
|
|
|
//case *ExprAny: // unexpected!
|
|
default:
|
|
return nil, fmt.Errorf("unexpected: %+v", node)
|
|
}
|
|
}
|
|
|
|
// trueCallee is a helper function because ExprTopLevel and ExprSingleton are
|
|
// sometimes added around builtins. This makes it difficult for the type checker
|
|
// to check if a particular builtin is the callee or not. This function removes
|
|
// the ExprTopLevel and ExprSingleton wrappers, if they exist.
|
|
func trueCallee(apparentCallee interfaces.Expr) interfaces.Expr {
|
|
switch x := apparentCallee.(type) {
|
|
case *ExprTopLevel:
|
|
return trueCallee(x.Definition)
|
|
case *ExprSingleton:
|
|
return trueCallee(x.Definition)
|
|
default:
|
|
return apparentCallee
|
|
}
|
|
}
|