lang: Add class and include statements
This adds support for the class definition statement and the include statement which produces the output from the corresponding class. The classes in this language support optional input parameters. In contrast with other tools, the class is *not* a singleton, although it can be used as one. Using include with equivalent input parameters will cause the class to act as a singleton, although it can also be used to produce distinct output. The output produced by including a class is actually a list of statements (a prog) which is ultimately a list of resources and edges. This is different from functions which produces values.
This commit is contained in:
@@ -85,8 +85,9 @@ These docs will be expanded on when things are more certain to be stable.
|
|||||||
|
|
||||||
There are a very small number of statements in our language. They include:
|
There are a very small number of statements in our language. They include:
|
||||||
|
|
||||||
- **bind**: bind's an expression to a variable within that scope
|
- **bind**: bind's an expression to a variable within that scope without output
|
||||||
- eg: `$x = 42`
|
- eg: `$x = 42`
|
||||||
|
|
||||||
- **if**: produces up to one branch of statements based on a conditional
|
- **if**: produces up to one branch of statements based on a conditional
|
||||||
expression
|
expression
|
||||||
|
|
||||||
@@ -114,6 +115,31 @@ expression
|
|||||||
File["/tmp/hello"] -> Print["alert4"]
|
File["/tmp/hello"] -> Print["alert4"]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
- **class**: bind's a list of statements to a class name in scope without output
|
||||||
|
|
||||||
|
```mcl
|
||||||
|
class foo {
|
||||||
|
# some statements go here
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
or
|
||||||
|
|
||||||
|
```mcl
|
||||||
|
class bar($a, $b) { # a parameterized class
|
||||||
|
# some statements go here
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- **include**: include a particular class at this location producing output
|
||||||
|
|
||||||
|
```mcl
|
||||||
|
include foo
|
||||||
|
|
||||||
|
include bar("hello", 42)
|
||||||
|
include bar("world", 13) # an include can be called multiple times
|
||||||
|
```
|
||||||
|
|
||||||
All statements produce _output_. Output consists of between zero and more
|
All statements produce _output_. Output consists of between zero and more
|
||||||
`edges` and `resources`. A resource statement can produce a resource, whereas an
|
`edges` and `resources`. A resource statement can produce a resource, whereas an
|
||||||
`if` statement produces whatever the chosen branch produces. Ultimately the goal
|
`if` statement produces whatever the chosen branch produces. Ultimately the goal
|
||||||
@@ -215,6 +241,82 @@ to express a relationship between three resources. The first character in the
|
|||||||
resource kind must be capitalized so that the parser can't ascertain
|
resource kind must be capitalized so that the parser can't ascertain
|
||||||
unambiguously that we are referring to a dependency relationship.
|
unambiguously that we are referring to a dependency relationship.
|
||||||
|
|
||||||
|
#### Class
|
||||||
|
|
||||||
|
A class is a grouping structure that bind's a list of statements to a name in
|
||||||
|
the scope where it is defined. It doesn't directly produce any output. To
|
||||||
|
produce output it must be called via the `include` statement.
|
||||||
|
|
||||||
|
Defining classes follows the same scoping and shadowing rules that is applied to
|
||||||
|
the `bind` statement, although they exist in a separate namespace. In other
|
||||||
|
words you can have a variable named `foo` and a class named `foo` in the same
|
||||||
|
scope without any conflicts.
|
||||||
|
|
||||||
|
Classes can be both parameterized or naked. If a parameterized class is defined,
|
||||||
|
then the argument types must be either specified manually, or inferred with the
|
||||||
|
type unification algorithm. One interesting property is that the same class
|
||||||
|
definition can be used with `include` via two different input signatures,
|
||||||
|
although in practice this is probably fairly rare. Some usage examples include:
|
||||||
|
|
||||||
|
A naked class definition:
|
||||||
|
|
||||||
|
```mcl
|
||||||
|
class foo {
|
||||||
|
# some statements go here
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
A parameterized class with both input types being inferred if possible:
|
||||||
|
|
||||||
|
```mcl
|
||||||
|
class bar($a, $b) {
|
||||||
|
# some statements go here
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
A parameterized class with one type specified statically and one being inferred:
|
||||||
|
|
||||||
|
```mcl
|
||||||
|
class baz($a str, $b) {
|
||||||
|
# some statements go here
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Classes can also be nested within other classes. Here's a contrived example:
|
||||||
|
|
||||||
|
```mcl
|
||||||
|
class c1($a, $b) {
|
||||||
|
# nested class definition
|
||||||
|
class c2($c) {
|
||||||
|
test $a {
|
||||||
|
stringptr => printf("%s is %d", $b, $c),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if $a == "t1" {
|
||||||
|
include c2(42)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Defining polymorphic classes was considered but is not currently allowed at this
|
||||||
|
time.
|
||||||
|
|
||||||
|
Recursive classes are not currently supported and it is not clear if they will
|
||||||
|
be in the future. Discussion about this topic is welcome on the mailing list.
|
||||||
|
|
||||||
|
#### Include
|
||||||
|
|
||||||
|
The `include` statement causes the previously defined class to produce the
|
||||||
|
contained output. This statement must be called with parameters if the named
|
||||||
|
class is defined with those.
|
||||||
|
|
||||||
|
The defined class can be called as many times as you'd like either within the
|
||||||
|
same scope or within different scopes. If a class uses inferred type input
|
||||||
|
parameters, then the same class can even be called with different signatures.
|
||||||
|
Whether the output is useful and whether there is a unique type unification
|
||||||
|
solution is dependent on your code.
|
||||||
|
|
||||||
### Stages
|
### Stages
|
||||||
|
|
||||||
The mgmt compiler runs in a number of stages. In order of execution they are:
|
The mgmt compiler runs in a number of stages. In order of execution they are:
|
||||||
@@ -596,6 +698,39 @@ someListOfStrings := &types.ListValue{
|
|||||||
If you don't build these properly, then you will cause a panic! Even empty lists
|
If you don't build these properly, then you will cause a panic! Even empty lists
|
||||||
have a type.
|
have a type.
|
||||||
|
|
||||||
|
### Is the `class` statement a singleton?
|
||||||
|
|
||||||
|
Not really, but practically it can be used as such. The `class` statement is not
|
||||||
|
a singleton since it can be called multiple times in different locations, and it
|
||||||
|
can also be parameterized and called multiple times (with `include`) using
|
||||||
|
different input parameters. The reason it can be used as such is that statement
|
||||||
|
output (from multple classes) that is compatible (and usually identical) will
|
||||||
|
be automatically collated and have the duplicates removed. In that way, you can
|
||||||
|
assume that an unparameterized class is always a singleton, and that
|
||||||
|
parameterized classes can often be singletons depending on their contents and if
|
||||||
|
they are called in an identical way or not. In reality the de-duplication
|
||||||
|
actually happens at the resource output level, so anything that produces
|
||||||
|
multiple compatible resources is allowed.
|
||||||
|
|
||||||
|
### Are recursive `class` definitions supported?
|
||||||
|
|
||||||
|
Recursive class definitions where the contents of a `class` contain a
|
||||||
|
self-referential `include`, either directly, or with indirection via any other
|
||||||
|
number of classes is not supported. It's not clear if it ever will be in the
|
||||||
|
future, unless we decide it's worth the extra complexity. The reason is that our
|
||||||
|
FRP actually generates a static graph which doesn't change unless the code does.
|
||||||
|
To support dynamic graphs would require our FRP to be a "higher-order" FRP,
|
||||||
|
instead of the simpler "first-order" FRP that it is now. You might want to
|
||||||
|
verify that I got the [nomenclature](https://github.com/gelisam/frp-zoo)
|
||||||
|
correct. If it turns out that there's an important advantage to supporting a
|
||||||
|
higher-order FRP in mgmt, then we can consider that in the future.
|
||||||
|
|
||||||
|
I realized that recursion would require a static graph when I considered the
|
||||||
|
structure required for a simple recursive class definition. If some "depth"
|
||||||
|
value wasn't known statically by compile time, then there would be no way to
|
||||||
|
know how large the graph would grow, and furthermore, the graph would need to
|
||||||
|
change if that "depth" value changed.
|
||||||
|
|
||||||
### I don't like the mgmt language, is there an alternative?
|
### I don't like the mgmt language, is there an alternative?
|
||||||
|
|
||||||
Yes, the language is just one of the available "frontends" that passes a stream
|
Yes, the language is just one of the available "frontends" that passes a stream
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ type Expr interface {
|
|||||||
type Scope struct {
|
type Scope struct {
|
||||||
Variables map[string]Expr
|
Variables map[string]Expr
|
||||||
//Functions map[string]??? // TODO: do we want a separate namespace for user defined functions?
|
//Functions map[string]??? // TODO: do we want a separate namespace for user defined functions?
|
||||||
|
Classes map[string]Stmt
|
||||||
}
|
}
|
||||||
|
|
||||||
// Empty returns the zero, empty value for the scope, with all the internal
|
// Empty returns the zero, empty value for the scope, with all the internal
|
||||||
@@ -76,6 +77,7 @@ func (obj *Scope) Empty() *Scope {
|
|||||||
return &Scope{
|
return &Scope{
|
||||||
Variables: make(map[string]Expr),
|
Variables: make(map[string]Expr),
|
||||||
//Functions: ???,
|
//Functions: ???,
|
||||||
|
Classes: make(map[string]Stmt),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,13 +87,18 @@ func (obj *Scope) Empty() *Scope {
|
|||||||
// we need those to be consistently pointing to the same things after copying.
|
// we need those to be consistently pointing to the same things after copying.
|
||||||
func (obj *Scope) Copy() *Scope {
|
func (obj *Scope) Copy() *Scope {
|
||||||
variables := make(map[string]Expr)
|
variables := make(map[string]Expr)
|
||||||
|
classes := make(map[string]Stmt)
|
||||||
if obj != nil { // allow copying nil scopes
|
if obj != nil { // allow copying nil scopes
|
||||||
for k, v := range obj.Variables { // copy
|
for k, v := range obj.Variables { // copy
|
||||||
variables[k] = v // we don't copy the expr's!
|
variables[k] = v // we don't copy the expr's!
|
||||||
}
|
}
|
||||||
|
for k, v := range obj.Classes { // copy
|
||||||
|
classes[k] = v // we don't copy the StmtClass!
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return &Scope{
|
return &Scope{
|
||||||
Variables: variables,
|
Variables: variables,
|
||||||
|
Classes: classes,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -378,6 +378,428 @@ func TestInterpretMany(t *testing.T) {
|
|||||||
graph: graph,
|
graph: graph,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
graph, _ := pgraph.NewGraph("g")
|
||||||
|
r1, _ := engine.NewNamedResource("test", "t1")
|
||||||
|
x1 := r1.(*resources.TestRes)
|
||||||
|
s1 := "hello"
|
||||||
|
x1.StringPtr = &s1
|
||||||
|
graph.AddVertex(x1)
|
||||||
|
values = append(values, test{
|
||||||
|
name: "single include",
|
||||||
|
code: `
|
||||||
|
class c1($a, $b) {
|
||||||
|
test $a {
|
||||||
|
stringptr => $b,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
include c1("t1", "hello")
|
||||||
|
`,
|
||||||
|
fail: false,
|
||||||
|
graph: graph,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
{
|
||||||
|
graph, _ := pgraph.NewGraph("g")
|
||||||
|
r1, _ := engine.NewNamedResource("test", "t1")
|
||||||
|
r2, _ := engine.NewNamedResource("test", "t2")
|
||||||
|
x1 := r1.(*resources.TestRes)
|
||||||
|
x2 := r2.(*resources.TestRes)
|
||||||
|
s1, s2 := "hello", "world"
|
||||||
|
x1.StringPtr = &s1
|
||||||
|
x2.StringPtr = &s2
|
||||||
|
graph.AddVertex(x1, x2)
|
||||||
|
values = append(values, test{
|
||||||
|
name: "double include",
|
||||||
|
code: `
|
||||||
|
class c1($a, $b) {
|
||||||
|
test $a {
|
||||||
|
stringptr => $b,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
include c1("t1", "hello")
|
||||||
|
include c1("t2", "world")
|
||||||
|
`,
|
||||||
|
fail: false,
|
||||||
|
graph: graph,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
{
|
||||||
|
//graph, _ := pgraph.NewGraph("g")
|
||||||
|
//r1, _ := engine.NewNamedResource("test", "t1")
|
||||||
|
//r2, _ := engine.NewNamedResource("test", "t2")
|
||||||
|
//x1 := r1.(*resources.TestRes)
|
||||||
|
//x2 := r2.(*resources.TestRes)
|
||||||
|
//s1, i2 := "hello", int64(42)
|
||||||
|
//x1.StringPtr = &s1
|
||||||
|
//x2.Int64Ptr = &i2
|
||||||
|
//graph.AddVertex(x1, x2)
|
||||||
|
values = append(values, test{
|
||||||
|
name: "double include different types error",
|
||||||
|
code: `
|
||||||
|
class c1($a, $b) {
|
||||||
|
if $a == "t1" {
|
||||||
|
test $a {
|
||||||
|
stringptr => $b,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
test $a {
|
||||||
|
int64ptr => $b,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
include c1("t1", "hello")
|
||||||
|
include c1("t2", 42)
|
||||||
|
`,
|
||||||
|
fail: true, // should not be able to type check this!
|
||||||
|
//graph: graph,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
{
|
||||||
|
graph, _ := pgraph.NewGraph("g")
|
||||||
|
r1, _ := engine.NewNamedResource("test", "t1")
|
||||||
|
r2, _ := engine.NewNamedResource("test", "t2")
|
||||||
|
x1 := r1.(*resources.TestRes)
|
||||||
|
x2 := r2.(*resources.TestRes)
|
||||||
|
s1, s2 := "testing", "testing"
|
||||||
|
x1.StringPtr = &s1
|
||||||
|
x2.StringPtr = &s2
|
||||||
|
graph.AddVertex(x1, x2)
|
||||||
|
values = append(values, test{
|
||||||
|
name: "double include different types allowed",
|
||||||
|
code: `
|
||||||
|
class c1($a, $b) {
|
||||||
|
if $b == $b { # for example purposes
|
||||||
|
test $a {
|
||||||
|
stringptr => "testing",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
include c1("t1", "hello")
|
||||||
|
include c1("t2", 42) # this has a different sig
|
||||||
|
`,
|
||||||
|
fail: false,
|
||||||
|
graph: graph,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// TODO: add this test once printf supports %v
|
||||||
|
//{
|
||||||
|
// graph, _ := pgraph.NewGraph("g")
|
||||||
|
// r1, _ := engine.NewNamedResource("test", "t1")
|
||||||
|
// r2, _ := engine.NewNamedResource("test", "t2")
|
||||||
|
// x1 := r1.(*resources.TestRes)
|
||||||
|
// x2 := r2.(*resources.TestRes)
|
||||||
|
// s1, s2 := "value is: hello", "value is: 42"
|
||||||
|
// x1.StringPtr = &s1
|
||||||
|
// x2.StringPtr = &s2
|
||||||
|
// graph.AddVertex(x1, x2)
|
||||||
|
// values = append(values, test{
|
||||||
|
// name: "double include different printf types allowed",
|
||||||
|
// code: `
|
||||||
|
// class c1($a, $b) {
|
||||||
|
// test $a {
|
||||||
|
// stringptr => printf("value is: %v", $b),
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// include c1("t1", "hello")
|
||||||
|
// include c1("t2", 42)
|
||||||
|
// `,
|
||||||
|
// fail: false,
|
||||||
|
// graph: graph,
|
||||||
|
// })
|
||||||
|
//}
|
||||||
|
{
|
||||||
|
graph, _ := pgraph.NewGraph("g")
|
||||||
|
r1, _ := engine.NewNamedResource("test", "t1")
|
||||||
|
r2, _ := engine.NewNamedResource("test", "t2")
|
||||||
|
x1 := r1.(*resources.TestRes)
|
||||||
|
x2 := r2.(*resources.TestRes)
|
||||||
|
s1, s2 := "hey", "hey"
|
||||||
|
x1.StringPtr = &s1
|
||||||
|
x2.StringPtr = &s2
|
||||||
|
graph.AddVertex(x1, x2)
|
||||||
|
values = append(values, test{
|
||||||
|
name: "double include with variable in parent scope",
|
||||||
|
code: `
|
||||||
|
$foo = "hey"
|
||||||
|
class c1($a) {
|
||||||
|
test $a {
|
||||||
|
stringptr => $foo,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
include c1("t1")
|
||||||
|
include c1("t2")
|
||||||
|
`,
|
||||||
|
fail: false,
|
||||||
|
graph: graph,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
{
|
||||||
|
graph, _ := pgraph.NewGraph("g")
|
||||||
|
r1, _ := engine.NewNamedResource("test", "t1")
|
||||||
|
r2, _ := engine.NewNamedResource("test", "t2")
|
||||||
|
x1 := r1.(*resources.TestRes)
|
||||||
|
x2 := r2.(*resources.TestRes)
|
||||||
|
s1, s2 := "hey", "hey"
|
||||||
|
x1.StringPtr = &s1
|
||||||
|
x2.StringPtr = &s2
|
||||||
|
graph.AddVertex(x1, x2)
|
||||||
|
values = append(values, test{
|
||||||
|
name: "double include with out of order variable in parent scope",
|
||||||
|
code: `
|
||||||
|
include c1("t1")
|
||||||
|
include c1("t2")
|
||||||
|
class c1($a) {
|
||||||
|
test $a {
|
||||||
|
stringptr => $foo,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$foo = "hey"
|
||||||
|
`,
|
||||||
|
fail: false,
|
||||||
|
graph: graph,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
{
|
||||||
|
graph, _ := pgraph.NewGraph("g")
|
||||||
|
r1, _ := engine.NewNamedResource("test", "t1")
|
||||||
|
x1 := r1.(*resources.TestRes)
|
||||||
|
s1 := "hello"
|
||||||
|
x1.StringPtr = &s1
|
||||||
|
graph.AddVertex(x1)
|
||||||
|
values = append(values, test{
|
||||||
|
name: "duplicate include identical",
|
||||||
|
code: `
|
||||||
|
include c1("t1", "hello")
|
||||||
|
class c1($a, $b) {
|
||||||
|
test $a {
|
||||||
|
stringptr => $b,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
include c1("t1", "hello") # this is an identical dupe
|
||||||
|
`,
|
||||||
|
fail: false,
|
||||||
|
graph: graph,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
{
|
||||||
|
graph, _ := pgraph.NewGraph("g")
|
||||||
|
r1, _ := engine.NewNamedResource("test", "t1")
|
||||||
|
x1 := r1.(*resources.TestRes)
|
||||||
|
s1 := "hello"
|
||||||
|
x1.StringPtr = &s1
|
||||||
|
graph.AddVertex(x1)
|
||||||
|
values = append(values, test{
|
||||||
|
name: "duplicate include non-identical",
|
||||||
|
code: `
|
||||||
|
include c1("t1", "hello")
|
||||||
|
class c1($a, $b) {
|
||||||
|
if $a == "t1" {
|
||||||
|
test $a {
|
||||||
|
stringptr => $b,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
test "t1" {
|
||||||
|
stringptr => $b,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
include c1("t?", "hello") # should cause an identical dupe
|
||||||
|
`,
|
||||||
|
fail: false,
|
||||||
|
graph: graph,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
{
|
||||||
|
values = append(values, test{
|
||||||
|
name: "duplicate include incompatible",
|
||||||
|
code: `
|
||||||
|
include c1("t1", "hello")
|
||||||
|
class c1($a, $b) {
|
||||||
|
test $a {
|
||||||
|
stringptr => $b,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
include c1("t1", "world") # incompatible
|
||||||
|
`,
|
||||||
|
fail: true, // incompatible resources
|
||||||
|
})
|
||||||
|
}
|
||||||
|
{
|
||||||
|
values = append(values, test{
|
||||||
|
name: "class wrong number of args 1",
|
||||||
|
code: `
|
||||||
|
include c1("hello") # missing second arg
|
||||||
|
class c1($a, $b) {
|
||||||
|
test $a {
|
||||||
|
stringptr => $b,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
fail: true, // but should NOT panic
|
||||||
|
})
|
||||||
|
}
|
||||||
|
{
|
||||||
|
values = append(values, test{
|
||||||
|
name: "class wrong number of args 2",
|
||||||
|
code: `
|
||||||
|
include c1("hello", 42) # added second arg
|
||||||
|
class c1($a) {
|
||||||
|
test $a {
|
||||||
|
stringptr => "world",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
fail: true, // but should NOT panic
|
||||||
|
})
|
||||||
|
}
|
||||||
|
{
|
||||||
|
graph, _ := pgraph.NewGraph("g")
|
||||||
|
r1, _ := engine.NewNamedResource("test", "t1")
|
||||||
|
r2, _ := engine.NewNamedResource("test", "t2")
|
||||||
|
x1 := r1.(*resources.TestRes)
|
||||||
|
x2 := r2.(*resources.TestRes)
|
||||||
|
s1, s2 := "hello is 42", "world is 13"
|
||||||
|
x1.StringPtr = &s1
|
||||||
|
x2.StringPtr = &s2
|
||||||
|
graph.AddVertex(x1, x2)
|
||||||
|
values = append(values, test{
|
||||||
|
name: "nested classes 1",
|
||||||
|
code: `
|
||||||
|
include c1("t1", "hello") # test["t1"] -> hello is 42
|
||||||
|
include c1("t2", "world") # test["t2"] -> world is 13
|
||||||
|
|
||||||
|
class c1($a, $b) {
|
||||||
|
# nested class definition
|
||||||
|
class c2($c) {
|
||||||
|
test $a {
|
||||||
|
stringptr => printf("%s is %d", $b, $c),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if $a == "t1" {
|
||||||
|
include c2(42)
|
||||||
|
} else {
|
||||||
|
include c2(13)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
fail: false,
|
||||||
|
graph: graph,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
{
|
||||||
|
values = append(values, test{
|
||||||
|
name: "nested classes out of scope 1",
|
||||||
|
code: `
|
||||||
|
include c1("t1", "hello") # test["t1"] -> hello is 42
|
||||||
|
include c2(99) # out of scope
|
||||||
|
|
||||||
|
class c1($a, $b) {
|
||||||
|
# nested class definition
|
||||||
|
class c2($c) {
|
||||||
|
test $a {
|
||||||
|
stringptr => printf("%s is %d", $b, $c),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if $a == "t1" {
|
||||||
|
include c2(42)
|
||||||
|
} else {
|
||||||
|
include c2(13)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
fail: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// TODO: recursive classes are not currently supported (should they be?)
|
||||||
|
//{
|
||||||
|
// graph, _ := pgraph.NewGraph("g")
|
||||||
|
// values = append(values, test{
|
||||||
|
// name: "recursive classes 0",
|
||||||
|
// code: `
|
||||||
|
// include c1(0) # start at zero
|
||||||
|
// class c1($count) {
|
||||||
|
// if $count != 3 {
|
||||||
|
// include c1($count + 1)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// `,
|
||||||
|
// fail: false,
|
||||||
|
// graph: graph, // produces no output
|
||||||
|
// })
|
||||||
|
//}
|
||||||
|
// TODO: recursive classes are not currently supported (should they be?)
|
||||||
|
//{
|
||||||
|
// graph, _ := pgraph.NewGraph("g")
|
||||||
|
// r0, _ := engine.NewNamedResource("test", "done")
|
||||||
|
// x0 := r0.(*resources.TestRes)
|
||||||
|
// s0 := "count is 3"
|
||||||
|
// x0.StringPtr = &s0
|
||||||
|
// graph.AddVertex(x0)
|
||||||
|
// values = append(values, test{
|
||||||
|
// name: "recursive classes 1",
|
||||||
|
// code: `
|
||||||
|
// $max = 3
|
||||||
|
// include c1(0) # start at zero
|
||||||
|
// # test["done"] -> count is 3
|
||||||
|
// class c1($count) {
|
||||||
|
// if $count == $max {
|
||||||
|
// test "done" {
|
||||||
|
// stringptr => printf("count is %d", $count),
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// include c1($count + 1)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// `,
|
||||||
|
// fail: false,
|
||||||
|
// graph: graph,
|
||||||
|
// })
|
||||||
|
//}
|
||||||
|
// TODO: recursive classes are not currently supported (should they be?)
|
||||||
|
//{
|
||||||
|
// graph, _ := pgraph.NewGraph("g")
|
||||||
|
// r0, _ := engine.NewNamedResource("test", "zero")
|
||||||
|
// r1, _ := engine.NewNamedResource("test", "ix:1")
|
||||||
|
// r2, _ := engine.NewNamedResource("test", "ix:2")
|
||||||
|
// r3, _ := engine.NewNamedResource("test", "ix:3")
|
||||||
|
// x0 := r0.(*resources.TestRes)
|
||||||
|
// x1 := r1.(*resources.TestRes)
|
||||||
|
// x2 := r2.(*resources.TestRes)
|
||||||
|
// x3 := r3.(*resources.TestRes)
|
||||||
|
// s0, s1, s2, s3 := "count is 0", "count is 1", "count is 2", "count is 3"
|
||||||
|
// x0.StringPtr = &s0
|
||||||
|
// x1.StringPtr = &s1
|
||||||
|
// x2.StringPtr = &s2
|
||||||
|
// x3.StringPtr = &s3
|
||||||
|
// graph.AddVertex(x0, x1, x2, x3)
|
||||||
|
// values = append(values, test{
|
||||||
|
// name: "recursive classes 2",
|
||||||
|
// code: `
|
||||||
|
// include c1("ix", 3)
|
||||||
|
// # test["ix:3"] -> count is 3
|
||||||
|
// # test["ix:2"] -> count is 2
|
||||||
|
// # test["ix:1"] -> count is 1
|
||||||
|
// # test["zero"] -> count is 0
|
||||||
|
// class c1($name, $count) {
|
||||||
|
// if $count == 0 {
|
||||||
|
// test "zero" {
|
||||||
|
// stringptr => printf("count is %d", $count),
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// include c1($name, $count - 1)
|
||||||
|
// test "${name}:${count}" {
|
||||||
|
// stringptr => printf("count is %d", $count),
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// `,
|
||||||
|
// fail: false,
|
||||||
|
// graph: graph,
|
||||||
|
// })
|
||||||
|
//}
|
||||||
|
|
||||||
for index, test := range values { // run all the tests
|
for index, test := range values { // run all the tests
|
||||||
name, code, fail, exp := test.name, test.code, test.fail, test.graph
|
name, code, fail, exp := test.name, test.code, test.fail, test.graph
|
||||||
|
|||||||
@@ -174,6 +174,16 @@
|
|||||||
lval.str = yylex.Text()
|
lval.str = yylex.Text()
|
||||||
return STRUCT_IDENTIFIER
|
return STRUCT_IDENTIFIER
|
||||||
}
|
}
|
||||||
|
/class/ {
|
||||||
|
yylex.pos(lval) // our pos
|
||||||
|
lval.str = yylex.Text()
|
||||||
|
return CLASS_IDENTIFIER
|
||||||
|
}
|
||||||
|
/include/ {
|
||||||
|
yylex.pos(lval) // our pos
|
||||||
|
lval.str = yylex.Text()
|
||||||
|
return INCLUDE_IDENTIFIER
|
||||||
|
}
|
||||||
/variant/ {
|
/variant/ {
|
||||||
yylex.pos(lval) // our pos
|
yylex.pos(lval) // our pos
|
||||||
lval.str = yylex.Text()
|
lval.str = yylex.Text()
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/purpleidea/mgmt/lang/interfaces"
|
"github.com/purpleidea/mgmt/lang/interfaces"
|
||||||
|
"github.com/purpleidea/mgmt/lang/types"
|
||||||
|
|
||||||
"github.com/davecgh/go-spew/spew"
|
"github.com/davecgh/go-spew/spew"
|
||||||
)
|
)
|
||||||
@@ -859,6 +860,195 @@ func TestLexParse0(t *testing.T) {
|
|||||||
fail: true,
|
fail: true,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
exp := &StmtProg{
|
||||||
|
Prog: []interfaces.Stmt{
|
||||||
|
&StmtClass{
|
||||||
|
Name: "c1",
|
||||||
|
Body: &StmtProg{
|
||||||
|
Prog: []interfaces.Stmt{
|
||||||
|
&StmtRes{
|
||||||
|
Kind: "test",
|
||||||
|
Name: &ExprStr{
|
||||||
|
V: "t1",
|
||||||
|
},
|
||||||
|
Contents: []StmtResContents{
|
||||||
|
&StmtResField{
|
||||||
|
Field: "stringptr",
|
||||||
|
Value: &ExprStr{
|
||||||
|
V: "hello",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&StmtInclude{
|
||||||
|
Name: "c1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
values = append(values, test{
|
||||||
|
name: "simple class 1",
|
||||||
|
code: `
|
||||||
|
class c1 {
|
||||||
|
test "t1" {
|
||||||
|
stringptr => "hello",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
include c1
|
||||||
|
`,
|
||||||
|
fail: false,
|
||||||
|
exp: exp,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
{
|
||||||
|
exp := &StmtProg{
|
||||||
|
Prog: []interfaces.Stmt{
|
||||||
|
&StmtClass{
|
||||||
|
Name: "c1",
|
||||||
|
Args: []*Arg{
|
||||||
|
{
|
||||||
|
Name: "a",
|
||||||
|
//Type: &types.Type{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "b",
|
||||||
|
//Type: &types.Type{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Body: &StmtProg{
|
||||||
|
Prog: []interfaces.Stmt{
|
||||||
|
&StmtRes{
|
||||||
|
Kind: "test",
|
||||||
|
Name: &ExprVar{
|
||||||
|
Name: "a",
|
||||||
|
},
|
||||||
|
Contents: []StmtResContents{
|
||||||
|
&StmtResField{
|
||||||
|
Field: "stringptr",
|
||||||
|
Value: &ExprVar{
|
||||||
|
Name: "b",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&StmtInclude{
|
||||||
|
Name: "c1",
|
||||||
|
Args: []interfaces.Expr{
|
||||||
|
&ExprStr{
|
||||||
|
V: "t1",
|
||||||
|
},
|
||||||
|
&ExprStr{
|
||||||
|
V: "hello",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&StmtInclude{
|
||||||
|
Name: "c1",
|
||||||
|
Args: []interfaces.Expr{
|
||||||
|
&ExprStr{
|
||||||
|
V: "t2",
|
||||||
|
},
|
||||||
|
&ExprStr{
|
||||||
|
V: "world",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
values = append(values, test{
|
||||||
|
name: "simple class with args 1",
|
||||||
|
code: `
|
||||||
|
class c1($a, $b) {
|
||||||
|
test $a {
|
||||||
|
stringptr => $b,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
include c1("t1", "hello")
|
||||||
|
include c1("t2", "world")
|
||||||
|
`,
|
||||||
|
fail: false,
|
||||||
|
exp: exp,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
{
|
||||||
|
exp := &StmtProg{
|
||||||
|
Prog: []interfaces.Stmt{
|
||||||
|
&StmtClass{
|
||||||
|
Name: "c1",
|
||||||
|
Args: []*Arg{
|
||||||
|
{
|
||||||
|
Name: "a",
|
||||||
|
Type: types.TypeStr,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "b",
|
||||||
|
//Type: &types.Type{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Body: &StmtProg{
|
||||||
|
Prog: []interfaces.Stmt{
|
||||||
|
&StmtRes{
|
||||||
|
Kind: "test",
|
||||||
|
Name: &ExprVar{
|
||||||
|
Name: "a",
|
||||||
|
},
|
||||||
|
Contents: []StmtResContents{
|
||||||
|
&StmtResField{
|
||||||
|
Field: "stringptr",
|
||||||
|
Value: &ExprVar{
|
||||||
|
Name: "b",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&StmtInclude{
|
||||||
|
Name: "c1",
|
||||||
|
Args: []interfaces.Expr{
|
||||||
|
&ExprStr{
|
||||||
|
V: "t1",
|
||||||
|
},
|
||||||
|
&ExprStr{
|
||||||
|
V: "hello",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&StmtInclude{
|
||||||
|
Name: "c1",
|
||||||
|
Args: []interfaces.Expr{
|
||||||
|
&ExprStr{
|
||||||
|
V: "t2",
|
||||||
|
},
|
||||||
|
&ExprStr{
|
||||||
|
V: "world",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
values = append(values, test{
|
||||||
|
name: "simple class with typed args 1",
|
||||||
|
code: `
|
||||||
|
class c1($a str, $b) {
|
||||||
|
test $a {
|
||||||
|
stringptr => $b,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
include c1("t1", "hello")
|
||||||
|
include c1("t2", "world")
|
||||||
|
`,
|
||||||
|
fail: false,
|
||||||
|
exp: exp,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
for index, test := range values { // run all the tests
|
for index, test := range values { // run all the tests
|
||||||
name, code, fail, exp := test.name, test.code, test.fail, test.exp
|
name, code, fail, exp := test.name, test.code, test.fail, test.exp
|
||||||
|
|||||||
@@ -63,6 +63,9 @@ func init() {
|
|||||||
structFields []*ExprStructField
|
structFields []*ExprStructField
|
||||||
structField *ExprStructField
|
structField *ExprStructField
|
||||||
|
|
||||||
|
args []*Arg
|
||||||
|
arg *Arg
|
||||||
|
|
||||||
resContents []StmtResContents // interface
|
resContents []StmtResContents // interface
|
||||||
resField *StmtResField
|
resField *StmtResField
|
||||||
resEdge *StmtResEdge
|
resEdge *StmtResEdge
|
||||||
@@ -82,6 +85,7 @@ func init() {
|
|||||||
%token STR_IDENTIFIER BOOL_IDENTIFIER INT_IDENTIFIER FLOAT_IDENTIFIER
|
%token STR_IDENTIFIER BOOL_IDENTIFIER INT_IDENTIFIER FLOAT_IDENTIFIER
|
||||||
%token STRUCT_IDENTIFIER VARIANT_IDENTIFIER VAR_IDENTIFIER IDENTIFIER
|
%token STRUCT_IDENTIFIER VARIANT_IDENTIFIER VAR_IDENTIFIER IDENTIFIER
|
||||||
%token VAR_IDENTIFIER_HX CAPITALIZED_IDENTIFIER
|
%token VAR_IDENTIFIER_HX CAPITALIZED_IDENTIFIER
|
||||||
|
%token CLASS_IDENTIFIER INCLUDE_IDENTIFIER
|
||||||
%token COMMENT ERROR
|
%token COMMENT ERROR
|
||||||
|
|
||||||
// precedence table
|
// precedence table
|
||||||
@@ -185,6 +189,44 @@ stmt:
|
|||||||
ElseBranch: $8.stmt,
|
ElseBranch: $8.stmt,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// `class name { <prog> }`
|
||||||
|
| CLASS_IDENTIFIER IDENTIFIER OPEN_CURLY prog CLOSE_CURLY
|
||||||
|
{
|
||||||
|
posLast(yylex, yyDollar) // our pos
|
||||||
|
$$.stmt = &StmtClass{
|
||||||
|
Name: $2.str,
|
||||||
|
Args: nil,
|
||||||
|
Body: $4.stmt,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// `class name(<arg>) { <prog> }`
|
||||||
|
// `class name(<arg>, <arg>) { <prog> }`
|
||||||
|
| CLASS_IDENTIFIER IDENTIFIER OPEN_PAREN args CLOSE_PAREN OPEN_CURLY prog CLOSE_CURLY
|
||||||
|
{
|
||||||
|
posLast(yylex, yyDollar) // our pos
|
||||||
|
$$.stmt = &StmtClass{
|
||||||
|
Name: $2.str,
|
||||||
|
Args: $4.args,
|
||||||
|
Body: $7.stmt,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// `include name`
|
||||||
|
| INCLUDE_IDENTIFIER IDENTIFIER
|
||||||
|
{
|
||||||
|
posLast(yylex, yyDollar) // our pos
|
||||||
|
$$.stmt = &StmtInclude{
|
||||||
|
Name: $2.str,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// `include name(...)`
|
||||||
|
| INCLUDE_IDENTIFIER IDENTIFIER OPEN_PAREN call_args CLOSE_PAREN
|
||||||
|
{
|
||||||
|
posLast(yylex, yyDollar) // our pos
|
||||||
|
$$.stmt = &StmtInclude{
|
||||||
|
Name: $2.str,
|
||||||
|
Args: $4.exprs,
|
||||||
|
}
|
||||||
|
}
|
||||||
/*
|
/*
|
||||||
// resource bind
|
// resource bind
|
||||||
| rbind
|
| rbind
|
||||||
@@ -596,6 +638,7 @@ call:
|
|||||||
}
|
}
|
||||||
;
|
;
|
||||||
// list order gets us the position of the arg, but named params would work too!
|
// list order gets us the position of the arg, but named params would work too!
|
||||||
|
// this is also used by the include statement when the called class uses args!
|
||||||
call_args:
|
call_args:
|
||||||
/* end of list */
|
/* end of list */
|
||||||
{
|
{
|
||||||
@@ -623,6 +666,40 @@ var:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
;
|
;
|
||||||
|
args:
|
||||||
|
/* end of list */
|
||||||
|
{
|
||||||
|
posLast(yylex, yyDollar) // our pos
|
||||||
|
$$.args = []*Arg{}
|
||||||
|
}
|
||||||
|
| args COMMA arg
|
||||||
|
{
|
||||||
|
posLast(yylex, yyDollar) // our pos
|
||||||
|
$$.args = append($1.args, $3.arg)
|
||||||
|
}
|
||||||
|
| arg
|
||||||
|
{
|
||||||
|
posLast(yylex, yyDollar) // our pos
|
||||||
|
$$.args = append([]*Arg{}, $1.arg)
|
||||||
|
}
|
||||||
|
;
|
||||||
|
arg:
|
||||||
|
// `$x`
|
||||||
|
VAR_IDENTIFIER
|
||||||
|
{
|
||||||
|
$$.arg = &Arg{
|
||||||
|
Name: $1.str,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// `$x <type>`
|
||||||
|
| VAR_IDENTIFIER type
|
||||||
|
{
|
||||||
|
$$.arg = &Arg{
|
||||||
|
Name: $1.str,
|
||||||
|
Type: $2.typ,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
;
|
||||||
bind:
|
bind:
|
||||||
VAR_IDENTIFIER EQUALS expr
|
VAR_IDENTIFIER EQUALS expr
|
||||||
{
|
{
|
||||||
|
|||||||
345
lang/structs.go
345
lang/structs.go
@@ -61,7 +61,7 @@ type StmtBind struct {
|
|||||||
Value interfaces.Expr
|
Value interfaces.Expr
|
||||||
}
|
}
|
||||||
|
|
||||||
// Interpolate returns a new node (or itself) once it has been expanded. This
|
// 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
|
// 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.
|
||||||
func (obj *StmtBind) Interpolate() (interfaces.Stmt, error) {
|
func (obj *StmtBind) Interpolate() (interfaces.Stmt, error) {
|
||||||
@@ -125,7 +125,7 @@ type StmtRes struct {
|
|||||||
Contents []StmtResContents // list of fields/edges 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 (aka a copy) 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.
|
||||||
func (obj *StmtRes) Interpolate() (interfaces.Stmt, error) {
|
func (obj *StmtRes) Interpolate() (interfaces.Stmt, error) {
|
||||||
@@ -512,7 +512,7 @@ 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
|
// 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
|
// 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.
|
||||||
// This interpolate is different It is different from the interpolate found in
|
// This interpolate is different It is different from the interpolate found in
|
||||||
@@ -647,7 +647,7 @@ type StmtResEdge 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
|
// 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
|
// 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.
|
||||||
// This interpolate is different It is different from the interpolate found in
|
// This interpolate is different It is different from the interpolate found in
|
||||||
@@ -764,7 +764,7 @@ type StmtEdge struct {
|
|||||||
Notify bool // specifies that this edge sends a notification as well
|
Notify bool // specifies that this edge sends a notification as well
|
||||||
}
|
}
|
||||||
|
|
||||||
// Interpolate returns a new node (or itself) once it has been expanded. This
|
// 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
|
// 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 the Name's from the EdgeHalf (if they're lists) to have
|
// TODO: could we expand the Name's from the EdgeHalf (if they're lists) to have
|
||||||
@@ -908,7 +908,7 @@ 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
|
// 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
|
// 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.
|
||||||
// This interpolate is different It is different from the interpolate found in
|
// This interpolate is different It is different from the interpolate found in
|
||||||
@@ -983,7 +983,7 @@ type StmtIf struct {
|
|||||||
ElseBranch interfaces.Stmt // optional
|
ElseBranch interfaces.Stmt // optional
|
||||||
}
|
}
|
||||||
|
|
||||||
// Interpolate returns a new node (or itself) once it has been expanded. This
|
// 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
|
// 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.
|
||||||
func (obj *StmtIf) Interpolate() (interfaces.Stmt, error) {
|
func (obj *StmtIf) Interpolate() (interfaces.Stmt, error) {
|
||||||
@@ -1152,7 +1152,7 @@ type StmtProg struct {
|
|||||||
Prog []interfaces.Stmt
|
Prog []interfaces.Stmt
|
||||||
}
|
}
|
||||||
|
|
||||||
// Interpolate returns a new node (or itself) once it has been expanded. This
|
// 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
|
// 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.
|
||||||
func (obj *StmtProg) Interpolate() (interfaces.Stmt, error) {
|
func (obj *StmtProg) Interpolate() (interfaces.Stmt, error) {
|
||||||
@@ -1194,6 +1194,24 @@ func (obj *StmtProg) SetScope(scope *interfaces.Scope) error {
|
|||||||
newScope.Variables[bind.Ident] = bind.Value
|
newScope.Variables[bind.Ident] = bind.Value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// now collect any classes
|
||||||
|
// TODO: if we ever allow poly classes, then group in lists by name
|
||||||
|
classes := make(map[string]struct{})
|
||||||
|
for _, x := range obj.Prog {
|
||||||
|
class, ok := x.(*StmtClass)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// check for duplicates *in this scope*
|
||||||
|
if _, exists := classes[class.Name]; exists {
|
||||||
|
return fmt.Errorf("class `%s` already exists in this scope", class.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
classes[class.Name] = struct{}{} // mark as found in scope
|
||||||
|
// add to scope, (overwriting, aka shadowing is ok)
|
||||||
|
newScope.Classes[class.Name] = class
|
||||||
|
}
|
||||||
|
|
||||||
// now set the child scopes (even on bind...)
|
// now set the child scopes (even on bind...)
|
||||||
for _, x := range obj.Prog {
|
for _, x := range obj.Prog {
|
||||||
if err := x.SetScope(newScope); err != nil {
|
if err := x.SetScope(newScope); err != nil {
|
||||||
@@ -1212,6 +1230,11 @@ func (obj *StmtProg) Unify() ([]interfaces.Invariant, error) {
|
|||||||
|
|
||||||
// collect all the invariants of each sub-expression
|
// collect all the invariants of each sub-expression
|
||||||
for _, x := range obj.Prog {
|
for _, x := range obj.Prog {
|
||||||
|
// skip over *StmtClass here
|
||||||
|
if _, ok := x.(*StmtClass); ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
invars, err := x.Unify()
|
invars, err := x.Unify()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -1236,6 +1259,11 @@ func (obj *StmtProg) Graph() (*pgraph.Graph, error) {
|
|||||||
|
|
||||||
// collect all graphs that need to be included
|
// collect all graphs that need to be included
|
||||||
for _, x := range obj.Prog {
|
for _, x := range obj.Prog {
|
||||||
|
// skip over *StmtClass here
|
||||||
|
if _, ok := x.(*StmtClass); ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
g, err := x.Graph()
|
g, err := x.Graph()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -1255,6 +1283,13 @@ func (obj *StmtProg) Output() (*interfaces.Output, error) {
|
|||||||
edges := []*interfaces.Edge{}
|
edges := []*interfaces.Edge{}
|
||||||
|
|
||||||
for _, stmt := range obj.Prog {
|
for _, stmt := range obj.Prog {
|
||||||
|
// skip over *StmtClass here so its Output method can be used...
|
||||||
|
if _, ok := stmt.(*StmtClass); ok {
|
||||||
|
// don't read output from StmtClass, it
|
||||||
|
// gets consumed by StmtInclude instead
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
output, err := stmt.Output()
|
output, err := stmt.Output()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -1271,6 +1306,229 @@ func (obj *StmtProg) Output() (*interfaces.Output, error) {
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StmtClass represents a user defined class. It's effectively a program body
|
||||||
|
// that can optionally take some parameterized inputs.
|
||||||
|
// TODO: We don't currently support defining polymorphic classes (eg: different
|
||||||
|
// signatures for the same class name) but it might be something to consider.
|
||||||
|
type StmtClass struct {
|
||||||
|
Name string
|
||||||
|
Args []*Arg
|
||||||
|
Body interfaces.Stmt // probably a *StmtProg
|
||||||
|
}
|
||||||
|
|
||||||
|
// Interpolate returns a new node (aka a copy) once it has been expanded. This
|
||||||
|
// generally increases the size of the AST when it is used. It calls Interpolate
|
||||||
|
// on any child elements and builds the new node with those new node contents.
|
||||||
|
func (obj *StmtClass) Interpolate() (interfaces.Stmt, error) {
|
||||||
|
interpolated, err := obj.Body.Interpolate()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
args := obj.Args
|
||||||
|
if obj.Args == nil {
|
||||||
|
args = []*Arg{}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &StmtClass{
|
||||||
|
Name: obj.Name,
|
||||||
|
Args: args, // ensure this has length == 0 instead of nil
|
||||||
|
Body: 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 *StmtClass) SetScope(scope *interfaces.Scope) error {
|
||||||
|
return obj.Body.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 *StmtClass) Unify() ([]interfaces.Invariant, error) {
|
||||||
|
if obj.Name == "" {
|
||||||
|
return nil, fmt.Errorf("missing class name")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: do we need to add anything else here because of the obj.Args ?
|
||||||
|
return obj.Body.Unify()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Graph returns the reactive function graph which is expressed by this node. It
|
||||||
|
// includes any vertices produced by this node, and the appropriate edges to any
|
||||||
|
// vertices that are produced by its children. Nodes which fulfill the Expr
|
||||||
|
// interface directly produce vertices (and possible children) where as nodes
|
||||||
|
// that fulfill the Stmt interface do not produces vertices, where as their
|
||||||
|
// children might. This particular func statement adds its linked expression to
|
||||||
|
// the graph.
|
||||||
|
func (obj *StmtClass) Graph() (*pgraph.Graph, error) {
|
||||||
|
return obj.Body.Graph()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output for the class statement produces no output. Any values of interest
|
||||||
|
// come from the use of the include which this binds the statements to. This is
|
||||||
|
// usually called from the parent in StmtProg, but it skips running it so that
|
||||||
|
// it can be called from the StmtInclude Output method.
|
||||||
|
func (obj *StmtClass) Output() (*interfaces.Output, error) {
|
||||||
|
return obj.Body.Output()
|
||||||
|
}
|
||||||
|
|
||||||
|
// StmtInclude causes a user defined class to get used. It's effectively the way
|
||||||
|
// to call a class except that it produces output instead of a value. Most of
|
||||||
|
// the interesting logic for classes happens here or in StmtProg.
|
||||||
|
type StmtInclude struct {
|
||||||
|
class *StmtClass // copy of class that we're using
|
||||||
|
|
||||||
|
Name string
|
||||||
|
Args []interfaces.Expr
|
||||||
|
}
|
||||||
|
|
||||||
|
// Interpolate returns a new node (aka a copy) once it has been expanded. This
|
||||||
|
// generally increases the size of the AST when it is used. It calls Interpolate
|
||||||
|
// on any child elements and builds the new node with those new node contents.
|
||||||
|
func (obj *StmtInclude) Interpolate() (interfaces.Stmt, error) {
|
||||||
|
args := []interfaces.Expr{}
|
||||||
|
if obj.Args != nil {
|
||||||
|
for _, x := range obj.Args {
|
||||||
|
interpolated, err := x.Interpolate()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
args = append(args, interpolated)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &StmtInclude{
|
||||||
|
Name: obj.Name,
|
||||||
|
Args: args,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetScope stores the scope for use in this statement.
|
||||||
|
func (obj *StmtInclude) SetScope(scope *interfaces.Scope) error {
|
||||||
|
if scope == nil {
|
||||||
|
scope = scope.Empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
stmt, exists := scope.Classes[obj.Name]
|
||||||
|
if !exists {
|
||||||
|
return fmt.Errorf("class `%s` does not exist in this scope", obj.Name)
|
||||||
|
}
|
||||||
|
class, ok := stmt.(*StmtClass)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("class scope of `%s` does not contain a class", obj.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// helper function to keep things more logical
|
||||||
|
cp := func(input *StmtClass) (*StmtClass, error) {
|
||||||
|
// TODO: should we have a dedicated copy method instead? because
|
||||||
|
// we want to copy some things, but not others like Expr I think
|
||||||
|
copied, err := input.Interpolate() // this sort of copies things
|
||||||
|
if err != nil {
|
||||||
|
return nil, errwrap.Wrapf(err, "could not copy class")
|
||||||
|
}
|
||||||
|
class, ok := copied.(*StmtClass) // convert it back again
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("copied class named `%s` is not a class", obj.Name)
|
||||||
|
}
|
||||||
|
return class, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
copied, err := cp(class) // copy it for each use of the include
|
||||||
|
if err != nil {
|
||||||
|
return errwrap.Wrapf(err, "could not copy class")
|
||||||
|
}
|
||||||
|
obj.class = copied
|
||||||
|
|
||||||
|
newScope := scope.Copy()
|
||||||
|
for i, arg := range obj.class.Args { // copy
|
||||||
|
newScope.Variables[arg.Name] = obj.Args[i]
|
||||||
|
}
|
||||||
|
if err := obj.class.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 *StmtInclude) Unify() ([]interfaces.Invariant, error) {
|
||||||
|
if obj.Name == "" {
|
||||||
|
return nil, fmt.Errorf("missing include name")
|
||||||
|
}
|
||||||
|
|
||||||
|
// is it even possible for the signatures to match?
|
||||||
|
if len(obj.class.Args) != len(obj.Args) {
|
||||||
|
return nil, fmt.Errorf("class `%s` expected %d args but got %d", obj.Name, len(obj.class.Args), len(obj.Args))
|
||||||
|
}
|
||||||
|
|
||||||
|
var invariants []interfaces.Invariant
|
||||||
|
|
||||||
|
// do this here because we skip doing it in the StmtProg parent
|
||||||
|
invars, err := obj.class.Unify()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
invariants = append(invariants, invars...)
|
||||||
|
|
||||||
|
// collect all the invariants of each sub-expression
|
||||||
|
for i, x := range obj.Args {
|
||||||
|
invars, err := x.Unify()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
invariants = append(invariants, invars...)
|
||||||
|
|
||||||
|
// TODO: are additional invariants required?
|
||||||
|
// add invariants between the args and the class
|
||||||
|
if typ := obj.class.Args[i].Type; typ != nil {
|
||||||
|
invar := &unification.EqualsInvariant{
|
||||||
|
Expr: obj.Args[i],
|
||||||
|
Type: typ, // type of arg
|
||||||
|
}
|
||||||
|
invariants = append(invariants, invar)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return invariants, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Graph returns the reactive function graph which is expressed by this node. It
|
||||||
|
// includes any vertices produced by this node, and the appropriate edges to any
|
||||||
|
// vertices that are produced by its children. Nodes which fulfill the Expr
|
||||||
|
// interface directly produce vertices (and possible children) where as nodes
|
||||||
|
// that fulfill the Stmt interface do not produces vertices, where as their
|
||||||
|
// children might. This particular func statement adds its linked expression to
|
||||||
|
// the graph.
|
||||||
|
func (obj *StmtInclude) Graph() (*pgraph.Graph, error) {
|
||||||
|
graph, err := pgraph.NewGraph("include")
|
||||||
|
if err != nil {
|
||||||
|
return nil, errwrap.Wrapf(err, "could not create graph")
|
||||||
|
}
|
||||||
|
|
||||||
|
g, err := obj.class.Graph()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
graph.AddGraph(g)
|
||||||
|
|
||||||
|
return graph, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output returns the output that this include produces. This output is what
|
||||||
|
// is used to build the output graph. This only exists for statements. The
|
||||||
|
// analogous function for expressions is Value. Those Value functions might get
|
||||||
|
// called by this Output function if they are needed to produce the output. The
|
||||||
|
// ultimate source of this output comes from the previously defined StmtClass
|
||||||
|
// which should be found in our scope.
|
||||||
|
func (obj *StmtInclude) Output() (*interfaces.Output, error) {
|
||||||
|
return obj.class.Output()
|
||||||
|
}
|
||||||
|
|
||||||
// StmtComment is a representation of a comment. It is currently unused. It
|
// 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
|
// 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
|
// that comments can still be part of the AST (for eventual automatic code
|
||||||
@@ -1280,11 +1538,15 @@ type StmtComment struct {
|
|||||||
Value string
|
Value string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Interpolate returns a new node (or itself) once it has been expanded. This
|
// 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
|
// 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.
|
||||||
// Here it simply returns itself, as no interpolation is possible.
|
// Here it simply returns itself, as no interpolation is possible.
|
||||||
func (obj *StmtComment) Interpolate() (interfaces.Stmt, error) { return obj, nil }
|
func (obj *StmtComment) Interpolate() (interfaces.Stmt, error) {
|
||||||
|
return &StmtComment{
|
||||||
|
Value: obj.Value,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
// SetScope does nothing for this struct, because it has no child nodes, and it
|
// SetScope does nothing for this struct, because it has no child nodes, and it
|
||||||
// does not need to know about the parent scope.
|
// does not need to know about the parent scope.
|
||||||
@@ -1324,11 +1586,15 @@ type ExprBool struct {
|
|||||||
// String returns a short representation of this expression.
|
// String returns a short representation of this expression.
|
||||||
func (obj *ExprBool) String() string { return fmt.Sprintf("bool(%t)", obj.V) }
|
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
|
// 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
|
// 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.
|
||||||
// Here it simply returns itself, as no interpolation is possible.
|
// Here it simply returns itself, as no interpolation is possible.
|
||||||
func (obj *ExprBool) Interpolate() (interfaces.Expr, error) { return obj, nil }
|
func (obj *ExprBool) Interpolate() (interfaces.Expr, error) {
|
||||||
|
return &ExprBool{
|
||||||
|
V: obj.V,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
// SetScope does nothing for this struct, because it has no child nodes, and it
|
// SetScope does nothing for this struct, because it has no child nodes, and it
|
||||||
// does not need to know about the parent scope.
|
// does not need to know about the parent scope.
|
||||||
@@ -1405,7 +1671,7 @@ type ExprStr struct {
|
|||||||
// String returns a short representation of this expression.
|
// String returns a short representation of this expression.
|
||||||
func (obj *ExprStr) String() string { return fmt.Sprintf("str(%s)", obj.V) }
|
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
|
// 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
|
// 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.
|
||||||
// Here it attempts to expand the string if there are any internal variables
|
// Here it attempts to expand the string if there are any internal variables
|
||||||
@@ -1424,7 +1690,9 @@ func (obj *ExprStr) Interpolate() (interfaces.Expr, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if result == nil {
|
if result == nil {
|
||||||
return obj, nil // pass self through, no changes
|
return &ExprStr{
|
||||||
|
V: obj.V,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
// we got something, overwrite the existing static str
|
// we got something, overwrite the existing static str
|
||||||
return result, nil // replacement
|
return result, nil // replacement
|
||||||
@@ -1505,11 +1773,15 @@ type ExprInt struct {
|
|||||||
// String returns a short representation of this expression.
|
// String returns a short representation of this expression.
|
||||||
func (obj *ExprInt) String() string { return fmt.Sprintf("int(%d)", obj.V) }
|
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
|
// 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
|
// 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.
|
||||||
// Here it simply returns itself, as no interpolation is possible.
|
// Here it simply returns itself, as no interpolation is possible.
|
||||||
func (obj *ExprInt) Interpolate() (interfaces.Expr, error) { return obj, nil }
|
func (obj *ExprInt) Interpolate() (interfaces.Expr, error) {
|
||||||
|
return &ExprInt{
|
||||||
|
V: obj.V,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
// SetScope does nothing for this struct, because it has no child nodes, and it
|
// SetScope does nothing for this struct, because it has no child nodes, and it
|
||||||
// does not need to know about the parent scope.
|
// does not need to know about the parent scope.
|
||||||
@@ -1588,11 +1860,15 @@ func (obj *ExprFloat) String() string {
|
|||||||
return fmt.Sprintf("float(%g)", obj.V) // TODO: %f instead?
|
return fmt.Sprintf("float(%g)", obj.V) // TODO: %f instead?
|
||||||
}
|
}
|
||||||
|
|
||||||
// Interpolate returns a new node (or itself) once it has been expanded. This
|
// 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
|
// 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.
|
||||||
// Here it simply returns itself, as no interpolation is possible.
|
// Here it simply returns itself, as no interpolation is possible.
|
||||||
func (obj *ExprFloat) Interpolate() (interfaces.Expr, error) { return obj, nil }
|
func (obj *ExprFloat) Interpolate() (interfaces.Expr, error) {
|
||||||
|
return &ExprFloat{
|
||||||
|
V: obj.V,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
// SetScope does nothing for this struct, because it has no child nodes, and it
|
// SetScope does nothing for this struct, because it has no child nodes, and it
|
||||||
// does not need to know about the parent scope.
|
// does not need to know about the parent scope.
|
||||||
@@ -1678,7 +1954,7 @@ func (obj *ExprList) String() string {
|
|||||||
return fmt.Sprintf("list(%s)", strings.Join(s, ", "))
|
return fmt.Sprintf("list(%s)", strings.Join(s, ", "))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Interpolate returns a new node (or itself) once it has been expanded. This
|
// 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
|
// 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.
|
||||||
func (obj *ExprList) Interpolate() (interfaces.Expr, error) {
|
func (obj *ExprList) Interpolate() (interfaces.Expr, error) {
|
||||||
@@ -1923,7 +2199,7 @@ func (obj *ExprMap) String() string {
|
|||||||
return fmt.Sprintf("map(%s)", strings.Join(s, ", "))
|
return fmt.Sprintf("map(%s)", strings.Join(s, ", "))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Interpolate returns a new node (or itself) once it has been expanded. This
|
// 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
|
// 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.
|
||||||
func (obj *ExprMap) Interpolate() (interfaces.Expr, error) {
|
func (obj *ExprMap) Interpolate() (interfaces.Expr, error) {
|
||||||
@@ -2263,7 +2539,7 @@ func (obj *ExprStruct) String() string {
|
|||||||
return fmt.Sprintf("struct(%s)", strings.Join(s, "; "))
|
return fmt.Sprintf("struct(%s)", strings.Join(s, "; "))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Interpolate returns a new node (or itself) once it has been expanded. This
|
// 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
|
// 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.
|
||||||
func (obj *ExprStruct) Interpolate() (interfaces.Expr, error) {
|
func (obj *ExprStruct) Interpolate() (interfaces.Expr, error) {
|
||||||
@@ -2521,11 +2797,15 @@ type ExprFunc struct {
|
|||||||
// we have a better printable function value and put that here instead.
|
// we have a better printable function value and put that here instead.
|
||||||
func (obj *ExprFunc) String() string { return fmt.Sprintf("func(???)") } // TODO: print nicely
|
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
|
// 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
|
// 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.
|
||||||
// Here it simply returns itself, as no interpolation is possible.
|
// Here it simply returns itself, as no interpolation is possible.
|
||||||
func (obj *ExprFunc) Interpolate() (interfaces.Expr, error) { return obj, nil }
|
func (obj *ExprFunc) Interpolate() (interfaces.Expr, error) {
|
||||||
|
return &ExprFunc{
|
||||||
|
V: obj.V,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
// SetScope does nothing for this struct, because it has no child nodes, and it
|
// SetScope does nothing for this struct, because it has no child nodes, and it
|
||||||
// does not need to know about the parent scope.
|
// does not need to know about the parent scope.
|
||||||
@@ -2677,7 +2957,7 @@ func (obj *ExprCall) buildFunc() (interfaces.Func, error) {
|
|||||||
return fn, nil
|
return fn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Interpolate returns a new node (or itself) once it has been expanded. This
|
// 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
|
// 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.
|
||||||
func (obj *ExprCall) Interpolate() (interfaces.Expr, error) {
|
func (obj *ExprCall) Interpolate() (interfaces.Expr, error) {
|
||||||
@@ -3052,12 +3332,16 @@ type ExprVar struct {
|
|||||||
// String returns a short representation of this expression.
|
// String returns a short representation of this expression.
|
||||||
func (obj *ExprVar) String() string { return fmt.Sprintf("var(%s)", obj.Name) }
|
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
|
// 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
|
// 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.
|
||||||
// Here it returns itself, since variable names cannot be interpolated. We don't
|
// Here it returns itself, since variable names cannot be interpolated. We don't
|
||||||
// support variable, variables or anything crazy like that.
|
// support variable, variables or anything crazy like that.
|
||||||
func (obj *ExprVar) Interpolate() (interfaces.Expr, error) { return obj, nil }
|
func (obj *ExprVar) Interpolate() (interfaces.Expr, error) {
|
||||||
|
return &ExprVar{
|
||||||
|
Name: obj.Name,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
// SetScope stores the scope for use in this resource.
|
// SetScope stores the scope for use in this resource.
|
||||||
func (obj *ExprVar) SetScope(scope *interfaces.Scope) error {
|
func (obj *ExprVar) SetScope(scope *interfaces.Scope) error {
|
||||||
@@ -3249,6 +3533,13 @@ func (obj *ExprVar) Value() (types.Value, error) {
|
|||||||
return expr.Value() // recurse
|
return expr.Value() // recurse
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Arg represents a name identifier for a func or class argument declaration and
|
||||||
|
// is sometimes accompanied by a type. This does not satisfy the Expr interface.
|
||||||
|
type Arg struct {
|
||||||
|
Name string
|
||||||
|
Type *types.Type // nil if unspecified (needs to be solved for)
|
||||||
|
}
|
||||||
|
|
||||||
// ExprIf represents an if expression which *must* have both branches, and which
|
// 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,
|
// 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.
|
// which does not need to have both branches, and which does not return a value.
|
||||||
@@ -3265,7 +3556,7 @@ func (obj *ExprIf) String() string {
|
|||||||
return fmt.Sprintf("if(%s)", obj.Condition.String()) // TODO: improve this
|
return fmt.Sprintf("if(%s)", obj.Condition.String()) // TODO: improve this
|
||||||
}
|
}
|
||||||
|
|
||||||
// Interpolate returns a new node (or itself) once it has been expanded. This
|
// 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
|
// 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.
|
||||||
func (obj *ExprIf) Interpolate() (interfaces.Expr, error) {
|
func (obj *ExprIf) Interpolate() (interfaces.Expr, error) {
|
||||||
|
|||||||
Reference in New Issue
Block a user