lang: Add internal, resource specific edges
This adds the ability to specify internal, resource specific edges, with and without notifications. We use the special words: "Notify", "Before", "Listen", and "Depend". They must have the first character capitalized. They also support the "elvis" operator.
This commit is contained in:
@@ -163,6 +163,45 @@ it evaluates to `true`, then the parameter will be used. If no `elvis` operator
|
|||||||
is specified, then the parameter value will also be used. If the parameter is
|
is specified, then the parameter value will also be used. If the parameter is
|
||||||
not specified, then it will obviously not be used.
|
not specified, then it will obviously not be used.
|
||||||
|
|
||||||
|
Resources may also declare edges internally. The edges may point to or from
|
||||||
|
another resource, and may optionally include a notification. The four properties
|
||||||
|
are: `Before`, `Depend`, `Notify` and `Listen`. The first two represent normal
|
||||||
|
edge dependencies, and the second two are normal edge dependencies that also
|
||||||
|
send notifications. You may have multiples of these per resource, including
|
||||||
|
multiple `Depend` lines if necessary. Each of these properties also supports the
|
||||||
|
conditional inclusion `elvis` operator as well.
|
||||||
|
|
||||||
|
For example, you may write is:
|
||||||
|
|
||||||
|
```mcl
|
||||||
|
$b = true # for example purposes
|
||||||
|
if $b {
|
||||||
|
pkg "drbd" {
|
||||||
|
state => "installed",
|
||||||
|
|
||||||
|
# multiple properties may be used in the same resource
|
||||||
|
Before => File["/etc/drbd.conf"],
|
||||||
|
Before => Svc["drbd"],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file "/etc/drbd.conf" {
|
||||||
|
content => "some config",
|
||||||
|
|
||||||
|
Depend => $b ?: Pkg["drbd"],
|
||||||
|
Notify => Svc["drbd"],
|
||||||
|
}
|
||||||
|
svc "drbd" {
|
||||||
|
state => "running",
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
There are two unique properties about these edges that is different from what
|
||||||
|
you might expect from other automation software:
|
||||||
|
|
||||||
|
1. The ability to specify multiples of these properties allows you to avoid
|
||||||
|
having to manage arrays and conditional trees of these different dependencies.
|
||||||
|
2. The keywords all have the same length, which means your code lines up nicely.
|
||||||
|
|
||||||
#### Edge
|
#### Edge
|
||||||
|
|
||||||
Edges express dependencies in the graph of resources which are output. They can
|
Edges express dependencies in the graph of resources which are output. They can
|
||||||
|
|||||||
18
examples/lang/edges1.mcl
Normal file
18
examples/lang/edges1.mcl
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
$b = true
|
||||||
|
if $b {
|
||||||
|
exec "exec0" {
|
||||||
|
cmd => "sleep 10s",
|
||||||
|
shell => "/bin/bash",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exec "exec1" {
|
||||||
|
cmd => "sleep 10s",
|
||||||
|
shell => "/bin/bash",
|
||||||
|
|
||||||
|
Depend => $b ?: Exec["exec0"],
|
||||||
|
Before => Exec["exec2"],
|
||||||
|
}
|
||||||
|
exec "exec2" {
|
||||||
|
cmd => "sleep 10s",
|
||||||
|
shell => "/bin/bash",
|
||||||
|
}
|
||||||
@@ -59,8 +59,8 @@ func TestInterpolate0(t *testing.T) {
|
|||||||
Name: &ExprStr{
|
Name: &ExprStr{
|
||||||
V: "t1",
|
V: "t1",
|
||||||
},
|
},
|
||||||
Fields: []*StmtResField{
|
Contents: []StmtResContents{
|
||||||
{
|
&StmtResField{
|
||||||
Field: "stringptr",
|
Field: "stringptr",
|
||||||
Value: &ExprStr{
|
Value: &ExprStr{
|
||||||
V: "foo",
|
V: "foo",
|
||||||
@@ -103,8 +103,8 @@ func TestInterpolate0(t *testing.T) {
|
|||||||
Name: &ExprStr{
|
Name: &ExprStr{
|
||||||
V: "t1",
|
V: "t1",
|
||||||
},
|
},
|
||||||
Fields: []*StmtResField{
|
Contents: []StmtResContents{
|
||||||
{
|
&StmtResField{
|
||||||
Field: "stringptr",
|
Field: "stringptr",
|
||||||
Value: fieldName,
|
Value: fieldName,
|
||||||
},
|
},
|
||||||
@@ -190,8 +190,8 @@ func TestInterpolateBasicStmt(t *testing.T) {
|
|||||||
Name: &ExprStr{
|
Name: &ExprStr{
|
||||||
V: "t1",
|
V: "t1",
|
||||||
},
|
},
|
||||||
Fields: []*StmtResField{
|
Contents: []StmtResContents{
|
||||||
{
|
&StmtResField{
|
||||||
Field: "stringptr",
|
Field: "stringptr",
|
||||||
Value: &ExprStr{
|
Value: &ExprStr{
|
||||||
V: "foo",
|
V: "foo",
|
||||||
@@ -208,8 +208,8 @@ func TestInterpolateBasicStmt(t *testing.T) {
|
|||||||
Name: &ExprStr{
|
Name: &ExprStr{
|
||||||
V: "t1",
|
V: "t1",
|
||||||
},
|
},
|
||||||
Fields: []*StmtResField{
|
Contents: []StmtResContents{
|
||||||
{
|
&StmtResField{
|
||||||
Field: "stringptr",
|
Field: "stringptr",
|
||||||
Value: &ExprStr{
|
Value: &ExprStr{
|
||||||
V: "foo",
|
V: "foo",
|
||||||
@@ -234,8 +234,8 @@ func TestInterpolateBasicStmt(t *testing.T) {
|
|||||||
Name: &ExprStr{
|
Name: &ExprStr{
|
||||||
V: "t${blah}",
|
V: "t${blah}",
|
||||||
},
|
},
|
||||||
Fields: []*StmtResField{
|
Contents: []StmtResContents{
|
||||||
{
|
&StmtResField{
|
||||||
Field: "stringptr",
|
Field: "stringptr",
|
||||||
Value: &ExprStr{
|
Value: &ExprStr{
|
||||||
V: "foo",
|
V: "foo",
|
||||||
@@ -264,8 +264,8 @@ func TestInterpolateBasicStmt(t *testing.T) {
|
|||||||
&StmtRes{
|
&StmtRes{
|
||||||
Kind: "test",
|
Kind: "test",
|
||||||
Name: resName,
|
Name: resName,
|
||||||
Fields: []*StmtResField{
|
Contents: []StmtResContents{
|
||||||
{
|
&StmtResField{
|
||||||
Field: "stringptr",
|
Field: "stringptr",
|
||||||
Value: &ExprStr{
|
Value: &ExprStr{
|
||||||
V: "foo",
|
V: "foo",
|
||||||
@@ -290,8 +290,8 @@ func TestInterpolateBasicStmt(t *testing.T) {
|
|||||||
Name: &ExprStr{
|
Name: &ExprStr{
|
||||||
V: "t${42}", // incorrect type
|
V: "t${42}", // incorrect type
|
||||||
},
|
},
|
||||||
Fields: []*StmtResField{
|
Contents: []StmtResContents{
|
||||||
{
|
&StmtResField{
|
||||||
Field: "stringptr",
|
Field: "stringptr",
|
||||||
Value: &ExprStr{
|
Value: &ExprStr{
|
||||||
V: "foo",
|
V: "foo",
|
||||||
@@ -321,8 +321,8 @@ func TestInterpolateBasicStmt(t *testing.T) {
|
|||||||
&StmtRes{
|
&StmtRes{
|
||||||
Kind: "test",
|
Kind: "test",
|
||||||
Name: resName,
|
Name: resName,
|
||||||
Fields: []*StmtResField{
|
Contents: []StmtResContents{
|
||||||
{
|
&StmtResField{
|
||||||
Field: "stringptr",
|
Field: "stringptr",
|
||||||
Value: &ExprStr{
|
Value: &ExprStr{
|
||||||
V: "foo",
|
V: "foo",
|
||||||
|
|||||||
@@ -320,8 +320,8 @@ func TestLexParse0(t *testing.T) {
|
|||||||
Name: &ExprStr{
|
Name: &ExprStr{
|
||||||
V: "t1",
|
V: "t1",
|
||||||
},
|
},
|
||||||
Fields: []*StmtResField{
|
Contents: []StmtResContents{
|
||||||
{
|
&StmtResField{
|
||||||
Field: "int64ptr",
|
Field: "int64ptr",
|
||||||
Value: &ExprCall{
|
Value: &ExprCall{
|
||||||
Name: operatorFuncName,
|
Name: operatorFuncName,
|
||||||
@@ -361,8 +361,8 @@ func TestLexParse0(t *testing.T) {
|
|||||||
Name: &ExprStr{
|
Name: &ExprStr{
|
||||||
V: "t1",
|
V: "t1",
|
||||||
},
|
},
|
||||||
Fields: []*StmtResField{
|
Contents: []StmtResContents{
|
||||||
{
|
&StmtResField{
|
||||||
Field: "float32",
|
Field: "float32",
|
||||||
Value: &ExprCall{
|
Value: &ExprCall{
|
||||||
Name: operatorFuncName,
|
Name: operatorFuncName,
|
||||||
@@ -413,8 +413,8 @@ func TestLexParse0(t *testing.T) {
|
|||||||
Name: &ExprStr{
|
Name: &ExprStr{
|
||||||
V: "t1",
|
V: "t1",
|
||||||
},
|
},
|
||||||
Fields: []*StmtResField{
|
Contents: []StmtResContents{
|
||||||
{
|
&StmtResField{
|
||||||
Field: "int64ptr",
|
Field: "int64ptr",
|
||||||
Value: &ExprCall{
|
Value: &ExprCall{
|
||||||
Name: operatorFuncName,
|
Name: operatorFuncName,
|
||||||
@@ -465,8 +465,8 @@ func TestLexParse0(t *testing.T) {
|
|||||||
Name: &ExprStr{
|
Name: &ExprStr{
|
||||||
V: "t1",
|
V: "t1",
|
||||||
},
|
},
|
||||||
Fields: []*StmtResField{
|
Contents: []StmtResContents{
|
||||||
{
|
&StmtResField{
|
||||||
Field: "int64ptr",
|
Field: "int64ptr",
|
||||||
Value: &ExprCall{
|
Value: &ExprCall{
|
||||||
Name: operatorFuncName,
|
Name: operatorFuncName,
|
||||||
@@ -517,8 +517,8 @@ func TestLexParse0(t *testing.T) {
|
|||||||
Name: &ExprStr{
|
Name: &ExprStr{
|
||||||
V: "t1",
|
V: "t1",
|
||||||
},
|
},
|
||||||
Fields: []*StmtResField{
|
Contents: []StmtResContents{
|
||||||
{
|
&StmtResField{
|
||||||
Field: "int64ptr",
|
Field: "int64ptr",
|
||||||
Value: &ExprCall{
|
Value: &ExprCall{
|
||||||
Name: operatorFuncName,
|
Name: operatorFuncName,
|
||||||
@@ -569,8 +569,8 @@ func TestLexParse0(t *testing.T) {
|
|||||||
Name: &ExprStr{
|
Name: &ExprStr{
|
||||||
V: "t1",
|
V: "t1",
|
||||||
},
|
},
|
||||||
Fields: []*StmtResField{
|
Contents: []StmtResContents{
|
||||||
{
|
&StmtResField{
|
||||||
Field: "boolptr",
|
Field: "boolptr",
|
||||||
Value: &ExprCall{
|
Value: &ExprCall{
|
||||||
Name: operatorFuncName,
|
Name: operatorFuncName,
|
||||||
@@ -621,8 +621,8 @@ func TestLexParse0(t *testing.T) {
|
|||||||
Name: &ExprStr{
|
Name: &ExprStr{
|
||||||
V: "t1",
|
V: "t1",
|
||||||
},
|
},
|
||||||
Fields: []*StmtResField{
|
Contents: []StmtResContents{
|
||||||
{
|
&StmtResField{
|
||||||
Field: "boolptr",
|
Field: "boolptr",
|
||||||
Value: &ExprCall{
|
Value: &ExprCall{
|
||||||
Name: operatorFuncName,
|
Name: operatorFuncName,
|
||||||
@@ -673,8 +673,8 @@ func TestLexParse0(t *testing.T) {
|
|||||||
Name: &ExprStr{
|
Name: &ExprStr{
|
||||||
V: "t1",
|
V: "t1",
|
||||||
},
|
},
|
||||||
Fields: []*StmtResField{
|
Contents: []StmtResContents{
|
||||||
{
|
&StmtResField{
|
||||||
Field: "boolptr",
|
Field: "boolptr",
|
||||||
Value: &ExprCall{
|
Value: &ExprCall{
|
||||||
Name: operatorFuncName,
|
Name: operatorFuncName,
|
||||||
@@ -722,8 +722,8 @@ func TestLexParse0(t *testing.T) {
|
|||||||
Name: &ExprStr{
|
Name: &ExprStr{
|
||||||
V: "t1",
|
V: "t1",
|
||||||
},
|
},
|
||||||
Fields: []*StmtResField{
|
Contents: []StmtResContents{
|
||||||
{
|
&StmtResField{
|
||||||
Field: "boolptr",
|
Field: "boolptr",
|
||||||
Value: &ExprCall{
|
Value: &ExprCall{
|
||||||
Name: operatorFuncName,
|
Name: operatorFuncName,
|
||||||
@@ -774,8 +774,8 @@ func TestLexParse0(t *testing.T) {
|
|||||||
Name: &ExprStr{
|
Name: &ExprStr{
|
||||||
V: "t1",
|
V: "t1",
|
||||||
},
|
},
|
||||||
Fields: []*StmtResField{
|
Contents: []StmtResContents{
|
||||||
{
|
&StmtResField{
|
||||||
Field: "int64ptr",
|
Field: "int64ptr",
|
||||||
Value: &ExprInt{
|
Value: &ExprInt{
|
||||||
V: 42,
|
V: 42,
|
||||||
@@ -788,8 +788,8 @@ func TestLexParse0(t *testing.T) {
|
|||||||
Name: &ExprStr{
|
Name: &ExprStr{
|
||||||
V: "t2",
|
V: "t2",
|
||||||
},
|
},
|
||||||
Fields: []*StmtResField{
|
Contents: []StmtResContents{
|
||||||
{
|
&StmtResField{
|
||||||
Field: "int64ptr",
|
Field: "int64ptr",
|
||||||
Value: &ExprInt{
|
Value: &ExprInt{
|
||||||
V: 13,
|
V: 13,
|
||||||
|
|||||||
@@ -63,8 +63,9 @@ func init() {
|
|||||||
structFields []*ExprStructField
|
structFields []*ExprStructField
|
||||||
structField *ExprStructField
|
structField *ExprStructField
|
||||||
|
|
||||||
resFields []*StmtResField
|
resContents []StmtResContents // interface
|
||||||
resField *StmtResField
|
resField *StmtResField
|
||||||
|
resEdge *StmtResEdge
|
||||||
|
|
||||||
edgeHalfList []*StmtEdgeHalf
|
edgeHalfList []*StmtEdgeHalf
|
||||||
edgeHalf *StmtEdgeHalf
|
edgeHalf *StmtEdgeHalf
|
||||||
@@ -667,7 +668,7 @@ resource:
|
|||||||
$$.stmt = &StmtRes{
|
$$.stmt = &StmtRes{
|
||||||
Kind: $1.str,
|
Kind: $1.str,
|
||||||
Name: $2.expr,
|
Name: $2.expr,
|
||||||
Fields: $4.resFields,
|
Contents: $4.resContents,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
;
|
;
|
||||||
@@ -675,17 +676,27 @@ resource_body:
|
|||||||
/* end of list */
|
/* end of list */
|
||||||
{
|
{
|
||||||
posLast(yylex, yyDollar) // our pos
|
posLast(yylex, yyDollar) // our pos
|
||||||
$$.resFields = []*StmtResField{}
|
$$.resContents = []StmtResContents{}
|
||||||
}
|
}
|
||||||
| resource_body resource_field
|
| resource_body resource_field
|
||||||
{
|
{
|
||||||
posLast(yylex, yyDollar) // our pos
|
posLast(yylex, yyDollar) // our pos
|
||||||
$$.resFields = append($1.resFields, $2.resField)
|
$$.resContents = append($1.resContents, $2.resField)
|
||||||
}
|
}
|
||||||
| resource_body conditional_resource_field
|
| resource_body conditional_resource_field
|
||||||
{
|
{
|
||||||
posLast(yylex, yyDollar) // our pos
|
posLast(yylex, yyDollar) // our pos
|
||||||
$$.resFields = append($1.resFields, $2.resField)
|
$$.resContents = append($1.resContents, $2.resField)
|
||||||
|
}
|
||||||
|
| resource_body resource_edge
|
||||||
|
{
|
||||||
|
posLast(yylex, yyDollar) // our pos
|
||||||
|
$$.resContents = append($1.resContents, $2.resEdge)
|
||||||
|
}
|
||||||
|
| resource_body conditional_resource_edge
|
||||||
|
{
|
||||||
|
posLast(yylex, yyDollar) // our pos
|
||||||
|
$$.resContents = append($1.resContents, $2.resEdge)
|
||||||
}
|
}
|
||||||
;
|
;
|
||||||
resource_field:
|
resource_field:
|
||||||
@@ -710,6 +721,29 @@ conditional_resource_field:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
;
|
;
|
||||||
|
resource_edge:
|
||||||
|
// Before => Test["t1"],
|
||||||
|
CAPITALIZED_IDENTIFIER ROCKET edge_half COMMA
|
||||||
|
{
|
||||||
|
posLast(yylex, yyDollar) // our pos
|
||||||
|
$$.resEdge = &StmtResEdge{
|
||||||
|
Property: $1.str,
|
||||||
|
EdgeHalf: $3.edgeHalf,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
;
|
||||||
|
conditional_resource_edge:
|
||||||
|
// Before => $present ?: Test["t1"],
|
||||||
|
CAPITALIZED_IDENTIFIER ROCKET expr ELVIS edge_half COMMA
|
||||||
|
{
|
||||||
|
posLast(yylex, yyDollar) // our pos
|
||||||
|
$$.resEdge = &StmtResEdge{
|
||||||
|
Property: $1.str,
|
||||||
|
EdgeHalf: $5.edgeHalf,
|
||||||
|
Condition: $3.expr,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
;
|
||||||
edge:
|
edge:
|
||||||
// TODO: we could technically prevent single edge_half pieces from being
|
// TODO: we could technically prevent single edge_half pieces from being
|
||||||
// parsed, but it's probably more work than is necessary...
|
// parsed, but it's probably more work than is necessary...
|
||||||
|
|||||||
593
lang/structs.go
593
lang/structs.go
@@ -35,6 +35,24 @@ import (
|
|||||||
errwrap "github.com/pkg/errors"
|
errwrap "github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// EdgeNotify declares an edge a -> b, such that a notification occurs.
|
||||||
|
// This is most similar to "notify" in Puppet.
|
||||||
|
EdgeNotify = "notify"
|
||||||
|
|
||||||
|
// EdgeBefore declares an edge a -> b, such that no notification occurs.
|
||||||
|
// This is most similar to "before" in Puppet.
|
||||||
|
EdgeBefore = "before"
|
||||||
|
|
||||||
|
// EdgeListen declares an edge a <- b, such that a notification occurs.
|
||||||
|
// This is most similar to "subscribe" in Puppet.
|
||||||
|
EdgeListen = "listen"
|
||||||
|
|
||||||
|
// EdgeDepend declares an edge a <- b, such that no notification occurs.
|
||||||
|
// This is most similar to "require" in Puppet.
|
||||||
|
EdgeDepend = "depend"
|
||||||
|
)
|
||||||
|
|
||||||
// StmtBind is a representation of an assignment, which binds a variable to an
|
// StmtBind is a representation of an assignment, which binds a variable to an
|
||||||
// expression.
|
// expression.
|
||||||
type StmtBind struct {
|
type StmtBind struct {
|
||||||
@@ -96,48 +114,38 @@ func (obj *StmtBind) Output() (*interfaces.Output, error) {
|
|||||||
return (&interfaces.Output{}).Empty(), nil
|
return (&interfaces.Output{}).Empty(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// StmtRes is a representation of a resource.
|
// StmtRes is a representation of a resource and possibly some edges.
|
||||||
|
// TODO: consider expanding Name (if it's a list) to have this return a list of
|
||||||
|
// Res's in the Output function. Alternatively, it could be a map[name]struct{},
|
||||||
|
// or even a map[[]name]struct{}.
|
||||||
type StmtRes struct {
|
type StmtRes struct {
|
||||||
Kind string // kind of resource, eg: pkg, file, svc, etc...
|
Kind string // kind of resource, eg: pkg, file, svc, etc...
|
||||||
Name interfaces.Expr // unique name for the res of this kind
|
Name interfaces.Expr // unique name for the res of this kind
|
||||||
Fields []*StmtResField // list of fields in parsed order
|
Contents []StmtResContents // list of fields/edges in parsed order
|
||||||
}
|
}
|
||||||
|
|
||||||
// Interpolate returns a new node (or itself) once it has been expanded. 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
|
// 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.
|
// 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) {
|
func (obj *StmtRes) Interpolate() (interfaces.Stmt, error) {
|
||||||
name, err := obj.Name.Interpolate()
|
name, err := obj.Name.Interpolate()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
fields := []*StmtResField{}
|
contents := []StmtResContents{}
|
||||||
for _, x := range obj.Fields {
|
for _, x := range obj.Contents { // make sure we preserve ordering...
|
||||||
interpolated, err := x.Value.Interpolate()
|
interpolated, err := x.Interpolate()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
var condition interfaces.Expr
|
contents = append(contents, interpolated)
|
||||||
if x.Condition != nil {
|
|
||||||
condition, err = x.Condition.Interpolate()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
field := &StmtResField{
|
|
||||||
Field: x.Field,
|
|
||||||
Value: interpolated,
|
|
||||||
Condition: condition,
|
|
||||||
}
|
|
||||||
fields = append(fields, field)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return &StmtRes{
|
return &StmtRes{
|
||||||
Kind: obj.Kind,
|
Kind: obj.Kind,
|
||||||
Name: name,
|
Name: name,
|
||||||
Fields: fields,
|
Contents: contents,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,15 +155,10 @@ func (obj *StmtRes) SetScope(scope *interfaces.Scope) error {
|
|||||||
if err := obj.Name.SetScope(scope); err != nil {
|
if err := obj.Name.SetScope(scope); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for _, x := range obj.Fields {
|
for _, x := range obj.Contents {
|
||||||
if err := x.Value.SetScope(scope); err != nil {
|
if err := x.SetScope(scope); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if x.Condition != nil {
|
|
||||||
if err := x.Condition.SetScope(scope); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -179,54 +182,13 @@ func (obj *StmtRes) Unify() ([]interfaces.Invariant, error) {
|
|||||||
}
|
}
|
||||||
invariants = append(invariants, invar)
|
invariants = append(invariants, invar)
|
||||||
|
|
||||||
// collect all the invariants of each field
|
// collect all the invariants of each field and edge
|
||||||
for _, x := range obj.Fields {
|
for _, x := range obj.Contents {
|
||||||
invars, err := x.Value.Unify()
|
invars, err := x.Unify(obj.Kind) // pass in the resource kind
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
invariants = append(invariants, invars...)
|
invariants = append(invariants, invars...)
|
||||||
|
|
||||||
// conditional expression might have some children invariants to share
|
|
||||||
if x.Condition != nil {
|
|
||||||
condition, err := x.Condition.Unify()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
invariants = append(invariants, condition...)
|
|
||||||
|
|
||||||
// the condition must ultimately be a boolean
|
|
||||||
conditionInvar := &unification.EqualsInvariant{
|
|
||||||
Expr: x.Condition,
|
|
||||||
Type: types.TypeBool,
|
|
||||||
}
|
|
||||||
invariants = append(invariants, conditionInvar)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
return invariants, nil
|
||||||
@@ -253,20 +215,12 @@ func (obj *StmtRes) Graph() (*pgraph.Graph, error) {
|
|||||||
}
|
}
|
||||||
graph.AddGraph(g)
|
graph.AddGraph(g)
|
||||||
|
|
||||||
for _, x := range obj.Fields {
|
for _, x := range obj.Contents {
|
||||||
g, err := x.Value.Graph()
|
g, err := x.Graph()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
graph.AddGraph(g)
|
graph.AddGraph(g)
|
||||||
|
|
||||||
if x.Condition != nil {
|
|
||||||
g, err := x.Condition.Graph()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
graph.AddGraph(g)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return graph, nil
|
return graph, nil
|
||||||
@@ -277,12 +231,13 @@ func (obj *StmtRes) Graph() (*pgraph.Graph, error) {
|
|||||||
// analogous function for expressions is Value. Those Value functions might get
|
// 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
|
// 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.
|
// the case of this resource statement, this is definitely the case.
|
||||||
// XXX: Add MetaParams...
|
// XXX: Add MetaParams as a simple meta field with a struct of the right type...
|
||||||
func (obj *StmtRes) Output() (*interfaces.Output, error) {
|
func (obj *StmtRes) Output() (*interfaces.Output, error) {
|
||||||
nameValue, err := obj.Name.Value()
|
nameValue, err := obj.Name.Value()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
// TODO: test for []str instead, and loop
|
||||||
name := nameValue.Str() // must not panic
|
name := nameValue.Str() // must not panic
|
||||||
|
|
||||||
res, err := resources.NewNamedResource(obj.Kind, name)
|
res, err := resources.NewNamedResource(obj.Kind, name)
|
||||||
@@ -302,7 +257,12 @@ func (obj *StmtRes) Output() (*interfaces.Output, error) {
|
|||||||
ts := reflect.TypeOf(res).Elem() // pointer to struct, then struct
|
ts := reflect.TypeOf(res).Elem() // pointer to struct, then struct
|
||||||
|
|
||||||
// FIXME: we could probably simplify this code...
|
// FIXME: we could probably simplify this code...
|
||||||
for _, x := range obj.Fields {
|
for _, line := range obj.Contents {
|
||||||
|
x, ok := line.(*StmtResField)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
if x.Condition != nil {
|
if x.Condition != nil {
|
||||||
b, err := x.Condition.Value()
|
b, err := x.Condition.Value()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -408,13 +368,141 @@ func (obj *StmtRes) Output() (*interfaces.Output, error) {
|
|||||||
value.Elem().Set(valof)
|
value.Elem().Set(valof)
|
||||||
}
|
}
|
||||||
f.Set(value) // set it !
|
f.Set(value) // set it !
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
edges, err := obj.edges()
|
||||||
|
if err != nil {
|
||||||
|
return nil, errwrap.Wrapf(err, "error building edges")
|
||||||
}
|
}
|
||||||
|
|
||||||
return &interfaces.Output{
|
return &interfaces.Output{
|
||||||
Resources: []resources.Res{res},
|
Resources: []resources.Res{res},
|
||||||
|
Edges: edges,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// edges is a helper function to generate the edges that come from the resource.
|
||||||
|
func (obj *StmtRes) edges() ([]*interfaces.Edge, error) {
|
||||||
|
edges := []*interfaces.Edge{}
|
||||||
|
|
||||||
|
// to and from self, map of kind, name, notify
|
||||||
|
var to = make(map[string]map[string]bool) // to this from self
|
||||||
|
var from = make(map[string]map[string]bool) // from this to self
|
||||||
|
|
||||||
|
for _, line := range obj.Contents {
|
||||||
|
x, ok := line.(*StmtResEdge)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if x.Condition != nil {
|
||||||
|
b, err := x.Condition.Value()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !b.Bool() { // if value exists, and is false, skip it
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
v, err := x.EdgeHalf.Name.Value()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
name := v.Str() // must not panic
|
||||||
|
kind := x.EdgeHalf.Kind
|
||||||
|
var notify bool
|
||||||
|
|
||||||
|
switch p := x.Property; p {
|
||||||
|
// a -> b
|
||||||
|
// a notify b
|
||||||
|
// a before b
|
||||||
|
case EdgeNotify:
|
||||||
|
notify = true
|
||||||
|
fallthrough
|
||||||
|
case EdgeBefore:
|
||||||
|
if m, exists := to[kind]; !exists {
|
||||||
|
to[kind] = make(map[string]bool)
|
||||||
|
} else if n, exists := m[name]; exists {
|
||||||
|
notify = notify || n // collate
|
||||||
|
}
|
||||||
|
to[kind][name] = notify // to this from self
|
||||||
|
|
||||||
|
// b -> a
|
||||||
|
// b listen a
|
||||||
|
// b depend a
|
||||||
|
case EdgeListen:
|
||||||
|
notify = true
|
||||||
|
fallthrough
|
||||||
|
case EdgeDepend:
|
||||||
|
if m, exists := from[kind]; !exists {
|
||||||
|
from[kind] = make(map[string]bool)
|
||||||
|
} else if n, exists := m[name]; exists {
|
||||||
|
notify = notify || n // collate
|
||||||
|
}
|
||||||
|
from[kind][name] = notify // from this to self
|
||||||
|
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unknown property: %s", p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: we could detect simple loops here (if `from` and `to` have the
|
||||||
|
// same entry) but we can leave this to the proper dag checker later on
|
||||||
|
|
||||||
|
v, err := obj.Name.Value()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
self := v.Str() // must not panic
|
||||||
|
|
||||||
|
for kind, x := range to { // to this from self
|
||||||
|
for name, notify := range x {
|
||||||
|
edge := &interfaces.Edge{
|
||||||
|
Kind1: obj.Kind,
|
||||||
|
Name1: self,
|
||||||
|
//Send: "",
|
||||||
|
|
||||||
|
Kind2: kind,
|
||||||
|
Name2: name,
|
||||||
|
//Recv: "",
|
||||||
|
|
||||||
|
Notify: notify,
|
||||||
|
}
|
||||||
|
edges = append(edges, edge)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for kind, x := range from { // from this to self
|
||||||
|
for name, notify := range x {
|
||||||
|
edge := &interfaces.Edge{
|
||||||
|
Kind1: kind,
|
||||||
|
Name1: name,
|
||||||
|
//Send: "",
|
||||||
|
|
||||||
|
Kind2: obj.Kind,
|
||||||
|
Name2: self,
|
||||||
|
//Recv: "",
|
||||||
|
|
||||||
|
Notify: notify,
|
||||||
|
}
|
||||||
|
edges = append(edges, edge)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return edges, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// StmtResContents is the interface that is met by the resource contents. Look
|
||||||
|
// closely for while it is similar to the Stmt interface, it is quite different.
|
||||||
|
type StmtResContents interface {
|
||||||
|
Interpolate() (StmtResContents, error) // different!
|
||||||
|
SetScope(*interfaces.Scope) error
|
||||||
|
Unify(kind string) ([]interfaces.Invariant, error) // different!
|
||||||
|
Graph() (*pgraph.Graph, error)
|
||||||
|
}
|
||||||
|
|
||||||
// StmtResField represents a single field in the parsed resource representation.
|
// StmtResField represents a single field in the parsed resource representation.
|
||||||
// This does not satisfy the Stmt interface.
|
// This does not satisfy the Stmt interface.
|
||||||
type StmtResField struct {
|
type StmtResField struct {
|
||||||
@@ -423,6 +511,243 @@ type StmtResField struct {
|
|||||||
Condition interfaces.Expr // the value will be used if nil or true
|
Condition interfaces.Expr // the value will be used if nil or true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
// This interpolate is different It is different from the interpolate found in
|
||||||
|
// the Expr and Stmt interfaces because it returns a different type as output.
|
||||||
|
func (obj *StmtResField) Interpolate() (StmtResContents, error) {
|
||||||
|
interpolated, err := obj.Value.Interpolate()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var condition interfaces.Expr
|
||||||
|
if obj.Condition != nil {
|
||||||
|
condition, err = obj.Condition.Interpolate()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &StmtResField{
|
||||||
|
Field: obj.Field,
|
||||||
|
Value: interpolated,
|
||||||
|
Condition: condition,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetScope stores the scope for later use in this resource and it's children,
|
||||||
|
// which it propagates this downwards to.
|
||||||
|
func (obj *StmtResField) SetScope(scope *interfaces.Scope) error {
|
||||||
|
if err := obj.Value.SetScope(scope); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if obj.Condition != nil {
|
||||||
|
if err := obj.Condition.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. It is different from the Unify found in the Expr
|
||||||
|
// and Stmt interfaces because it adds an input parameter.
|
||||||
|
func (obj *StmtResField) Unify(kind string) ([]interfaces.Invariant, error) {
|
||||||
|
var invariants []interfaces.Invariant
|
||||||
|
|
||||||
|
invars, err := obj.Value.Unify()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
invariants = append(invariants, invars...)
|
||||||
|
|
||||||
|
// conditional expression might have some children invariants to share
|
||||||
|
if obj.Condition != nil {
|
||||||
|
condition, err := obj.Condition.Unify()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
invariants = append(invariants, condition...)
|
||||||
|
|
||||||
|
// the condition must ultimately be a boolean
|
||||||
|
conditionInvar := &unification.EqualsInvariant{
|
||||||
|
Expr: obj.Condition,
|
||||||
|
Type: types.TypeBool,
|
||||||
|
}
|
||||||
|
invariants = append(invariants, conditionInvar)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: unfortunately this gets called separately for each field... if
|
||||||
|
// we could cache this, it might be worth looking into for performance!
|
||||||
|
typMap, err := resources.LangFieldNameToStructType(kind)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
field := strings.TrimSpace(obj.Field)
|
||||||
|
if len(field) != len(obj.Field) {
|
||||||
|
return nil, fmt.Errorf("field was wrapped in whitespace")
|
||||||
|
}
|
||||||
|
if len(strings.Fields(field)) != 1 {
|
||||||
|
return nil, fmt.Errorf("field was empty or contained spaces")
|
||||||
|
}
|
||||||
|
|
||||||
|
typ, exists := typMap[obj.Field]
|
||||||
|
if !exists {
|
||||||
|
return nil, fmt.Errorf("could not determine type for `%s` field of `%s`", obj.Field, kind)
|
||||||
|
}
|
||||||
|
invar := &unification.EqualsInvariant{
|
||||||
|
Expr: obj.Value,
|
||||||
|
Type: typ,
|
||||||
|
}
|
||||||
|
invariants = append(invariants, invar)
|
||||||
|
|
||||||
|
return invariants, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Graph returns the reactive function graph which is expressed by this node. It
|
||||||
|
// includes any vertices produced by this node, and the appropriate edges to any
|
||||||
|
// vertices that are produced by its children. Nodes which fulfill the Expr
|
||||||
|
// interface directly produce vertices (and possible children) where as nodes
|
||||||
|
// that fulfill the Stmt interface do not produces vertices, where as their
|
||||||
|
// children might. It is interesting to note that nothing directly adds an edge
|
||||||
|
// to the resources created, but rather, once all the values (expressions) with
|
||||||
|
// no outgoing edges have produced at least a single value, then the resources
|
||||||
|
// know they're able to be built.
|
||||||
|
func (obj *StmtResField) Graph() (*pgraph.Graph, error) {
|
||||||
|
graph, err := pgraph.NewGraph("resfield")
|
||||||
|
if err != nil {
|
||||||
|
return nil, errwrap.Wrapf(err, "could not create graph")
|
||||||
|
}
|
||||||
|
|
||||||
|
g, err := obj.Value.Graph()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
graph.AddGraph(g)
|
||||||
|
|
||||||
|
if obj.Condition != nil {
|
||||||
|
g, err := obj.Condition.Graph()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
graph.AddGraph(g)
|
||||||
|
}
|
||||||
|
|
||||||
|
return graph, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// StmtResEdge represents a single edge property in the parsed resource
|
||||||
|
// representation. This does not satisfy the Stmt interface.
|
||||||
|
type StmtResEdge struct {
|
||||||
|
Property string // TODO: iota constant instead?
|
||||||
|
EdgeHalf *StmtEdgeHalf
|
||||||
|
Condition interfaces.Expr // the value will be used if nil or true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
// This interpolate is different It is different from the interpolate found in
|
||||||
|
// the Expr and Stmt interfaces because it returns a different type as output.
|
||||||
|
func (obj *StmtResEdge) Interpolate() (StmtResContents, error) {
|
||||||
|
interpolated, err := obj.EdgeHalf.Interpolate()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var condition interfaces.Expr
|
||||||
|
if obj.Condition != nil {
|
||||||
|
condition, err = obj.Condition.Interpolate()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &StmtResEdge{
|
||||||
|
Property: obj.Property,
|
||||||
|
EdgeHalf: interpolated,
|
||||||
|
Condition: condition,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetScope stores the scope for later use in this resource and it's children,
|
||||||
|
// which it propagates this downwards to.
|
||||||
|
func (obj *StmtResEdge) SetScope(scope *interfaces.Scope) error {
|
||||||
|
if err := obj.EdgeHalf.SetScope(scope); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if obj.Condition != nil {
|
||||||
|
if err := obj.Condition.SetScope(scope); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unify returns the list of invariants that this node produces. It recursively
|
||||||
|
// calls Unify on any children elements that exist in the AST, and returns the
|
||||||
|
// collection to the caller. It is different from the Unify found in the Expr
|
||||||
|
// and Stmt interfaces because it adds an input parameter.
|
||||||
|
func (obj *StmtResEdge) Unify(kind string) ([]interfaces.Invariant, error) {
|
||||||
|
var invariants []interfaces.Invariant
|
||||||
|
|
||||||
|
invars, err := obj.EdgeHalf.Unify()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
invariants = append(invariants, invars...)
|
||||||
|
|
||||||
|
// conditional expression might have some children invariants to share
|
||||||
|
if obj.Condition != nil {
|
||||||
|
condition, err := obj.Condition.Unify()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
invariants = append(invariants, condition...)
|
||||||
|
|
||||||
|
// the condition must ultimately be a boolean
|
||||||
|
conditionInvar := &unification.EqualsInvariant{
|
||||||
|
Expr: obj.Condition,
|
||||||
|
Type: types.TypeBool,
|
||||||
|
}
|
||||||
|
invariants = append(invariants, conditionInvar)
|
||||||
|
}
|
||||||
|
|
||||||
|
return invariants, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Graph returns the reactive function graph which is expressed by this node. It
|
||||||
|
// includes any vertices produced by this node, and the appropriate edges to any
|
||||||
|
// vertices that are produced by its children. Nodes which fulfill the Expr
|
||||||
|
// interface directly produce vertices (and possible children) where as nodes
|
||||||
|
// that fulfill the Stmt interface do not produces vertices, where as their
|
||||||
|
// children might. It is interesting to note that nothing directly adds an edge
|
||||||
|
// to the resources created, but rather, once all the values (expressions) with
|
||||||
|
// no outgoing edges have produced at least a single value, then the resources
|
||||||
|
// know they're able to be built.
|
||||||
|
func (obj *StmtResEdge) Graph() (*pgraph.Graph, error) {
|
||||||
|
graph, err := pgraph.NewGraph("resedge")
|
||||||
|
if err != nil {
|
||||||
|
return nil, errwrap.Wrapf(err, "could not create graph")
|
||||||
|
}
|
||||||
|
|
||||||
|
g, err := obj.EdgeHalf.Graph()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
graph.AddGraph(g)
|
||||||
|
|
||||||
|
if obj.Condition != nil {
|
||||||
|
g, err := obj.Condition.Graph()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
graph.AddGraph(g)
|
||||||
|
}
|
||||||
|
|
||||||
|
return graph, nil
|
||||||
|
}
|
||||||
|
|
||||||
// StmtEdge is a representation of a dependency. It also supports send/recv.
|
// StmtEdge is a representation of a dependency. It also supports send/recv.
|
||||||
// Edges represents that the first resource (Kind/Name) listed in the
|
// Edges represents that the first resource (Kind/Name) listed in the
|
||||||
// EdgeHalfList should happen in the resource graph *before* the next resource
|
// EdgeHalfList should happen in the resource graph *before* the next resource
|
||||||
@@ -448,16 +773,10 @@ type StmtEdge struct {
|
|||||||
func (obj *StmtEdge) Interpolate() (interfaces.Stmt, error) {
|
func (obj *StmtEdge) Interpolate() (interfaces.Stmt, error) {
|
||||||
edgeHalfList := []*StmtEdgeHalf{}
|
edgeHalfList := []*StmtEdgeHalf{}
|
||||||
for _, x := range obj.EdgeHalfList {
|
for _, x := range obj.EdgeHalfList {
|
||||||
name, err := x.Name.Interpolate()
|
edgeHalf, err := x.Interpolate()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
edgeHalf := &StmtEdgeHalf{
|
|
||||||
Kind: x.Kind,
|
|
||||||
Name: name,
|
|
||||||
SendRecv: x.SendRecv,
|
|
||||||
}
|
|
||||||
edgeHalfList = append(edgeHalfList, edgeHalf)
|
edgeHalfList = append(edgeHalfList, edgeHalf)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -471,7 +790,7 @@ func (obj *StmtEdge) Interpolate() (interfaces.Stmt, error) {
|
|||||||
// which it propagates this downwards to.
|
// which it propagates this downwards to.
|
||||||
func (obj *StmtEdge) SetScope(scope *interfaces.Scope) error {
|
func (obj *StmtEdge) SetScope(scope *interfaces.Scope) error {
|
||||||
for _, x := range obj.EdgeHalfList {
|
for _, x := range obj.EdgeHalfList {
|
||||||
if err := x.Name.SetScope(scope); err != nil {
|
if err := x.SetScope(scope); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -499,26 +818,15 @@ func (obj *StmtEdge) Unify() ([]interfaces.Invariant, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, x := range obj.EdgeHalfList {
|
for _, x := range obj.EdgeHalfList {
|
||||||
if x.Kind == "" {
|
|
||||||
return nil, fmt.Errorf("missing resource kind in edge")
|
|
||||||
}
|
|
||||||
|
|
||||||
if x.SendRecv != "" && len(obj.EdgeHalfList) != 2 {
|
if x.SendRecv != "" && len(obj.EdgeHalfList) != 2 {
|
||||||
return nil, fmt.Errorf("send/recv edges must come in pairs")
|
return nil, fmt.Errorf("send/recv edges must come in pairs")
|
||||||
}
|
}
|
||||||
|
|
||||||
invars, err := x.Name.Unify()
|
invars, err := x.Unify()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
invariants = append(invariants, invars...)
|
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
|
return invariants, nil
|
||||||
@@ -540,7 +848,7 @@ func (obj *StmtEdge) Graph() (*pgraph.Graph, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, x := range obj.EdgeHalfList {
|
for _, x := range obj.EdgeHalfList {
|
||||||
g, err := x.Name.Graph()
|
g, err := x.Graph()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -599,6 +907,81 @@ type StmtEdgeHalf struct {
|
|||||||
SendRecv string // name of field to send/recv from, empty to ignore
|
SendRecv string // name of field to send/recv from, empty to ignore
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
// This interpolate is different It is different from the interpolate found in
|
||||||
|
// the Expr and Stmt interfaces because it returns a different type as output.
|
||||||
|
func (obj *StmtEdgeHalf) Interpolate() (*StmtEdgeHalf, error) {
|
||||||
|
name, err := obj.Name.Interpolate()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
edgeHalf := &StmtEdgeHalf{
|
||||||
|
Kind: obj.Kind,
|
||||||
|
Name: name,
|
||||||
|
SendRecv: obj.SendRecv,
|
||||||
|
}
|
||||||
|
return edgeHalf, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetScope stores the scope for later use in this resource and it's children,
|
||||||
|
// which it propagates this downwards to.
|
||||||
|
func (obj *StmtEdgeHalf) SetScope(scope *interfaces.Scope) error {
|
||||||
|
return obj.Name.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 *StmtEdgeHalf) Unify() ([]interfaces.Invariant, error) {
|
||||||
|
var invariants []interfaces.Invariant
|
||||||
|
|
||||||
|
if obj.Kind == "" {
|
||||||
|
return nil, fmt.Errorf("missing resource kind in edge")
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
return invariants, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Graph returns the reactive function graph which is expressed by this node. It
|
||||||
|
// includes any vertices produced by this node, and the appropriate edges to any
|
||||||
|
// vertices that are produced by its children. Nodes which fulfill the Expr
|
||||||
|
// interface directly produce vertices (and possible children) where as nodes
|
||||||
|
// that fulfill the Stmt interface do not produces vertices, where as their
|
||||||
|
// children might. It is interesting to note that nothing directly adds an edge
|
||||||
|
// to the resources created, but rather, once all the values (expressions) with
|
||||||
|
// no outgoing edges have produced at least a single value, then the resources
|
||||||
|
// know they're able to be built.
|
||||||
|
func (obj *StmtEdgeHalf) Graph() (*pgraph.Graph, error) {
|
||||||
|
graph, err := pgraph.NewGraph("edgehalf")
|
||||||
|
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)
|
||||||
|
|
||||||
|
return graph, nil
|
||||||
|
}
|
||||||
|
|
||||||
// StmtIf represents an if condition that contains between one and two branches
|
// 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
|
// 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
|
// over time. In particular, this is different from an ExprIf which returns a
|
||||||
|
|||||||
@@ -51,8 +51,8 @@ func TestUnification1(t *testing.T) {
|
|||||||
&StmtRes{
|
&StmtRes{
|
||||||
Kind: "test",
|
Kind: "test",
|
||||||
Name: &ExprStr{V: "t1"},
|
Name: &ExprStr{V: "t1"},
|
||||||
Fields: []*StmtResField{
|
Contents: []StmtResContents{
|
||||||
{
|
&StmtResField{
|
||||||
Field: "str",
|
Field: "str",
|
||||||
Value: expr,
|
Value: expr,
|
||||||
},
|
},
|
||||||
@@ -85,8 +85,8 @@ func TestUnification1(t *testing.T) {
|
|||||||
&StmtRes{
|
&StmtRes{
|
||||||
Kind: "test",
|
Kind: "test",
|
||||||
Name: &ExprStr{V: "test"},
|
Name: &ExprStr{V: "test"},
|
||||||
Fields: []*StmtResField{
|
Contents: []StmtResContents{
|
||||||
{
|
&StmtResField{
|
||||||
Field: "slicestring",
|
Field: "slicestring",
|
||||||
Value: expr,
|
Value: expr,
|
||||||
},
|
},
|
||||||
@@ -125,8 +125,8 @@ func TestUnification1(t *testing.T) {
|
|||||||
&StmtRes{
|
&StmtRes{
|
||||||
Kind: "test",
|
Kind: "test",
|
||||||
Name: &ExprStr{V: "test"},
|
Name: &ExprStr{V: "test"},
|
||||||
Fields: []*StmtResField{
|
Contents: []StmtResContents{
|
||||||
{
|
&StmtResField{
|
||||||
Field: "mapintfloat",
|
Field: "mapintfloat",
|
||||||
Value: expr,
|
Value: expr,
|
||||||
},
|
},
|
||||||
@@ -167,8 +167,8 @@ func TestUnification1(t *testing.T) {
|
|||||||
&StmtRes{
|
&StmtRes{
|
||||||
Kind: "test",
|
Kind: "test",
|
||||||
Name: &ExprStr{V: "test"},
|
Name: &ExprStr{V: "test"},
|
||||||
Fields: []*StmtResField{
|
Contents: []StmtResContents{
|
||||||
{
|
&StmtResField{
|
||||||
Field: "mixedstruct",
|
Field: "mixedstruct",
|
||||||
Value: expr,
|
Value: expr,
|
||||||
},
|
},
|
||||||
@@ -215,8 +215,8 @@ func TestUnification1(t *testing.T) {
|
|||||||
Name: &ExprStr{
|
Name: &ExprStr{
|
||||||
V: "n1",
|
V: "n1",
|
||||||
},
|
},
|
||||||
Fields: []*StmtResField{
|
Contents: []StmtResContents{
|
||||||
{
|
&StmtResField{
|
||||||
Field: "int64ptr",
|
Field: "int64ptr",
|
||||||
Value: expr, // func
|
Value: expr, // func
|
||||||
},
|
},
|
||||||
@@ -270,8 +270,8 @@ func TestUnification1(t *testing.T) {
|
|||||||
Name: &ExprStr{
|
Name: &ExprStr{
|
||||||
V: "n1",
|
V: "n1",
|
||||||
},
|
},
|
||||||
Fields: []*StmtResField{
|
Contents: []StmtResContents{
|
||||||
{
|
&StmtResField{
|
||||||
Field: "int64ptr",
|
Field: "int64ptr",
|
||||||
Value: expr,
|
Value: expr,
|
||||||
},
|
},
|
||||||
@@ -326,8 +326,8 @@ func TestUnification1(t *testing.T) {
|
|||||||
Name: &ExprStr{
|
Name: &ExprStr{
|
||||||
V: "n1",
|
V: "n1",
|
||||||
},
|
},
|
||||||
Fields: []*StmtResField{
|
Contents: []StmtResContents{
|
||||||
{
|
&StmtResField{
|
||||||
Field: "float32",
|
Field: "float32",
|
||||||
Value: expr,
|
Value: expr,
|
||||||
},
|
},
|
||||||
@@ -458,8 +458,8 @@ func TestUnification1(t *testing.T) {
|
|||||||
&StmtRes{
|
&StmtRes{
|
||||||
Kind: "test",
|
Kind: "test",
|
||||||
Name: &ExprStr{V: "t1"},
|
Name: &ExprStr{V: "t1"},
|
||||||
Fields: []*StmtResField{
|
Contents: []StmtResContents{
|
||||||
{
|
&StmtResField{
|
||||||
Field: "stringptr",
|
Field: "stringptr",
|
||||||
Value: expr,
|
Value: expr,
|
||||||
},
|
},
|
||||||
@@ -493,8 +493,8 @@ func TestUnification1(t *testing.T) {
|
|||||||
&StmtRes{
|
&StmtRes{
|
||||||
Kind: "test",
|
Kind: "test",
|
||||||
Name: &ExprStr{V: "t1"},
|
Name: &ExprStr{V: "t1"},
|
||||||
Fields: []*StmtResField{
|
Contents: []StmtResContents{
|
||||||
{
|
&StmtResField{
|
||||||
Field: "stringptr",
|
Field: "stringptr",
|
||||||
Value: expr,
|
Value: expr,
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user