lang: Add meta parameter parsing to resources
Now we can actually specify metaparameters in the resources!
This commit is contained in:
@@ -214,6 +214,50 @@ 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
|
||||
not specified, then it will obviously not be used.
|
||||
|
||||
Resources may specify meta parameters. To do so, you must add them as you would
|
||||
a regular parameter, except that they start with `Meta` and are capitalized. Eg:
|
||||
|
||||
```mcl
|
||||
file "/tmp/f1" {
|
||||
content => "hello!\n",
|
||||
|
||||
Meta:noop => true,
|
||||
Meta:delay => $b ?: 42,
|
||||
}
|
||||
```
|
||||
|
||||
As you can see, they also support the elvis operator, and you can add as many as
|
||||
you like. While it is not recommended to add the same meta parameter more than
|
||||
once, it does not currently cause an error, and even though the result of doing
|
||||
so is officially undefined, it will currently take the last specified value.
|
||||
|
||||
You may also specify a single meta parameter struct. This is useful if you'd
|
||||
like to reuse a value, or build a combined value programmatically. For example:
|
||||
|
||||
```mcl
|
||||
file "/tmp/f1" {
|
||||
content => "hello!\n",
|
||||
|
||||
Meta => $b ?: struct{
|
||||
noop => false,
|
||||
retry => -1,
|
||||
delay => 0,
|
||||
poll => 5,
|
||||
limit => 4.2,
|
||||
burst => 3,
|
||||
sema => ["foo:1", "bar:3",],
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Remember that the top-level `Meta` field supports the elvis operator, while the
|
||||
individual struct fields in the struct type do not. This is to be expected, but
|
||||
since they are syntactically similar, it is worth mentioning to avoid confusion.
|
||||
|
||||
Please note that at the moment, you must specify a full metaparams struct, since
|
||||
partial struct types are currently not supported in the language. Patches are
|
||||
welcome if you'd like to add this tricky feature!
|
||||
|
||||
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
|
||||
|
||||
@@ -963,6 +963,44 @@ func TestAstInterpret0(t *testing.T) {
|
||||
graph: graph,
|
||||
})
|
||||
}
|
||||
{
|
||||
graph, _ := pgraph.NewGraph("g")
|
||||
t1, _ := engine.NewNamedResource("test", "t1")
|
||||
x := t1.(*resources.TestRes)
|
||||
stringptr := "this is meta"
|
||||
x.StringPtr = &stringptr
|
||||
m := &engine.MetaParams{
|
||||
Noop: true, // overwritten
|
||||
Retry: -1,
|
||||
Delay: 0,
|
||||
Poll: 5,
|
||||
Limit: 4.2,
|
||||
Burst: 3,
|
||||
Sema: []string{"foo:1", "bar:3"},
|
||||
}
|
||||
x.SetMetaParams(m)
|
||||
graph.AddVertex(t1)
|
||||
testCases = append(testCases, test{
|
||||
name: "resource with meta params",
|
||||
code: `
|
||||
test "t1" {
|
||||
stringptr => "this is meta",
|
||||
|
||||
Meta => struct{
|
||||
noop => false,
|
||||
retry => -1,
|
||||
delay => 0,
|
||||
poll => 5,
|
||||
limit => 4.2,
|
||||
burst => 3,
|
||||
sema => ["foo:1", "bar:3",],
|
||||
},
|
||||
Meta:noop => true,
|
||||
}
|
||||
`,
|
||||
graph: graph,
|
||||
})
|
||||
}
|
||||
|
||||
names := []string{}
|
||||
for index, tc := range testCases { // run all the tests
|
||||
|
||||
31
lang/interpret_test/TestAstFunc1/metaparams0.graph
Normal file
31
lang/interpret_test/TestAstFunc1/metaparams0.graph
Normal file
@@ -0,0 +1,31 @@
|
||||
Edge: bool(false) -> struct(noop: bool(false); retry: int(-1); delay: int(0); poll: int(5); limit: float(4.2); burst: int(3); sema: list(str(foo:1), str(bar:3))) # noop
|
||||
Edge: bool(true) -> var(b) # b
|
||||
Edge: bool(true) -> var(b) # b
|
||||
Edge: float(4.2) -> struct(noop: bool(false); retry: int(-1); delay: int(0); poll: int(5); limit: float(4.2); burst: int(3); sema: list(str(foo:1), str(bar:3))) # limit
|
||||
Edge: int(-1) -> struct(noop: bool(false); retry: int(-1); delay: int(0); poll: int(5); limit: float(4.2); burst: int(3); sema: list(str(foo:1), str(bar:3))) # retry
|
||||
Edge: int(0) -> struct(noop: bool(false); retry: int(-1); delay: int(0); poll: int(5); limit: float(4.2); burst: int(3); sema: list(str(foo:1), str(bar:3))) # delay
|
||||
Edge: int(3) -> struct(noop: bool(false); retry: int(-1); delay: int(0); poll: int(5); limit: float(4.2); burst: int(3); sema: list(str(foo:1), str(bar:3))) # burst
|
||||
Edge: int(5) -> struct(noop: bool(false); retry: int(-1); delay: int(0); poll: int(5); limit: float(4.2); burst: int(3); sema: list(str(foo:1), str(bar:3))) # poll
|
||||
Edge: list(str(foo:1), str(bar:3)) -> struct(noop: bool(false); retry: int(-1); delay: int(0); poll: int(5); limit: float(4.2); burst: int(3); sema: list(str(foo:1), str(bar:3))) # sema
|
||||
Edge: str(bar:3) -> list(str(foo:1), str(bar:3)) # 1
|
||||
Edge: str(foo:1) -> list(str(foo:1), str(bar:3)) # 0
|
||||
Edge: str(hello world) -> call:fmt.printf(str(hello world)) # a
|
||||
Vertex: bool(false)
|
||||
Vertex: bool(false)
|
||||
Vertex: bool(true)
|
||||
Vertex: bool(true)
|
||||
Vertex: call:fmt.printf(str(hello world))
|
||||
Vertex: float(4.2)
|
||||
Vertex: int(-1)
|
||||
Vertex: int(0)
|
||||
Vertex: int(3)
|
||||
Vertex: int(42)
|
||||
Vertex: int(5)
|
||||
Vertex: list(str(foo:1), str(bar:3))
|
||||
Vertex: str(bar:3)
|
||||
Vertex: str(foo:1)
|
||||
Vertex: str(greeting)
|
||||
Vertex: str(hello world)
|
||||
Vertex: struct(noop: bool(false); retry: int(-1); delay: int(0); poll: int(5); limit: float(4.2); burst: int(3); sema: list(str(foo:1), str(bar:3)))
|
||||
Vertex: var(b)
|
||||
Vertex: var(b)
|
||||
20
lang/interpret_test/TestAstFunc1/metaparams0/main.mcl
Normal file
20
lang/interpret_test/TestAstFunc1/metaparams0/main.mcl
Normal file
@@ -0,0 +1,20 @@
|
||||
import "fmt"
|
||||
|
||||
$b = true
|
||||
|
||||
test "greeting" {
|
||||
anotherstr => fmt.printf("hello world"),
|
||||
|
||||
Meta => $b ?: struct{
|
||||
noop => false,
|
||||
retry => -1,
|
||||
delay => 0,
|
||||
poll => 5,
|
||||
limit => 4.2,
|
||||
burst => 3,
|
||||
sema => ["foo:1", "bar:3",],
|
||||
},
|
||||
Meta:noop => false,
|
||||
Meta:noop => true, # duplicates allowed atm, but not recommended!
|
||||
Meta:poll => $b ?: 42,
|
||||
}
|
||||
@@ -75,6 +75,11 @@ func vertexCmpFn(v1, v2 pgraph.Vertex) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
m1, m2 := r1.MetaParams(), r2.MetaParams()
|
||||
if err := m1.Cmp(m2); err != nil {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return v1.String() == v2.String(), nil
|
||||
}
|
||||
|
||||
|
||||
@@ -52,6 +52,7 @@ const (
|
||||
ErrLexerFloatOverflow = interfaces.Error("float: overflow")
|
||||
ErrParseError = interfaces.Error("parser")
|
||||
ErrParseSetType = interfaces.Error("can't set return type in parser")
|
||||
ErrParseResFieldInvalid = interfaces.Error("can't use unknown resource field")
|
||||
ErrParseAdditionalEquals = interfaces.Error(errstrParseAdditionalEquals)
|
||||
ErrParseExpectingComma = interfaces.Error(errstrParseExpectingComma)
|
||||
)
|
||||
|
||||
@@ -1078,6 +1078,109 @@ func TestLexParse0(t *testing.T) {
|
||||
exp: exp,
|
||||
})
|
||||
}
|
||||
{
|
||||
exp := &StmtProg{
|
||||
Prog: []interfaces.Stmt{
|
||||
&StmtRes{
|
||||
Kind: "test",
|
||||
Name: &ExprStr{
|
||||
V: "t1",
|
||||
},
|
||||
Contents: []StmtResContents{
|
||||
&StmtResMeta{
|
||||
Property: "noop",
|
||||
MetaExpr: &ExprBool{
|
||||
V: true,
|
||||
},
|
||||
},
|
||||
&StmtResMeta{
|
||||
Property: "delay",
|
||||
MetaExpr: &ExprInt{
|
||||
V: 42,
|
||||
},
|
||||
Condition: &ExprBool{
|
||||
V: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
&StmtRes{
|
||||
Kind: "test",
|
||||
Name: &ExprStr{
|
||||
V: "t2",
|
||||
},
|
||||
Contents: []StmtResContents{
|
||||
&StmtResMeta{
|
||||
Property: "limit",
|
||||
MetaExpr: &ExprFloat{
|
||||
V: 0.45,
|
||||
},
|
||||
},
|
||||
&StmtResMeta{
|
||||
Property: "burst",
|
||||
MetaExpr: &ExprInt{
|
||||
V: 4,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
&StmtRes{
|
||||
Kind: "test",
|
||||
Name: &ExprStr{
|
||||
V: "t3",
|
||||
},
|
||||
Contents: []StmtResContents{
|
||||
&StmtResMeta{
|
||||
Property: "noop",
|
||||
MetaExpr: &ExprBool{
|
||||
V: true,
|
||||
},
|
||||
},
|
||||
&StmtResMeta{
|
||||
Property: "meta",
|
||||
MetaExpr: &ExprStruct{
|
||||
Fields: []*ExprStructField{
|
||||
{Name: "poll", Value: &ExprInt{V: 5}},
|
||||
{Name: "retry", Value: &ExprInt{V: 3}},
|
||||
{
|
||||
Name: "sema",
|
||||
Value: &ExprList{
|
||||
Elements: []interfaces.Expr{
|
||||
&ExprStr{V: "foo:1"},
|
||||
&ExprStr{V: "bar:3"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}},
|
||||
}
|
||||
testCases = append(testCases, test{
|
||||
name: "res meta stmt",
|
||||
code: `
|
||||
test "t1" {
|
||||
Meta:noop => true,
|
||||
Meta:delay => true ?: 42,
|
||||
}
|
||||
test "t2" {
|
||||
Meta:limit => 0.45,
|
||||
Meta:burst => 4,
|
||||
}
|
||||
test "t3" {
|
||||
Meta:noop => true, # meta params can be combined
|
||||
Meta => struct{
|
||||
poll => 5,
|
||||
retry => 3,
|
||||
sema => ["foo:1", "bar:3",],
|
||||
},
|
||||
}
|
||||
`,
|
||||
fail: false,
|
||||
exp: exp,
|
||||
})
|
||||
}
|
||||
{
|
||||
testCases = append(testCases, test{
|
||||
name: "parser set type incompatibility str",
|
||||
|
||||
@@ -69,6 +69,7 @@ func init() {
|
||||
resContents []StmtResContents // interface
|
||||
resField *StmtResField
|
||||
resEdge *StmtResEdge
|
||||
resMeta *StmtResMeta
|
||||
|
||||
edgeHalfList []*StmtEdgeHalf
|
||||
edgeHalf *StmtEdgeHalf
|
||||
@@ -945,6 +946,26 @@ resource_body:
|
||||
posLast(yylex, yyDollar) // our pos
|
||||
$$.resContents = append($1.resContents, $2.resEdge)
|
||||
}
|
||||
| resource_body resource_meta
|
||||
{
|
||||
posLast(yylex, yyDollar) // our pos
|
||||
$$.resContents = append($1.resContents, $2.resMeta)
|
||||
}
|
||||
| resource_body conditional_resource_meta
|
||||
{
|
||||
posLast(yylex, yyDollar) // our pos
|
||||
$$.resContents = append($1.resContents, $2.resMeta)
|
||||
}
|
||||
| resource_body resource_meta_struct
|
||||
{
|
||||
posLast(yylex, yyDollar) // our pos
|
||||
$$.resContents = append($1.resContents, $2.resMeta)
|
||||
}
|
||||
| resource_body conditional_resource_meta_struct
|
||||
{
|
||||
posLast(yylex, yyDollar) // our pos
|
||||
$$.resContents = append($1.resContents, $2.resMeta)
|
||||
}
|
||||
;
|
||||
resource_field:
|
||||
IDENTIFIER ROCKET expr COMMA
|
||||
@@ -991,6 +1012,68 @@ conditional_resource_edge:
|
||||
}
|
||||
}
|
||||
;
|
||||
resource_meta:
|
||||
// Meta:noop => true,
|
||||
CAPITALIZED_IDENTIFIER COLON IDENTIFIER ROCKET expr COMMA
|
||||
{
|
||||
posLast(yylex, yyDollar) // our pos
|
||||
if strings.ToLower($1.str) != strings.ToLower(MetaField) {
|
||||
// this will ultimately cause a parser error to occur...
|
||||
yylex.Error(fmt.Sprintf("%s: %s", ErrParseResFieldInvalid, $1.str))
|
||||
}
|
||||
$$.resMeta = &StmtResMeta{
|
||||
Property: $3.str,
|
||||
MetaExpr: $5.expr,
|
||||
}
|
||||
}
|
||||
;
|
||||
conditional_resource_meta:
|
||||
// Meta:limit => $present ?: 4,
|
||||
CAPITALIZED_IDENTIFIER COLON IDENTIFIER ROCKET expr ELVIS expr COMMA
|
||||
{
|
||||
posLast(yylex, yyDollar) // our pos
|
||||
if strings.ToLower($1.str) != strings.ToLower(MetaField) {
|
||||
// this will ultimately cause a parser error to occur...
|
||||
yylex.Error(fmt.Sprintf("%s: %s", ErrParseResFieldInvalid, $1.str))
|
||||
}
|
||||
$$.resMeta = &StmtResMeta{
|
||||
Property: $3.str,
|
||||
MetaExpr: $7.expr,
|
||||
Condition: $5.expr,
|
||||
}
|
||||
}
|
||||
;
|
||||
resource_meta_struct:
|
||||
// Meta => struct{meta => true, retry => 3,},
|
||||
CAPITALIZED_IDENTIFIER ROCKET expr COMMA
|
||||
{
|
||||
posLast(yylex, yyDollar) // our pos
|
||||
if strings.ToLower($1.str) != strings.ToLower(MetaField) {
|
||||
// this will ultimately cause a parser error to occur...
|
||||
yylex.Error(fmt.Sprintf("%s: %s", ErrParseResFieldInvalid, $1.str))
|
||||
}
|
||||
$$.resMeta = &StmtResMeta{
|
||||
Property: $1.str,
|
||||
MetaExpr: $3.expr,
|
||||
}
|
||||
}
|
||||
;
|
||||
conditional_resource_meta_struct:
|
||||
// Meta => $present ?: struct{poll => 60, sema => ["foo:1", "bar:3",],},
|
||||
CAPITALIZED_IDENTIFIER ROCKET expr ELVIS expr COMMA
|
||||
{
|
||||
posLast(yylex, yyDollar) // our pos
|
||||
if strings.ToLower($1.str) != strings.ToLower(MetaField) {
|
||||
// this will ultimately cause a parser error to occur...
|
||||
yylex.Error(fmt.Sprintf("%s: %s", ErrParseResFieldInvalid, $1.str))
|
||||
}
|
||||
$$.resMeta = &StmtResMeta{
|
||||
Property: $1.str,
|
||||
MetaExpr: $5.expr,
|
||||
Condition: $3.expr,
|
||||
}
|
||||
}
|
||||
;
|
||||
edge:
|
||||
// TODO: we could technically prevent single edge_half pieces from being
|
||||
// parsed, but it's probably more work than is necessary...
|
||||
|
||||
322
lang/structs.go
322
lang/structs.go
@@ -36,6 +36,7 @@ import (
|
||||
"github.com/purpleidea/mgmt/util"
|
||||
|
||||
errwrap "github.com/pkg/errors"
|
||||
"golang.org/x/time/rate"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -55,6 +56,9 @@ const (
|
||||
// This is most similar to "require" in Puppet.
|
||||
EdgeDepend = "depend"
|
||||
|
||||
// MetaField is the prefix used to specify a meta parameter for the res.
|
||||
MetaField = "meta"
|
||||
|
||||
// AllowUserDefinedPolyFunc specifies if we allow user-defined
|
||||
// polymorphic functions or not. At the moment this is not implemented.
|
||||
// XXX: not implemented
|
||||
@@ -302,7 +306,6 @@ func (obj *StmtRes) Graph() (*pgraph.Graph, error) {
|
||||
// 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 as a simple meta field with a struct of the right type...
|
||||
func (obj *StmtRes) Output() (*interfaces.Output, error) {
|
||||
nameValue, err := obj.Name.Value()
|
||||
if err != nil {
|
||||
@@ -447,6 +450,12 @@ func (obj *StmtRes) Output() (*interfaces.Output, error) {
|
||||
return nil, errwrap.Wrapf(err, "error building edges")
|
||||
}
|
||||
|
||||
metaparams, err := obj.metaparams()
|
||||
if err != nil {
|
||||
return nil, errwrap.Wrapf(err, "error building meta params")
|
||||
}
|
||||
res.SetMetaParams(metaparams)
|
||||
|
||||
return &interfaces.Output{
|
||||
Resources: []engine.Res{res},
|
||||
Edges: edges,
|
||||
@@ -565,6 +574,115 @@ func (obj *StmtRes) edges() ([]*interfaces.Edge, error) {
|
||||
return edges, nil
|
||||
}
|
||||
|
||||
// metaparams is a helper function to generate the metaparams that come from the
|
||||
// resource.
|
||||
func (obj *StmtRes) metaparams() (*engine.MetaParams, error) {
|
||||
meta := engine.DefaultMetaParams.Copy() // defaults
|
||||
|
||||
for _, line := range obj.Contents {
|
||||
x, ok := line.(*StmtResMeta)
|
||||
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.MetaExpr.Value()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch p := strings.ToLower(x.Property); p {
|
||||
// TODO: we could add these fields dynamically if we were fancy!
|
||||
case "noop":
|
||||
meta.Noop = v.Bool() // must not panic
|
||||
|
||||
case "retry":
|
||||
x := v.Int() // must not panic
|
||||
// TODO: check that it doesn't overflow
|
||||
meta.Retry = int16(x)
|
||||
|
||||
case "delay":
|
||||
x := v.Int() // must not panic
|
||||
// TODO: check that it isn't signed
|
||||
meta.Delay = uint64(x)
|
||||
|
||||
case "poll":
|
||||
x := v.Int() // must not panic
|
||||
// TODO: check that it doesn't overflow and isn't signed
|
||||
meta.Poll = uint32(x)
|
||||
|
||||
case "limit": // rate.Limit
|
||||
x := v.Float() // must not panic
|
||||
meta.Limit = rate.Limit(x)
|
||||
|
||||
case "burst":
|
||||
x := v.Int() // must not panic
|
||||
// TODO: check that it doesn't overflow
|
||||
meta.Burst = int(x)
|
||||
|
||||
case "sema": // []string
|
||||
values := []string{}
|
||||
for _, x := range v.List() { // must not panic
|
||||
s := x.Str() // must not panic
|
||||
values = append(values, s)
|
||||
}
|
||||
meta.Sema = values
|
||||
|
||||
case MetaField:
|
||||
if val, exists := v.Struct()["noop"]; exists {
|
||||
meta.Noop = val.Bool() // must not panic
|
||||
}
|
||||
if val, exists := v.Struct()["retry"]; exists {
|
||||
x := val.Int() // must not panic
|
||||
// TODO: check that it doesn't overflow
|
||||
meta.Retry = int16(x)
|
||||
}
|
||||
if val, exists := v.Struct()["delay"]; exists {
|
||||
x := val.Int() // must not panic
|
||||
// TODO: check that it isn't signed
|
||||
meta.Delay = uint64(x)
|
||||
}
|
||||
if val, exists := v.Struct()["poll"]; exists {
|
||||
x := val.Int() // must not panic
|
||||
// TODO: check that it doesn't overflow and isn't signed
|
||||
meta.Poll = uint32(x)
|
||||
}
|
||||
if val, exists := v.Struct()["limit"]; exists {
|
||||
x := val.Float() // must not panic
|
||||
meta.Limit = rate.Limit(x)
|
||||
}
|
||||
if val, exists := v.Struct()["burst"]; exists {
|
||||
x := val.Int() // must not panic
|
||||
// TODO: check that it doesn't overflow
|
||||
meta.Burst = int(x)
|
||||
}
|
||||
if val, exists := v.Struct()["sema"]; exists {
|
||||
values := []string{}
|
||||
for _, x := range val.List() { // must not panic
|
||||
s := x.Str() // must not panic
|
||||
values = append(values, s)
|
||||
}
|
||||
meta.Sema = values
|
||||
}
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown property: %s", p)
|
||||
}
|
||||
}
|
||||
|
||||
return meta, 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 {
|
||||
@@ -884,6 +1002,208 @@ func (obj *StmtResEdge) Graph() (*pgraph.Graph, error) {
|
||||
return graph, nil
|
||||
}
|
||||
|
||||
// StmtResMeta represents a single meta value in the parsed resource
|
||||
// representation. It can also contain a struct that contains one or more meta
|
||||
// parameters. If it contains such a struct, then the `Property` field contains
|
||||
// the string found in the MetaField constant, otherwise this field will
|
||||
// correspond to the particular meta parameter specified. This does not satisfy
|
||||
// the Stmt interface.
|
||||
type StmtResMeta struct {
|
||||
Property string // TODO: iota constant instead?
|
||||
MetaExpr interfaces.Expr
|
||||
Condition interfaces.Expr // the value will be used if nil or true
|
||||
}
|
||||
|
||||
// Apply is a general purpose iterator method that operates on any AST node. It
|
||||
// is not used as the primary AST traversal function because it is less readable
|
||||
// and easy to reason about than manually implementing traversal for each node.
|
||||
// Nevertheless, it is a useful facility for operations that might only apply to
|
||||
// a select number of node types, since they won't need extra noop iterators...
|
||||
func (obj *StmtResMeta) Apply(fn func(interfaces.Node) error) error {
|
||||
if obj.Condition != nil {
|
||||
if err := obj.Condition.Apply(fn); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := obj.MetaExpr.Apply(fn); err != nil {
|
||||
return err
|
||||
}
|
||||
return fn(obj)
|
||||
}
|
||||
|
||||
// Init initializes this branch of the AST, and returns an error if it fails to
|
||||
// validate.
|
||||
func (obj *StmtResMeta) Init(data *interfaces.Data) error {
|
||||
if obj.Property == "" {
|
||||
return fmt.Errorf("empty property")
|
||||
}
|
||||
|
||||
switch p := strings.ToLower(obj.Property); p {
|
||||
// TODO: we could add these fields dynamically if we were fancy!
|
||||
case "noop":
|
||||
case "retry":
|
||||
case "delay":
|
||||
case "poll":
|
||||
case "limit":
|
||||
case "burst":
|
||||
case "sema":
|
||||
case MetaField:
|
||||
|
||||
default:
|
||||
return fmt.Errorf("invalid property: `%s`", obj.Property)
|
||||
}
|
||||
|
||||
if obj.Condition != nil {
|
||||
if err := obj.Condition.Init(data); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return obj.MetaExpr.Init(data)
|
||||
}
|
||||
|
||||
// Interpolate returns a new node (aka a copy) once it has been expanded. This
|
||||
// generally increases the size of the AST when it is used. It calls Interpolate
|
||||
// on any child elements and builds the new node with those new node contents.
|
||||
// This interpolate is different It is different from the interpolate found in
|
||||
// the Expr and Stmt interfaces because it returns a different type as output.
|
||||
func (obj *StmtResMeta) Interpolate() (StmtResContents, error) {
|
||||
interpolated, err := obj.MetaExpr.Interpolate()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var condition interfaces.Expr
|
||||
if obj.Condition != nil {
|
||||
condition, err = obj.Condition.Interpolate()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return &StmtResMeta{
|
||||
Property: obj.Property,
|
||||
MetaExpr: interpolated,
|
||||
Condition: condition,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// SetScope stores the scope for later use in this resource and it's children,
|
||||
// which it propagates this downwards to.
|
||||
func (obj *StmtResMeta) SetScope(scope *interfaces.Scope) error {
|
||||
if err := obj.MetaExpr.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.
|
||||
// XXX: Allow specifying partial meta param structs and unify the subset type.
|
||||
// XXX: The resource fields have the same limitation with field structs.
|
||||
func (obj *StmtResMeta) Unify(kind string) ([]interfaces.Invariant, error) {
|
||||
var invariants []interfaces.Invariant
|
||||
|
||||
invars, err := obj.MetaExpr.Unify()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
invariants = append(invariants, invars...)
|
||||
|
||||
// conditional expression might have some children invariants to share
|
||||
if obj.Condition != nil {
|
||||
condition, err := obj.Condition.Unify()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
invariants = append(invariants, condition...)
|
||||
|
||||
// the condition must ultimately be a boolean
|
||||
conditionInvar := &unification.EqualsInvariant{
|
||||
Expr: obj.Condition,
|
||||
Type: types.TypeBool,
|
||||
}
|
||||
invariants = append(invariants, conditionInvar)
|
||||
}
|
||||
|
||||
// add additional invariants based on what's in obj.Property !!!
|
||||
var typ *types.Type
|
||||
switch p := strings.ToLower(obj.Property); p {
|
||||
// TODO: we could add these fields dynamically if we were fancy!
|
||||
case "noop":
|
||||
typ = types.TypeBool
|
||||
|
||||
case "retry":
|
||||
typ = types.TypeInt
|
||||
|
||||
case "delay":
|
||||
typ = types.TypeInt
|
||||
|
||||
case "poll":
|
||||
typ = types.TypeInt
|
||||
|
||||
case "limit": // rate.Limit
|
||||
typ = types.TypeFloat
|
||||
|
||||
case "burst":
|
||||
typ = types.TypeInt
|
||||
|
||||
case "sema":
|
||||
typ = types.NewType("[]str")
|
||||
|
||||
case MetaField:
|
||||
// FIXME: allow partial subsets of this struct, and in any order
|
||||
// FIXME: we might need an updated unification engine to do this
|
||||
typ = types.NewType("struct{noop bool; retry int; delay int; poll int; limit float; burst int; sema []str}")
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown property: %s", p)
|
||||
}
|
||||
invar := &unification.EqualsInvariant{
|
||||
Expr: obj.MetaExpr,
|
||||
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 *StmtResMeta) Graph() (*pgraph.Graph, error) {
|
||||
graph, err := pgraph.NewGraph("resmeta")
|
||||
if err != nil {
|
||||
return nil, errwrap.Wrapf(err, "could not create graph")
|
||||
}
|
||||
|
||||
g, err := obj.MetaExpr.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.
|
||||
// Edges represents that the first resource (Kind/Name) listed in the
|
||||
// EdgeHalfList should happen in the resource graph *before* the next resource
|
||||
|
||||
Reference in New Issue
Block a user