The test for gometalinter got silently broken in an earlier commit. Look for the missing space that was added back in this commit to see why! In any case, this now fixes some of the things that weren't previously caught by this change. If anyone knows how to run these sorts of tests properly so that entire packages are tested and so that we can enable additional tests, please let me know! It's also unclear why goreportcard catches a few additional problems which aren't found by running this ourselves. See: https://goreportcard.com/report/github.com/purpleidea/mgmt for more information.
3046 lines
99 KiB
Go
3046 lines
99 KiB
Go
// Mgmt
|
|
// Copyright (C) 2013-2018+ 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 lang // TODO: move this into a sub package of lang/$name?
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"reflect"
|
|
"strings"
|
|
|
|
"github.com/purpleidea/mgmt/lang/funcs"
|
|
"github.com/purpleidea/mgmt/lang/funcs/structs"
|
|
"github.com/purpleidea/mgmt/lang/interfaces"
|
|
"github.com/purpleidea/mgmt/lang/types"
|
|
"github.com/purpleidea/mgmt/lang/unification"
|
|
"github.com/purpleidea/mgmt/pgraph"
|
|
"github.com/purpleidea/mgmt/resources"
|
|
"github.com/purpleidea/mgmt/util"
|
|
|
|
errwrap "github.com/pkg/errors"
|
|
)
|
|
|
|
// StmtBind is a representation of an assignment, which binds a variable to an
|
|
// expression.
|
|
type StmtBind struct {
|
|
Ident string
|
|
Value interfaces.Expr
|
|
}
|
|
|
|
// 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 *StmtBind) Interpolate() (interfaces.Stmt, error) {
|
|
interpolated, err := obj.Value.Interpolate()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &StmtBind{
|
|
Ident: obj.Ident,
|
|
Value: interpolated,
|
|
}, 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 *StmtBind) SetScope(scope *interfaces.Scope) error {
|
|
return obj.Value.SetScope(scope)
|
|
}
|
|
|
|
// 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) {
|
|
var invariants []interfaces.Invariant
|
|
|
|
invars, err := obj.Value.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. 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) {
|
|
return obj.Value.Graph()
|
|
}
|
|
|
|
// 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() (*interfaces.Output, error) {
|
|
return (&interfaces.Output{}).Empty(), nil
|
|
}
|
|
|
|
// StmtRes is a representation of a resource.
|
|
type StmtRes struct {
|
|
Kind string // kind of resource, eg: pkg, file, svc, etc...
|
|
Name interfaces.Expr // unique name for the res of this kind
|
|
Fields []*StmtResField // list of fields in parsed order
|
|
}
|
|
|
|
// 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.
|
|
// TODO: could we expand Name (if it's a list) to have this return a list of
|
|
// Res's ? We'd have to return a StmtProg containing those in its place...
|
|
func (obj *StmtRes) Interpolate() (interfaces.Stmt, error) {
|
|
name, err := obj.Name.Interpolate()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
fields := []*StmtResField{}
|
|
for _, x := range obj.Fields {
|
|
interpolated, err := x.Value.Interpolate()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
field := &StmtResField{
|
|
Field: x.Field,
|
|
Value: interpolated,
|
|
}
|
|
fields = append(fields, field)
|
|
}
|
|
return &StmtRes{
|
|
Kind: obj.Kind,
|
|
Name: name,
|
|
Fields: fields,
|
|
}, nil
|
|
}
|
|
|
|
// SetScope stores the scope for later use in this resource and it's children,
|
|
// which it propagates this downwards to.
|
|
func (obj *StmtRes) SetScope(scope *interfaces.Scope) error {
|
|
if err := obj.Name.SetScope(scope); err != nil {
|
|
return err
|
|
}
|
|
for _, x := range obj.Fields {
|
|
if err := x.Value.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
|
|
invar := &unification.EqualsInvariant{
|
|
Expr: obj.Name,
|
|
Type: types.TypeStr,
|
|
}
|
|
invariants = append(invariants, invar)
|
|
|
|
// collect all the invariants of each field
|
|
for _, x := range obj.Fields {
|
|
invars, err := x.Value.Unify()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
invariants = append(invariants, invars...)
|
|
}
|
|
|
|
typMap, err := resources.LangFieldNameToStructType(obj.Kind)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for _, x := range obj.Fields {
|
|
field := strings.TrimSpace(x.Field)
|
|
if len(field) != len(x.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[x.Field]
|
|
if !exists {
|
|
return nil, fmt.Errorf("could not determine type for `%s` field of `%s`", x.Field, obj.Kind)
|
|
}
|
|
invar := &unification.EqualsInvariant{
|
|
Expr: x.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 *StmtRes) Graph() (*pgraph.Graph, error) {
|
|
graph, err := pgraph.NewGraph("res")
|
|
if err != nil {
|
|
return nil, errwrap.Wrapf(err, "could not create graph")
|
|
}
|
|
|
|
g, err := obj.Name.Graph()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
graph.AddGraph(g)
|
|
|
|
for _, x := range obj.Fields {
|
|
g, err := x.Value.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.
|
|
// XXX: Add MetaParams...
|
|
func (obj *StmtRes) Output() (*interfaces.Output, error) {
|
|
nameValue, err := obj.Name.Value()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
name := nameValue.Str() // must not panic
|
|
|
|
res, err := resources.NewNamedResource(obj.Kind, name)
|
|
if err != nil {
|
|
return nil, errwrap.Wrapf(err, "cannot create resource kind `%s` with named `%s`", obj.Kind, name)
|
|
}
|
|
|
|
s := reflect.ValueOf(res).Elem() // pointer to struct, then struct
|
|
if k := s.Kind(); k != reflect.Struct {
|
|
panic(fmt.Sprintf("expected struct, got: %s", k))
|
|
}
|
|
|
|
mapping, err := resources.LangFieldNameToStructFieldName(obj.Kind)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
ts := reflect.TypeOf(res).Elem() // pointer to struct, then struct
|
|
|
|
// FIXME: we could probably simplify this code...
|
|
for _, x := range obj.Fields {
|
|
typ, err := x.Value.Type()
|
|
if err != nil {
|
|
return nil, errwrap.Wrapf(err, "resource field `%s` did not return a type", x.Field)
|
|
}
|
|
|
|
fieldValue, err := x.Value.Value() // Value method on Expr
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
val := fieldValue.Value() // get interface value
|
|
|
|
name, exists := mapping[x.Field] // lookup recommended field name
|
|
if !exists {
|
|
return nil, fmt.Errorf("field `%s` does not exist", x.Field) // user made a typo?
|
|
}
|
|
|
|
f := s.FieldByName(name) // exported field
|
|
if !f.IsValid() || !f.CanSet() {
|
|
return nil, fmt.Errorf("field `%s` cannot be set", name) // field is broken?
|
|
}
|
|
|
|
tf, exists := ts.FieldByName(name) // exported field type
|
|
if !exists { // illogical because of above check?
|
|
return nil, fmt.Errorf("field `%s` type does not exist", x.Field)
|
|
}
|
|
|
|
// is expr type compatible with expected field type?
|
|
t, err := types.TypeOf(tf.Type)
|
|
if err != nil {
|
|
return nil, errwrap.Wrapf(err, "resource field `%s` has no compatible type", x.Field)
|
|
}
|
|
if err := t.Cmp(typ); err != nil {
|
|
return nil, errwrap.Wrapf(err, "resource field `%s` of type `%+v`, cannot take type `%+v", x.Field, t, typ)
|
|
}
|
|
|
|
// user `pestle` on #go-nuts irc wrongly insisted that it wasn't
|
|
// right to use reflect to do all of this. what is a better way?
|
|
|
|
// first iterate through the raw pointers to the underlying type
|
|
ttt := tf.Type // ttt is field expected type
|
|
tkk := ttt.Kind()
|
|
for tkk == reflect.Ptr {
|
|
ttt = ttt.Elem() // un-nest one pointer
|
|
tkk = ttt.Kind()
|
|
}
|
|
|
|
// all our int's are src kind == reflect.Int64 in our language!
|
|
if interfaces.Debug {
|
|
log.Printf("field `%s`: type(%+v), expected(%+v)", x.Field, typ, tkk)
|
|
}
|
|
|
|
// overflow check
|
|
switch tkk { // match on destination field kind
|
|
case reflect.Int, reflect.Int64, reflect.Int32, reflect.Int16, reflect.Int8:
|
|
ff := reflect.Zero(ttt) // test on a non-ptr equivalent
|
|
if ff.OverflowInt(val.(int64)) { // this is valid!
|
|
return nil, fmt.Errorf("field `%s` is an `%s`, and value `%d` will overflow it", x.Field, f.Kind(), val)
|
|
}
|
|
|
|
case reflect.Uint, reflect.Uint64, reflect.Uint32, reflect.Uint16, reflect.Uint8:
|
|
ff := reflect.Zero(ttt)
|
|
if ff.OverflowUint(uint64(val.(int64))) { // TODO: is this correct?
|
|
return nil, fmt.Errorf("field `%s` is an `%s`, and value `%d` will overflow it", x.Field, f.Kind(), val)
|
|
}
|
|
|
|
case reflect.Float64, reflect.Float32:
|
|
ff := reflect.Zero(ttt)
|
|
if ff.OverflowFloat(val.(float64)) {
|
|
return nil, fmt.Errorf("field `%s` is an `%s`, and value `%d` will overflow it", x.Field, f.Kind(), val)
|
|
}
|
|
}
|
|
|
|
value := reflect.ValueOf(val) // raw value
|
|
value = value.Convert(ttt) // now convert our raw value properly
|
|
|
|
// finally build a new value to set
|
|
tt := tf.Type
|
|
kk := tt.Kind()
|
|
if interfaces.Debug {
|
|
log.Printf("field `%s`: start(%v)->kind(%v)", x.Field, tt, kk)
|
|
}
|
|
//fmt.Printf("start: %v || %+v\n", tt, kk)
|
|
for kk == reflect.Ptr {
|
|
tt = tt.Elem() // un-nest one pointer
|
|
kk = tt.Kind()
|
|
if interfaces.Debug {
|
|
log.Printf("field `%s`:\tloop(%v)->kind(%v)", x.Field, tt, kk)
|
|
}
|
|
// wrap in ptr by one level
|
|
valof := reflect.ValueOf(value.Interface())
|
|
value = reflect.New(valof.Type())
|
|
value.Elem().Set(valof)
|
|
}
|
|
f.Set(value) // set it !
|
|
}
|
|
|
|
return &interfaces.Output{
|
|
Resources: []resources.Res{res},
|
|
}, nil
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// 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.
|
|
// 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 {
|
|
name, err := x.Name.Interpolate()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
edgeHalf := &StmtEdgeHalf{
|
|
Kind: x.Kind,
|
|
Name: name,
|
|
SendRecv: x.SendRecv,
|
|
}
|
|
edgeHalfList = append(edgeHalfList, edgeHalf)
|
|
}
|
|
|
|
return &StmtEdge{
|
|
EdgeHalfList: edgeHalfList,
|
|
Notify: obj.Notify,
|
|
}, nil
|
|
}
|
|
|
|
// SetScope stores the scope for later use in this resource and it's children,
|
|
// which it propagates this downwards to.
|
|
func (obj *StmtEdge) SetScope(scope *interfaces.Scope) error {
|
|
for _, x := range obj.EdgeHalfList {
|
|
if err := x.Name.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 {
|
|
if (obj.EdgeHalfList[0].SendRecv == "") != (obj.EdgeHalfList[1].SendRecv == "") { // xor
|
|
return nil, fmt.Errorf("you must specify both send/recv fields or neither")
|
|
}
|
|
|
|
// XXX: check that the kind1:send -> kind2:recv fields are type
|
|
// compatible! XXX: we won't know the names yet, but it's okay.
|
|
}
|
|
|
|
for _, x := range obj.EdgeHalfList {
|
|
if x.Kind == "" {
|
|
return nil, fmt.Errorf("missing resource kind in edge")
|
|
}
|
|
|
|
if x.SendRecv != "" && len(obj.EdgeHalfList) != 2 {
|
|
return nil, fmt.Errorf("send/recv edges must come in pairs")
|
|
}
|
|
|
|
invars, err := x.Name.Unify()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
invariants = append(invariants, invars...)
|
|
|
|
// name must be a string
|
|
invar := &unification.EqualsInvariant{
|
|
Expr: x.Name,
|
|
Type: types.TypeStr,
|
|
}
|
|
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 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, errwrap.Wrapf(err, "could not create graph")
|
|
}
|
|
|
|
for _, x := range obj.EdgeHalfList {
|
|
g, err := x.Name.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() (*interfaces.Output, error) {
|
|
edges := []*interfaces.Edge{}
|
|
|
|
for i := 0; i < len(obj.EdgeHalfList)-1; i++ {
|
|
nameValue1, err := obj.EdgeHalfList[i].Name.Value()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
name1 := nameValue1.Str() // must not panic
|
|
|
|
nameValue2, err := obj.EdgeHalfList[i+1].Name.Value()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
name2 := nameValue2.Str() // must not panic
|
|
|
|
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
|
|
SendRecv string // name of field to send/recv from, empty to ignore
|
|
}
|
|
|
|
// 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
|
|
ThenBranch interfaces.Stmt // optional, but usually present
|
|
ElseBranch interfaces.Stmt // optional
|
|
}
|
|
|
|
// 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 *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
|
|
}
|
|
|
|
// SetScope stores the scope for later use in this resource and it's children,
|
|
// which it propagates this downwards to.
|
|
func (obj *StmtIf) SetScope(scope *interfaces.Scope) error {
|
|
if err := obj.Condition.SetScope(scope); 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 := &unification.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, errwrap.Wrapf(err, "could not create graph")
|
|
}
|
|
|
|
g, err := obj.Condition.Graph()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
graph.AddGraph(g)
|
|
|
|
for _, x := range []interfaces.Stmt{obj.ThenBranch, obj.ElseBranch} {
|
|
if x == nil {
|
|
continue
|
|
}
|
|
g, err := x.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() (*interfaces.Output, error) {
|
|
b, err := obj.Condition.Value()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var output *interfaces.Output
|
|
if b.Bool() { // must not panic!
|
|
if obj.ThenBranch != nil { // logically then branch is optional
|
|
output, err = obj.ThenBranch.Output()
|
|
}
|
|
} else {
|
|
if obj.ElseBranch != nil { // else branch is optional
|
|
output, err = obj.ElseBranch.Output()
|
|
}
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
resources := []resources.Res{}
|
|
if output != nil {
|
|
resources = append(resources, output.Resources...)
|
|
//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 {
|
|
Prog []interfaces.Stmt
|
|
}
|
|
|
|
// 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 *StmtProg) Interpolate() (interfaces.Stmt, error) {
|
|
prog := []interfaces.Stmt{}
|
|
for _, x := range obj.Prog {
|
|
interpolated, err := x.Interpolate()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
prog = append(prog, interpolated)
|
|
}
|
|
return &StmtProg{
|
|
Prog: prog,
|
|
}, nil
|
|
}
|
|
|
|
// SetScope propagates the scope into its list of statements. It does so
|
|
// cleverly by first collecting all bind statements and adding those into the
|
|
// scope after checking for any collisions. Finally it pushes the new scope
|
|
// downwards to all child statements.
|
|
func (obj *StmtProg) SetScope(scope *interfaces.Scope) error {
|
|
newScope := scope.Copy()
|
|
binds := []*StmtBind{}
|
|
names := make(map[string]struct{})
|
|
|
|
// collect all the bind statements in the first pass
|
|
// this allows them to appear out of order in this scope
|
|
for _, x := range obj.Prog {
|
|
bind, ok := x.(*StmtBind)
|
|
if !ok {
|
|
continue
|
|
}
|
|
// check for duplicates *in this scope*
|
|
if _, exists := names[bind.Ident]; exists {
|
|
return fmt.Errorf("var `%s` already exists in this scope", bind.Ident)
|
|
}
|
|
names[bind.Ident] = struct{}{} // add to scope
|
|
binds = append(binds, bind)
|
|
}
|
|
|
|
// now we know there are no duplicates in this scope, there is only
|
|
// the possibility of shadowing a variable from the parent scope...
|
|
for _, bind := range binds {
|
|
// add to scope, (overwriting, aka shadowing is ok)
|
|
newScope.Variables[bind.Ident] = bind.Value
|
|
}
|
|
|
|
// now set the child scopes (even on bind...)
|
|
for _, x := range obj.Prog {
|
|
if err := x.SetScope(newScope); 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 *StmtProg) Unify() ([]interfaces.Invariant, error) {
|
|
var invariants []interfaces.Invariant
|
|
|
|
// collect all the invariants of each sub-expression
|
|
for _, x := range obj.Prog {
|
|
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, errwrap.Wrapf(err, "could not create graph")
|
|
}
|
|
|
|
// collect all graphs that need to be included
|
|
for _, x := range obj.Prog {
|
|
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 *StmtProg) Output() (*interfaces.Output, error) {
|
|
resources := []resources.Res{}
|
|
|
|
for _, stmt := range obj.Prog {
|
|
output, err := stmt.Output()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if output != nil {
|
|
resources = append(resources, output.Resources...)
|
|
//edges = append(edges, output.Edges)
|
|
}
|
|
}
|
|
|
|
return &interfaces.Output{
|
|
Resources: resources,
|
|
//Edges: edges,
|
|
}, 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
|
|
}
|
|
|
|
// 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.
|
|
// Here it simply returns itself, as no interpolation is possible.
|
|
func (obj *StmtComment) Interpolate() (interfaces.Stmt, error) { return obj, 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) {
|
|
graph, err := pgraph.NewGraph("comment")
|
|
if err != nil {
|
|
return nil, errwrap.Wrapf(err, "could not create graph")
|
|
}
|
|
return graph, nil
|
|
}
|
|
|
|
// Output for the comment statement produces no output.
|
|
func (obj *StmtComment) Output() (*interfaces.Output, error) {
|
|
return (&interfaces.Output{}).Empty(), nil
|
|
}
|
|
|
|
// ExprBool is a representation of a boolean.
|
|
type ExprBool struct {
|
|
V bool
|
|
}
|
|
|
|
// String returns a short representation of this expression.
|
|
func (obj *ExprBool) String() string { return fmt.Sprintf("bool(%t)", obj.V) }
|
|
|
|
// 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.
|
|
// Here it simply returns itself, as no interpolation is possible.
|
|
func (obj *ExprBool) Interpolate() (interfaces.Expr, error) { return obj, 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 *ExprBool) SetScope(*interfaces.Scope) error { 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{
|
|
&unification.EqualsInvariant{
|
|
Expr: obj,
|
|
Type: types.TypeBool,
|
|
},
|
|
}
|
|
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 *ExprBool) Graph() (*pgraph.Graph, error) {
|
|
graph, err := pgraph.NewGraph("bool")
|
|
if err != nil {
|
|
return nil, errwrap.Wrapf(err, "could not create graph")
|
|
}
|
|
graph.AddVertex(obj)
|
|
return graph, 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
|
|
}
|
|
|
|
// 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
|
|
}
|
|
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 {
|
|
V string // value of this string
|
|
}
|
|
|
|
// String returns a short representation of this expression.
|
|
func (obj *ExprStr) String() string { return fmt.Sprintf("str(%s)", obj.V) }
|
|
|
|
// 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.
|
|
// 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 := &Pos{
|
|
// column/line number, starting at 1
|
|
//Column: -1, // TODO
|
|
//Line: -1, // TODO
|
|
//Filename: "", // optional source filename, if known
|
|
}
|
|
result, err := InterpolateStr(obj.V, pos)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if result == nil {
|
|
return obj, nil // pass self through, no changes
|
|
}
|
|
// we got something, overwrite the existing static str
|
|
return result, nil // replacement
|
|
}
|
|
|
|
// 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 *ExprStr) SetScope(*interfaces.Scope) error { 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{
|
|
&unification.EqualsInvariant{
|
|
Expr: obj, // unique id for this expression (a pointer)
|
|
Type: types.TypeStr,
|
|
},
|
|
}
|
|
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 *ExprStr) Graph() (*pgraph.Graph, error) {
|
|
graph, err := pgraph.NewGraph("str")
|
|
if err != nil {
|
|
return nil, errwrap.Wrapf(err, "could not create graph")
|
|
}
|
|
graph.AddVertex(obj)
|
|
return graph, 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
|
|
}
|
|
|
|
// 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 {
|
|
V int64
|
|
}
|
|
|
|
// String returns a short representation of this expression.
|
|
func (obj *ExprInt) String() string { return fmt.Sprintf("int(%d)", obj.V) }
|
|
|
|
// 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.
|
|
// Here it simply returns itself, as no interpolation is possible.
|
|
func (obj *ExprInt) Interpolate() (interfaces.Expr, error) { return obj, 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 *ExprInt) SetScope(*interfaces.Scope) error { 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{
|
|
&unification.EqualsInvariant{
|
|
Expr: obj,
|
|
Type: types.TypeInt,
|
|
},
|
|
}
|
|
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 *ExprInt) Graph() (*pgraph.Graph, error) {
|
|
graph, err := pgraph.NewGraph("int")
|
|
if err != nil {
|
|
return nil, errwrap.Wrapf(err, "could not create graph")
|
|
}
|
|
graph.AddVertex(obj)
|
|
return graph, 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
|
|
}
|
|
|
|
// 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 {
|
|
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?
|
|
}
|
|
|
|
// 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.
|
|
// Here it simply returns itself, as no interpolation is possible.
|
|
func (obj *ExprFloat) Interpolate() (interfaces.Expr, error) { return obj, 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 *ExprFloat) SetScope(*interfaces.Scope) error { 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{
|
|
&unification.EqualsInvariant{
|
|
Expr: obj,
|
|
Type: types.TypeFloat,
|
|
},
|
|
}
|
|
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 *ExprFloat) Graph() (*pgraph.Graph, error) {
|
|
graph, err := pgraph.NewGraph("float")
|
|
if err != nil {
|
|
return nil, errwrap.Wrapf(err, "could not create graph")
|
|
}
|
|
graph.AddVertex(obj)
|
|
return graph, 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
|
|
}
|
|
|
|
// 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 {
|
|
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, ", "))
|
|
}
|
|
|
|
// 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 *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{
|
|
typ: obj.typ,
|
|
Elements: elements,
|
|
}, nil
|
|
}
|
|
|
|
// SetScope stores the scope for later use in this resource and it's children,
|
|
// which it propagates this downwards to.
|
|
func (obj *ExprList) SetScope(scope *interfaces.Scope) error {
|
|
for _, x := range obj.Elements {
|
|
if err := x.SetScope(scope); 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 {
|
|
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 := &unification.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 := &unification.EqualityInvariantList{
|
|
Exprs: obj.Elements,
|
|
}
|
|
invariants = append(invariants, invariant)
|
|
}
|
|
|
|
// we should be type list of (type of element)
|
|
if len(obj.Elements) > 0 {
|
|
invariant := &unification.EqualityWrapListInvariant{
|
|
Expr1: obj, // unique id for this expression (a pointer)
|
|
Expr2Val: obj.Elements[0],
|
|
}
|
|
invariants = append(invariants, invariant)
|
|
}
|
|
|
|
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 *ExprList) Graph() (*pgraph.Graph, error) {
|
|
graph, err := pgraph.NewGraph("list")
|
|
if err != nil {
|
|
return nil, errwrap.Wrapf(err, "could not create graph")
|
|
}
|
|
graph.AddVertex(obj)
|
|
|
|
// each list element needs to point to the final list expression
|
|
for index, x := range obj.Elements { // list elements in order
|
|
g, err := x.Graph()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
fieldName := fmt.Sprintf("%d", index) // argNames as integers!
|
|
edge := &funcs.Edge{Args: []string{fieldName}}
|
|
|
|
var once bool
|
|
edgeGenFn := func(v1, v2 pgraph.Vertex) pgraph.Edge {
|
|
if once {
|
|
panic(fmt.Sprintf("edgeGenFn for list, index `%d` was called twice", index))
|
|
}
|
|
once = true
|
|
return edge
|
|
}
|
|
graph.AddEdgeGraphVertexLight(g, obj, edgeGenFn) // element -> list
|
|
}
|
|
|
|
return graph, 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
|
|
}
|
|
|
|
// 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 {
|
|
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, ", "))
|
|
}
|
|
|
|
// 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 *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{
|
|
typ: obj.typ,
|
|
KVs: kvs,
|
|
}, nil
|
|
}
|
|
|
|
// SetScope stores the scope for later use in this resource and it's children,
|
|
// which it propagates this downwards to.
|
|
func (obj *ExprMap) SetScope(scope *interfaces.Scope) error {
|
|
for _, x := range obj.KVs {
|
|
if err := x.Key.SetScope(scope); err != nil {
|
|
return err
|
|
}
|
|
if err := x.Val.SetScope(scope); 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 {
|
|
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 := &unification.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 := &unification.EqualityInvariantList{
|
|
Exprs: keyExprs,
|
|
}
|
|
invariants = append(invariants, keyInvariant)
|
|
|
|
valInvariant := &unification.EqualityInvariantList{
|
|
Exprs: valExprs,
|
|
}
|
|
invariants = append(invariants, valInvariant)
|
|
}
|
|
|
|
// we should be type map of (type of element)
|
|
if len(obj.KVs) > 0 {
|
|
invariant := &unification.EqualityWrapMapInvariant{
|
|
Expr1: obj, // unique id for this expression (a pointer)
|
|
Expr2Key: obj.KVs[0].Key,
|
|
Expr2Val: obj.KVs[0].Val,
|
|
}
|
|
invariants = append(invariants, invariant)
|
|
}
|
|
|
|
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 *ExprMap) Graph() (*pgraph.Graph, error) {
|
|
graph, err := pgraph.NewGraph("map")
|
|
if err != nil {
|
|
return nil, errwrap.Wrapf(err, "could not create graph")
|
|
}
|
|
graph.AddVertex(obj)
|
|
|
|
// each map key value pair needs to point to the final map expression
|
|
for index, x := range obj.KVs { // map fields in order
|
|
g, err := x.Key.Graph()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// do the key names ever change? -- yes
|
|
fieldName := fmt.Sprintf("key:%d", index) // stringify map key
|
|
edge := &funcs.Edge{Args: []string{fieldName}}
|
|
|
|
var once bool
|
|
edgeGenFn := func(v1, v2 pgraph.Vertex) pgraph.Edge {
|
|
if once {
|
|
panic(fmt.Sprintf("edgeGenFn for map, key `%s` was called twice", fieldName))
|
|
}
|
|
once = true
|
|
return edge
|
|
}
|
|
graph.AddEdgeGraphVertexLight(g, obj, edgeGenFn) // key -> func
|
|
}
|
|
|
|
// each map key value pair needs to point to the final map expression
|
|
for index, x := range obj.KVs { // map fields in order
|
|
g, err := x.Val.Graph()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
fieldName := fmt.Sprintf("val:%d", index) // stringify map val
|
|
edge := &funcs.Edge{Args: []string{fieldName}}
|
|
|
|
var once bool
|
|
edgeGenFn := func(v1, v2 pgraph.Vertex) pgraph.Edge {
|
|
if once {
|
|
panic(fmt.Sprintf("edgeGenFn for map, val `%s` was called twice", fieldName))
|
|
}
|
|
once = true
|
|
return edge
|
|
}
|
|
graph.AddEdgeGraphVertexLight(g, obj, edgeGenFn) // val -> func
|
|
}
|
|
|
|
return graph, 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
|
|
}
|
|
|
|
// 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 {
|
|
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, "; "))
|
|
}
|
|
|
|
// 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 *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{
|
|
typ: obj.typ,
|
|
Fields: fields,
|
|
}, nil
|
|
}
|
|
|
|
// SetScope stores the scope for later use in this resource and it's children,
|
|
// which it propagates this downwards to.
|
|
func (obj *ExprStruct) SetScope(scope *interfaces.Scope) error {
|
|
for _, x := range obj.Fields {
|
|
if err := x.Value.SetScope(scope); 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 `%s` 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 {
|
|
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 := &unification.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 := &unification.EqualityWrapStructInvariant{
|
|
Expr1: obj, // unique id for this expression (a pointer)
|
|
Expr2Map: mapped,
|
|
Expr2Ord: ordered,
|
|
}
|
|
invariants = append(invariants, invariant)
|
|
|
|
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 *ExprStruct) Graph() (*pgraph.Graph, error) {
|
|
graph, err := pgraph.NewGraph("struct")
|
|
if err != nil {
|
|
return nil, errwrap.Wrapf(err, "could not create graph")
|
|
}
|
|
graph.AddVertex(obj)
|
|
|
|
// each struct field needs to point to the final struct expression
|
|
for _, x := range obj.Fields { // struct fields in order
|
|
g, err := x.Value.Graph()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
fieldName := x.Name
|
|
edge := &funcs.Edge{Args: []string{fieldName}}
|
|
|
|
var once bool
|
|
edgeGenFn := func(v1, v2 pgraph.Vertex) pgraph.Edge {
|
|
if once {
|
|
panic(fmt.Sprintf("edgeGenFn for struct, arg `%s` was called twice", fieldName))
|
|
}
|
|
once = true
|
|
return edge
|
|
}
|
|
graph.AddEdgeGraphVertexLight(g, obj, edgeGenFn) // arg -> func
|
|
}
|
|
|
|
return graph, 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
|
|
}
|
|
|
|
// 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 `%s` 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.
|
|
// XXX: this is currently not fully implemented, and parts may be incorrect.
|
|
type ExprFunc struct {
|
|
typ *types.Type
|
|
|
|
V func([]types.Value) (types.Value, error)
|
|
}
|
|
|
|
// String returns a short representation of this expression.
|
|
// FIXME: fmt.Sprintf("func(%+v)", obj.V) fails `go vet` (bug?), so wait until
|
|
// we have a better printable function value and put that here instead.
|
|
func (obj *ExprFunc) String() string { return fmt.Sprintf("func(???)") } // TODO: print nicely
|
|
|
|
// 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.
|
|
// Here it simply returns itself, as no interpolation is possible.
|
|
func (obj *ExprFunc) Interpolate() (interfaces.Expr, error) { return obj, nil }
|
|
|
|
// SetScope does nothing for this struct, because it has no child nodes, and it
|
|
// does not need to know about the parent scope.
|
|
// XXX: this may not be true in the future...
|
|
func (obj *ExprFunc) SetScope(*interfaces.Scope) error { 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 {
|
|
// TODO: should we ensure this is set to a KindFunc ?
|
|
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 *ExprFunc) Type() (*types.Type, error) {
|
|
// TODO: implement speculative type lookup (if not already sufficient)
|
|
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 *ExprFunc) Unify() ([]interfaces.Invariant, error) {
|
|
return nil, fmt.Errorf("not implemented") // XXX: not implemented
|
|
}
|
|
|
|
// 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() (*pgraph.Graph, error) {
|
|
return nil, fmt.Errorf("not implemented") // XXX: not implemented
|
|
}
|
|
|
|
// Func returns the reactive stream of values that this expression produces.
|
|
func (obj *ExprFunc) Func() (interfaces.Func, error) {
|
|
return nil, fmt.Errorf("not implemented") // XXX: not implemented
|
|
}
|
|
|
|
// 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 {
|
|
return obj.typ.Cmp(value.Type())
|
|
}
|
|
|
|
// 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) {
|
|
// TODO: implement speculative value lookup (if not already sufficient)
|
|
return &types.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.
|
|
type ExprCall struct {
|
|
typ *types.Type
|
|
|
|
V types.Value // stored result (set with SetValue)
|
|
|
|
Name string
|
|
Args []interfaces.Expr // list of args in parsed order
|
|
}
|
|
|
|
// 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, ", "))
|
|
}
|
|
|
|
// buildType builds the KindFunc type of this function's signature if it can. It
|
|
// might not be able to if type unification hasn't yet been performed on this
|
|
// expression, and if SetType hasn't yet been called for the needed expressions.
|
|
// XXX: review this function logic please
|
|
func (obj *ExprCall) buildType() (*types.Type, error) {
|
|
|
|
m := make(map[string]*types.Type)
|
|
ord := []string{}
|
|
for pos, x := range obj.Args { // function arguments in order
|
|
t, err := x.Type()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
name := util.NumToAlpha(pos) // assume (incorrectly) for now...
|
|
//name := argNames[pos]
|
|
m[name] = t
|
|
ord = append(ord, name)
|
|
}
|
|
|
|
out, err := obj.Type()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &types.Type{
|
|
Kind: types.KindFunc,
|
|
Map: m,
|
|
Ord: ord,
|
|
Out: out,
|
|
}, nil
|
|
}
|
|
|
|
// buildFunc prepares and returns the function struct object needed for running
|
|
// this function execution.
|
|
// XXX: review this function logic please
|
|
func (obj *ExprCall) buildFunc() (interfaces.Func, error) {
|
|
// TODO: if we have locally defined functions that can exist in scope,
|
|
// then perhaps we should do a lookup here before we use the built-in.
|
|
//fn, exists := obj.scope.Functions[obj.Name] // look for a local function
|
|
// Remember that a local function might have Invariants it needs to add!
|
|
|
|
fn, err := funcs.Lookup(obj.Name) // lookup the function by name
|
|
if err != nil {
|
|
return nil, errwrap.Wrapf(err, "func `%s` could not be found", obj.Name)
|
|
}
|
|
|
|
polyFn, ok := fn.(interfaces.PolyFunc) // is it statically polymorphic?
|
|
if !ok {
|
|
return fn, nil
|
|
}
|
|
|
|
// PolyFunc's need more things done!
|
|
typ, err := obj.buildType()
|
|
if err == nil { // if we've errored, that's okay, this part isn't ready
|
|
if err := polyFn.Build(typ); err != nil {
|
|
return nil, errwrap.Wrapf(err, "could not build func `%s`", obj.Name)
|
|
}
|
|
}
|
|
return fn, 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 *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)
|
|
}
|
|
return &ExprCall{
|
|
typ: obj.typ,
|
|
Name: obj.Name,
|
|
Args: args,
|
|
}, nil
|
|
}
|
|
|
|
// SetScope stores the scope for later use in this resource and it's children,
|
|
// which it propagates this downwards to.
|
|
func (obj *ExprCall) SetScope(scope *interfaces.Scope) error {
|
|
for _, x := range obj.Args {
|
|
if err := x.SetScope(scope); 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. 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) {
|
|
fn, err := funcs.Lookup(obj.Name) // lookup the function by name
|
|
_, isPoly := fn.(interfaces.PolyFunc) // is it statically polymorphic?
|
|
if err == nil && obj.typ == nil && !isPoly {
|
|
if info := fn.Info(); info != nil {
|
|
if sig := info.Sig; sig != nil {
|
|
if typ := sig.Out; typ != nil && !typ.HasVariant() {
|
|
return typ, nil // speculate!
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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) {
|
|
var invariants []interfaces.Invariant
|
|
|
|
// if this was set explicitly by the parser
|
|
if obj.typ != nil {
|
|
invar := &unification.EqualsInvariant{
|
|
Expr: obj,
|
|
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...)
|
|
}
|
|
|
|
fn, err := obj.buildFunc() // uses obj.Name to build the func
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// XXX: can we put this inside the poly branch or is it needed everywhere?
|
|
// XXX: is there code we can pull out of this branch to use for all functions?
|
|
argNames := []string{}
|
|
mapped := make(map[string]*types.Type)
|
|
partialValues := []types.Value{}
|
|
for i := range obj.Args {
|
|
name := util.NumToAlpha(i) // assume (incorrectly) for now...
|
|
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
|
|
}
|
|
}
|
|
}
|
|
|
|
// do we have a special case like the operator or template function?
|
|
polyFn, ok := fn.(interfaces.PolyFunc) // is it statically polymorphic?
|
|
if ok {
|
|
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
|
|
}
|
|
|
|
results, err := polyFn.Polymorphisms(partialType, partialValues)
|
|
if err != nil {
|
|
return nil, errwrap.Wrapf(err, "polymorphic signatures for func `%s` could not be found", obj.Name)
|
|
}
|
|
|
|
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: ¯\_(ツ)_/¯
|
|
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: ¯\_(ツ)_/¯
|
|
invar := &unification.AnyInvariant{ // XXX: ???
|
|
Expr: obj.Args[i],
|
|
}
|
|
invars = append(invars, invar)
|
|
continue
|
|
}
|
|
invar := &unification.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: ¯\_(ツ)_/¯
|
|
invar := &unification.AnyInvariant{ // XXX: ???
|
|
Expr: obj,
|
|
}
|
|
invars = append(invars, invar)
|
|
} else {
|
|
invar := &unification.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)
|
|
}
|
|
|
|
// unused expression, here only for linking...
|
|
// TODO: eventually like with proper ExprFunc in lang?
|
|
exprFunc := &ExprFunc{}
|
|
if !typ.HasVariant() { // XXX: ¯\_(ツ)_/¯
|
|
exprFunc.SetType(typ)
|
|
funcInvariant := &unification.EqualsInvariant{
|
|
Expr: exprFunc,
|
|
Type: typ,
|
|
}
|
|
invars = append(invars, funcInvariant)
|
|
}
|
|
invar := &unification.EqualityWrapFuncInvariant{
|
|
Expr1: exprFunc,
|
|
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 := &unification.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 = &unification.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)
|
|
}
|
|
|
|
} else {
|
|
sig := fn.Info().Sig
|
|
// build the reference to ourself if we have undetermined arg types
|
|
mapped := make(map[string]interfaces.Expr)
|
|
ordered := []string{}
|
|
for pos, x := range obj.Args {
|
|
name := argNames[pos]
|
|
mapped[name] = x
|
|
ordered = append(ordered, name)
|
|
}
|
|
|
|
// add an unused expression, because we need to link it to the partial
|
|
exprFunc := &ExprFunc{}
|
|
exprFunc.SetType(sig)
|
|
funcInvariant := &unification.EqualsInvariant{
|
|
Expr: exprFunc,
|
|
Type: sig,
|
|
}
|
|
invariants = append(invariants, funcInvariant)
|
|
|
|
// 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...
|
|
invariant := &unification.EqualityWrapFuncInvariant{
|
|
Expr1: exprFunc, // unused placeholder for unification
|
|
Expr2Map: mapped,
|
|
Expr2Ord: ordered,
|
|
Expr2Out: obj, // type of expression is return type of function
|
|
}
|
|
invariants = append(invariants, invariant)
|
|
}
|
|
|
|
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() (*pgraph.Graph, error) {
|
|
graph, err := pgraph.NewGraph("func")
|
|
if err != nil {
|
|
return nil, errwrap.Wrapf(err, "could not create graph")
|
|
}
|
|
graph.AddVertex(obj)
|
|
|
|
fn, err := obj.buildFunc() // uses obj.Name to build the func
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
argNames := fn.Info().Sig.Ord
|
|
if len(argNames) != len(obj.Args) { // extra safety...
|
|
return nil, fmt.Errorf("func `%s` expected %d args, got %d", obj.Name, len(argNames), len(obj.Args))
|
|
}
|
|
|
|
// each function argument needs to point to the final function expression
|
|
for pos, x := range obj.Args { // function arguments in order
|
|
g, err := x.Graph()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
argName := argNames[pos]
|
|
edge := &funcs.Edge{Args: []string{argName}}
|
|
|
|
var once bool
|
|
edgeGenFn := func(v1, v2 pgraph.Vertex) pgraph.Edge {
|
|
if once {
|
|
panic(fmt.Sprintf("edgeGenFn for func `%s`, arg `%s` was called twice", obj.Name, argName))
|
|
}
|
|
once = true
|
|
return edge
|
|
}
|
|
graph.AddEdgeGraphVertexLight(g, obj, edgeGenFn) // arg -> func
|
|
}
|
|
|
|
return graph, nil
|
|
}
|
|
|
|
// Func returns the reactive stream of values that this expression produces.
|
|
func (obj *ExprCall) Func() (interfaces.Func, error) {
|
|
return obj.buildFunc() // uses obj.Name to build the func
|
|
}
|
|
|
|
// 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) }
|
|
|
|
// 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.
|
|
// 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 obj, nil }
|
|
|
|
// SetScope stores the scope for use in this resource.
|
|
func (obj *ExprVar) SetScope(scope *interfaces.Scope) error {
|
|
if scope == nil {
|
|
scope = scope.Empty()
|
|
}
|
|
obj.scope = scope
|
|
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) {
|
|
// return type if it is already known statically...
|
|
// it is useful for type unification to have some extra info
|
|
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)
|
|
}
|
|
|
|
// don't recurse because we already got this through the bind statement
|
|
//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 := &unification.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() (*pgraph.Graph, error) {
|
|
graph, err := pgraph.NewGraph("var")
|
|
if err != nil {
|
|
return nil, errwrap.Wrapf(err, "could not create graph")
|
|
}
|
|
graph.AddVertex(obj)
|
|
|
|
// ??? = $foo (this is the foo)
|
|
// 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)
|
|
}
|
|
|
|
// should already exist in graph (i think)...
|
|
graph.AddVertex(expr) // duplicate additions are ignored and are harmless
|
|
|
|
// the expr needs to point to the var lookup expression
|
|
g, err := expr.Graph()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
edge := &funcs.Edge{Args: []string{obj.Name}}
|
|
|
|
var once bool
|
|
edgeGenFn := func(v1, v2 pgraph.Vertex) pgraph.Edge {
|
|
if once {
|
|
panic(fmt.Sprintf("edgeGenFn for var `%s` was called twice", obj.Name))
|
|
}
|
|
once = true
|
|
return edge
|
|
}
|
|
graph.AddEdgeGraphVertexLight(g, obj, edgeGenFn) // expr -> var
|
|
|
|
return graph, nil
|
|
}
|
|
|
|
// Func returns a "pass-through" function which receives the bound value, and
|
|
// passes it to the consumer. This is essential for satisfying the type checker
|
|
// of the function graph engine.
|
|
func (obj *ExprVar) Func() (interfaces.Func, error) {
|
|
expr, exists := obj.scope.Variables[obj.Name]
|
|
if !exists {
|
|
return nil, fmt.Errorf("var `%s` does not exist in scope", obj.Name)
|
|
}
|
|
|
|
// this is wrong, if we did it this way, this expr wouldn't exist as a
|
|
// distinct node in the function graph to relay values through, instead,
|
|
// it would be acting as a "substitution/lookup" function, which just
|
|
// copies the bound function over into here. As a result, we'd have N
|
|
// copies of that function (based on the number of times N that that
|
|
// variable is used) instead of having that single bound function as
|
|
// input which is sent via N different edges to the multiple locations
|
|
// where the variables are used. Since the bound function would still
|
|
// have a single unique pointer, this wouldn't actually exist more than
|
|
// once in the graph, although since it's illogical, it causes the graph
|
|
// type checking (the edge counting in the function graph engine) to
|
|
// notice a problem and error.
|
|
//return expr.Func() // recurse?
|
|
|
|
// instead, return a function which correctly does a lookup in the scope
|
|
// and returns *that* stream of values instead.
|
|
typ, err := obj.Type()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
f, err := expr.Func()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// var func
|
|
return &structs.VarFunc{
|
|
Type: typ,
|
|
Func: f,
|
|
Edge: obj.Name, // the edge name used above in Graph is this...
|
|
}, 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 *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
|
|
}
|
|
|
|
// 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 {
|
|
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 {
|
|
return fmt.Sprintf("if(%s)", obj.Condition.String()) // TODO: improve this
|
|
}
|
|
|
|
// 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 *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{
|
|
typ: obj.typ,
|
|
Condition: condition,
|
|
ThenBranch: thenBranch,
|
|
ElseBranch: elseBranch,
|
|
}, nil
|
|
}
|
|
|
|
// SetScope stores the scope for later use in this resource and it's children,
|
|
// which it propagates this downwards to.
|
|
func (obj *ExprIf) SetScope(scope *interfaces.Scope) error {
|
|
if err := obj.ThenBranch.SetScope(scope); err != nil {
|
|
return err
|
|
}
|
|
if err := obj.ElseBranch.SetScope(scope); err != nil {
|
|
return err
|
|
}
|
|
return obj.Condition.SetScope(scope)
|
|
}
|
|
|
|
// 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) {
|
|
boolValue, err := obj.Condition.Value() // attempt early speculation
|
|
if err == nil && obj.typ == nil {
|
|
branch := obj.ElseBranch
|
|
if boolValue.Bool() { // must not panic
|
|
branch = obj.ThenBranch
|
|
}
|
|
return branch.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 *ExprIf) 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 := &unification.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 := &unification.EqualityInvariant{
|
|
Expr1: obj.ThenBranch,
|
|
Expr2: obj.ElseBranch,
|
|
}
|
|
invariants = append(invariants, branchesInvar)
|
|
|
|
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 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() (*pgraph.Graph, error) {
|
|
graph, err := pgraph.NewGraph("if")
|
|
if err != nil {
|
|
return nil, errwrap.Wrapf(err, "could not create graph")
|
|
}
|
|
graph.AddVertex(obj)
|
|
|
|
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, err := x.Graph()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
edge := &funcs.Edge{Args: []string{argName}}
|
|
|
|
var once bool
|
|
edgeGenFn := func(v1, v2 pgraph.Vertex) pgraph.Edge {
|
|
if once {
|
|
panic(fmt.Sprintf("edgeGenFn for ifexpr edge `%s` was called twice", argName))
|
|
}
|
|
once = true
|
|
return edge
|
|
}
|
|
graph.AddEdgeGraphVertexLight(g, obj, edgeGenFn) // branch -> if
|
|
}
|
|
|
|
return graph, 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
|
|
}
|
|
|
|
// 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()
|
|
}
|