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:
@@ -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
|
||||
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
|
||||
|
||||
Resources may also declare edges internally. The edges may point to or from
|
||||
|
||||
12
lang/interpret_test/TestAstFunc1/empty-res-list-0.graph
Normal file
12
lang/interpret_test/TestAstFunc1/empty-res-list-0.graph
Normal 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)
|
||||
13
lang/interpret_test/TestAstFunc1/empty-res-list-0/main.mcl
Normal file
13
lang/interpret_test/TestAstFunc1/empty-res-list-0/main.mcl
Normal 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",] {}
|
||||
@@ -150,10 +150,12 @@ func (obj *StmtBind) Output() (*interfaces.Output, error) {
|
||||
return interfaces.EmptyOutput(), nil
|
||||
}
|
||||
|
||||
// StmtRes is a representation of a resource and possibly some edges.
|
||||
// TODO: consider expanding Name (if it's a list) to have this return a list of
|
||||
// Res's in the Output function. Alternatively, it could be a map[name]struct{},
|
||||
// or even a map[[]name]struct{}.
|
||||
// StmtRes is a representation of a resource and possibly some edges. The `Name`
|
||||
// value can be a single string or a list of strings. The former will produce a
|
||||
// single resource, the latter produces a list of resources. Using this list
|
||||
// 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 {
|
||||
data *interfaces.Data
|
||||
|
||||
@@ -250,11 +252,24 @@ func (obj *StmtRes) Unify() ([]interfaces.Invariant, error) {
|
||||
}
|
||||
invariants = append(invariants, invars...)
|
||||
|
||||
// name must be a string
|
||||
invar := &unification.EqualsInvariant{
|
||||
// name must be a string or a list
|
||||
ors := []interfaces.Invariant{}
|
||||
|
||||
invarStr := &unification.EqualsInvariant{
|
||||
Expr: obj.Name,
|
||||
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)
|
||||
|
||||
// collect all the invariants of each field and edge
|
||||
@@ -311,27 +326,48 @@ func (obj *StmtRes) Output() (*interfaces.Output, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// TODO: test for []str instead, and loop
|
||||
name := nameValue.Str() // must not panic
|
||||
|
||||
names := []string{} // list of names to build
|
||||
switch {
|
||||
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())
|
||||
}
|
||||
|
||||
resources := []engine.Res{}
|
||||
edges := []*interfaces.Edge{}
|
||||
for _, name := range names {
|
||||
res, err := obj.resource(name)
|
||||
if err != nil {
|
||||
return nil, errwrap.Wrapf(err, "error building resource")
|
||||
}
|
||||
|
||||
edges, err := obj.edges(name)
|
||||
edgeList, err := obj.edges(name)
|
||||
if err != nil {
|
||||
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)
|
||||
}
|
||||
|
||||
return &interfaces.Output{
|
||||
Resources: []engine.Res{res},
|
||||
Resources: resources,
|
||||
Edges: edges,
|
||||
}, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user