lang: Allow a list of strings in the resource name

This adds a core looping construct by allowing a list of names to build
a resource. They'll all have the same parameters, but they'll
intelligently add the correct list of edges that they'd individually
create.

Constructs like these are one reason we do NOT have actual looping
functionality in the language, and it should stay that way.
This commit is contained in:
James Shubin
2019-01-12 11:18:51 -05:00
parent 7f1477b26d
commit 10dcf32f3c
4 changed files with 99 additions and 20 deletions

View File

@@ -262,6 +262,24 @@ 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 partial struct types are currently not supported in the language. Patches are
welcome if you'd like to add this tricky feature! welcome if you'd like to add this tricky feature!
##### Resource naming
Each resource must have a unique name of type `str` that is used to uniquely
identify that resource, and can be used in the functioning of the resource at
that resources discretion. For example, the `file` resource uses the unique name
value to specify the path.
Alternatively, the name value may be a list of strings `[]str` to build a list
of resources, each with a name from that list. When this is done, each resource
will use the same set of parameters. The list of internal edges specified in the
same resource block is created intelligently to have the appropriate edge for
each separate resource.
Using this construct is a veiled form of looping (iteration). This technique is
one of many ways you can perform iterative tasks that you might have
traditionally used a `for` loop for instead. This is preferred, because flow
control is error-prone and can make for less readable code.
##### Internal edges ##### Internal edges
Resources may also declare edges internally. The edges may point to or from Resources may also declare edges internally. The edges may point to or from

View File

@@ -0,0 +1,12 @@
Edge: list(str(hey)) -> var(names) # names
Edge: str(hello) -> list(str(hello), str(world)) # 0
Edge: str(hey) -> list(str(hey)) # 0
Edge: str(world) -> list(str(hello), str(world)) # 1
Vertex: list()
Vertex: list(str(hello), str(world))
Vertex: list(str(hey))
Vertex: str(hello)
Vertex: str(hey)
Vertex: str(name)
Vertex: str(world)
Vertex: var(names)

View File

@@ -0,0 +1,13 @@
# this is an empty list of test resources, iow test resources
# this must pass type unification
test [] {}
# single resource
test "name" {}
# single resource, defined by list variable
$names = ["hey",]
test $names {}
# multiples resources, defined by list
test ["hello", "world",] {}

View File

@@ -150,10 +150,12 @@ func (obj *StmtBind) Output() (*interfaces.Output, error) {
return interfaces.EmptyOutput(), nil return interfaces.EmptyOutput(), nil
} }
// StmtRes is a representation of a resource and possibly some edges. // StmtRes is a representation of a resource and possibly some edges. The `Name`
// TODO: consider expanding Name (if it's a list) to have this return a list of // value can be a single string or a list of strings. The former will produce a
// Res's in the Output function. Alternatively, it could be a map[name]struct{}, // single resource, the latter produces a list of resources. Using this list
// or even a map[[]name]struct{}. // mechanism is a safe alternative to traditional flow control like `for` loops.
// TODO: Consider expanding Name to have this return a list of Res's in the
// Output function if it is a map[name]struct{}, or even a map[[]name]struct{}.
type StmtRes struct { type StmtRes struct {
data *interfaces.Data data *interfaces.Data
@@ -250,11 +252,24 @@ func (obj *StmtRes) Unify() ([]interfaces.Invariant, error) {
} }
invariants = append(invariants, invars...) invariants = append(invariants, invars...)
// name must be a string // name must be a string or a list
invar := &unification.EqualsInvariant{ ors := []interfaces.Invariant{}
invarStr := &unification.EqualsInvariant{
Expr: obj.Name, Expr: obj.Name,
Type: types.TypeStr, Type: types.TypeStr,
} }
ors = append(ors, invarStr)
invarListStr := &unification.EqualsInvariant{
Expr: obj.Name,
Type: types.NewType("[]str"),
}
ors = append(ors, invarListStr)
invar := &unification.ExclusiveInvariant{
Invariants: ors, // one and only one of these should be true
}
invariants = append(invariants, invar) invariants = append(invariants, invar)
// collect all the invariants of each field and edge // collect all the invariants of each field and edge
@@ -311,27 +326,48 @@ func (obj *StmtRes) Output() (*interfaces.Output, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
// TODO: test for []str instead, and loop
name := nameValue.Str() // must not panic
res, err := obj.resource(name) names := []string{} // list of names to build
if err != nil { switch {
return nil, errwrap.Wrapf(err, "error building resource") case types.TypeStr.Cmp(nameValue.Type()) == nil:
name := nameValue.Str() // must not panic
names = append(names, name)
case types.NewType("[]str").Cmp(nameValue.Type()) == nil:
for _, x := range nameValue.List() { // must not panic
name := x.Str() // must not panic
names = append(names, name)
}
default:
// programming error
return nil, fmt.Errorf("unhandled resource name type: %+v", nameValue.Type())
} }
edges, err := obj.edges(name) resources := []engine.Res{}
if err != nil { edges := []*interfaces.Edge{}
return nil, errwrap.Wrapf(err, "error building edges") for _, name := range names {
} res, err := obj.resource(name)
if err != nil {
return nil, errwrap.Wrapf(err, "error building resource")
}
metaparams, err := obj.metaparams() edgeList, err := obj.edges(name)
if err != nil { if err != nil {
return nil, errwrap.Wrapf(err, "error building meta params") return nil, errwrap.Wrapf(err, "error building edges")
}
edges = append(edges, edgeList...)
metaparams, err := obj.metaparams()
if err != nil {
return nil, errwrap.Wrapf(err, "error building meta params")
}
res.SetMetaParams(metaparams)
resources = append(resources, res)
} }
res.SetMetaParams(metaparams)
return &interfaces.Output{ return &interfaces.Output{
Resources: []engine.Res{res}, Resources: resources,
Edges: edges, Edges: edges,
}, nil }, nil
} }