Skip to content

Commit

Permalink
lang: ast: The res and edge names should not use exclusives
Browse files Browse the repository at this point in the history
This removes the exclusive from the res names and edge names. We now
require that the names should be lists of strings, however they can
still be single strings if that can be determined statically.
Programmers should explicitly wrap their variables in a string by
interpolation to force this, or in square brackets to force a list. The
former is generally preferable because it generates a small function
graph since it doesn't need to build a list.
  • Loading branch information
purpleidea committed Apr 18, 2024
1 parent dc45c90 commit 51cf1e2
Show file tree
Hide file tree
Showing 175 changed files with 500 additions and 378 deletions.
30 changes: 30 additions & 0 deletions docs/language-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,14 @@ 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.

The single `str` variation, may only be used when it is possible for the
compiler to determine statically that the value is of that type. Otherwise, it
will assume it to be a list of strings. Programmers should explicitly wrap their
variables in a string by interpolation to force this static `str` determination,
or in square brackets to force a list. The former is generally preferable
because it generates a smaller function graph since it doesn't need to build a
list.

##### Internal edges

Resources may also declare edges internally. The edges may point to or from
Expand Down Expand Up @@ -337,6 +345,28 @@ to express a relationship between three resources. The first character in the
resource kind must be capitalized so that the parser can't ascertain
unambiguously that we are referring to a dependency relationship.

##### Edge naming

Each edge must have a unique name of type `str` that is used to uniquely
identify that edge, and can be used in the functioning of the edge at its
discretion.

Alternatively, the name value may be a list of strings `[]str` to build a list
of edges, each with a name from that list.

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.

The single `str` variation, may only be used when it is possible for the
compiler to determine statically that the value is of that type. Otherwise, it
will assume it to be a list of strings. Programmers should explicitly wrap their
variables in a string by interpolation to force this static `str` determination,
or in square brackets to force a list. The former is generally preferable
because it generates a smaller function graph since it doesn't need to build a
list.

#### Class

A class is a grouping structure that bind's a list of statements to a name in
Expand Down
104 changes: 53 additions & 51 deletions lang/ast/structs.go
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,10 @@ func (obj *StmtBind) Output(map[interfaces.Func]types.Value) (*interfaces.Output
// 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.
// The `Name` value can only be a single string when it can be detected
// statically. Otherwise, it is assumed that a list of strings should be
// expected. More mechanisms to determine if the value is static may be added
// over time.
// 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 {
Expand Down Expand Up @@ -576,38 +580,34 @@ func (obj *StmtRes) Unify() ([]interfaces.Invariant, error) {
}
invariants = append(invariants, invars...)

invarStr := &interfaces.EqualsInvariant{
Expr: obj.Name,
Type: types.TypeStr,
}

// Optimization: If we know it's an str, no need for exclusives!
// TODO: Check other cases, like if it's a function call, and we know it
// can only return a single string. (Eg: fmt.printf for example.)
isString := false
if _, ok := obj.Name.(*ExprStr); ok {
invariants = append(invariants, invarStr)
return invariants, nil
}

invarListStr := &interfaces.EqualsInvariant{
Expr: obj.Name,
Type: types.TypeListStr,
// It's a string! (A plain string was specified.)
isString = true
}

// Optimization: If we know it's a []str, no need for exclusives!
if expr, ok := obj.Name.(*ExprList); ok {
typ, err := expr.Type()
if err == nil && typ.Cmp(types.TypeListStr) == nil {
invariants = append(invariants, invarListStr)
return invariants, nil
if typ, err := obj.Name.Type(); err == nil {
// It has type of string! (Might be an interpolation specified.)
if typ.Cmp(types.TypeStr) == nil {
isString = true
}
}
if isString {
invar := &interfaces.EqualsInvariant{
Expr: obj.Name,
Type: types.TypeStr,
}
invariants = append(invariants, invar)

// name must be a string or a list
ors := []interfaces.Invariant{}
ors = append(ors, invarStr)
ors = append(ors, invarListStr)
return invariants, nil
}

invar := &interfaces.ExclusiveInvariant{
Invariants: ors, // one and only one of these should be true
// Down here, we only allow []str, no need for exclusives!
invar := &interfaces.EqualsInvariant{
Expr: obj.Name,
Type: types.TypeListStr,
}
invariants = append(invariants, invar)

Expand Down Expand Up @@ -2379,7 +2379,13 @@ func (obj *StmtEdge) Output(table map[interfaces.Func]types.Value) (*interfaces.
}

// StmtEdgeHalf represents half of an edge in the parsed edge representation.
// This does not satisfy the Stmt interface.
// This does not satisfy the Stmt interface. The `Name` value can be a single
// string or a list of strings. The former will produce a single edge half, the
// latter produces a list of resources. Using this list mechanism is a safe
// alternative to traditional flow control like `for` loops. The `Name` value
// can only be a single string when it can be detected statically. Otherwise, it
// is assumed that a list of strings should be expected. More mechanisms to
// determine if the value is static may be added over time.
type StmtEdgeHalf struct {
Kind string // kind of resource, eg: pkg, file, svc, etc...
Name interfaces.Expr // unique name for the res of this kind
Expand Down Expand Up @@ -2488,38 +2494,34 @@ func (obj *StmtEdgeHalf) Unify() ([]interfaces.Invariant, error) {
}
invariants = append(invariants, invars...)

invarStr := &interfaces.EqualsInvariant{
Expr: obj.Name,
Type: types.TypeStr,
}

// Optimization: If we know it's an str, no need for exclusives!
// TODO: Check other cases, like if it's a function call, and we know it
// can only return a single string. (Eg: fmt.printf for example.)
isString := false
if _, ok := obj.Name.(*ExprStr); ok {
invariants = append(invariants, invarStr)
return invariants, nil
}

invarListStr := &interfaces.EqualsInvariant{
Expr: obj.Name,
Type: types.TypeListStr,
// It's a string! (A plain string was specified.)
isString = true
}

// Optimization: If we know it's a []str, no need for exclusives!
if expr, ok := obj.Name.(*ExprList); ok {
typ, err := expr.Type()
if err == nil && typ.Cmp(types.TypeListStr) == nil {
invariants = append(invariants, invarListStr)
return invariants, nil
if typ, err := obj.Name.Type(); err == nil {
// It has type of string! (Might be an interpolation specified.)
if typ.Cmp(types.TypeStr) == nil {
isString = true
}
}
if isString {
invar := &interfaces.EqualsInvariant{
Expr: obj.Name,
Type: types.TypeStr,
}
invariants = append(invariants, invar)

// name must be a string or a list
ors := []interfaces.Invariant{}
ors = append(ors, invarStr)
ors = append(ors, invarListStr)
return invariants, nil
}

invar := &interfaces.ExclusiveInvariant{
Invariants: ors, // one and only one of these should be true
// Down here, we only allow []str, no need for exclusives!
invar := &interfaces.EqualsInvariant{
Expr: obj.Name,
Type: types.TypeListStr,
}
invariants = append(invariants, invar)

Expand Down
32 changes: 16 additions & 16 deletions lang/core/embedded/provisioner/main.mcl
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ class base($config) {
panic($prefix == "") # panic if prefix is empty
panic(not strings.has_suffix($prefix, "/"))

file $prefix { # dir
file "${prefix}" { # dir
state => $const.res.file.state.exists,
}
$tftp_prefix = "${prefix}${tftp_suffix}"
Expand All @@ -85,7 +85,7 @@ class base($config) {
#
# network
#
net $interface {
net "${interface}" {
state => $const.res.net.state.up,
addrs => [$router,], # has cidr suffix
#gateway => "192.168.42.1", # TODO: get upstream public gateway with new function
Expand Down Expand Up @@ -130,10 +130,10 @@ class base($config) {
}
}

file $tftp_prefix { # dir
file "${tftp_prefix}" { # dir
state => $const.res.file.state.exists,
}
file $uefi_prefix { # dir
file "${uefi_prefix}" { # dir
state => $const.res.file.state.exists,
}

Expand All @@ -155,7 +155,7 @@ class base($config) {

# XXX: should this also be part of repo too?
class tftp_root_file($f) {
#tftp:file $f { # without root slash
#tftp:file "${f}" { # without root slash
tftp:file "/${f}" { # with root slash
path => $syslinux_root + $f, # TODO: add autoedges

Expand Down Expand Up @@ -185,7 +185,7 @@ class base($config) {
#
# http
#
file $http_prefix { # dir
file "${http_prefix}" { # dir
state => $const.res.file.state.exists,
}

Expand All @@ -195,7 +195,7 @@ class base($config) {
}

$kickstart_http_prefix = "${http_prefix}${kickstart_suffix}"
file $kickstart_http_prefix {
file "${kickstart_http_prefix}" {
state => $const.res.file.state.exists,
#source => "", # this default means empty directory
recurse => true,
Expand Down Expand Up @@ -237,21 +237,21 @@ class base:repo($config) {
$distroarch_release_http_prefix = "${distroarch_http_prefix}release/"
$distroarch_updates_http_prefix = "${distroarch_http_prefix}updates/"

file $distroarch_tftp_prefix { # dir
file "${distroarch_tftp_prefix}" { # dir
state => $const.res.file.state.exists,

#Meta:quiet => true, # TODO
}
file $distroarch_uefi_prefix { # dir
file "${distroarch_uefi_prefix}" { # dir
state => $const.res.file.state.exists,
}
file $distroarch_http_prefix { # root http dir
file "${distroarch_http_prefix}" { # root http dir
state => $const.res.file.state.exists,
}
file $distroarch_release_http_prefix {
file "${distroarch_release_http_prefix}" {
state => $const.res.file.state.exists,
}
file $distroarch_updates_http_prefix {
file "${distroarch_updates_http_prefix}" {
state => $const.res.file.state.exists,
}

Expand All @@ -261,7 +261,7 @@ class base:repo($config) {
$uefi_download_dir = "${distroarch_uefi_prefix}download/"
$uefi_extract_dir = "${distroarch_uefi_prefix}extract/"

file $uefi_extract_dir { # mkdir
file "${uefi_extract_dir}" { # mkdir
state => $const.res.file.state.exists,

Depend => Exec["uefi-download-${uid}"],
Expand Down Expand Up @@ -609,7 +609,7 @@ class base:host($name, $config) {
}

$kickstart_file = "${kickstart_http_prefix}${hkey}.ks"
file $kickstart_file {
file "${kickstart_file}" {
state => $const.res.file.state.exists,
content => template(deploy.readfile("/files/kickstart.ks.tmpl"), $http_kickstart_template),
}
Expand All @@ -631,11 +631,11 @@ class base:host($name, $config) {
#kv "${name}" {
# key => $provision_key,
#}
#value $provision_key {
#value "${provision_key}" {
# #any => true, # bool
#}
#Http:Flag["${name}"].value -> Kv["${name}"].value
#Http:Flag["${name}"].value -> Value[$provision_key].any
#Http:Flag["${name}"].value -> Value["${provision_key}"].any
##$st_provisioned = value.get_bool($provision_key)
#$st_provisioned = value.get_str($provision_key)
#$provisioned = $st_provisioned->ready and $st_provisioned->value == "true" # export this value to parent scope
Expand Down
4 changes: 2 additions & 2 deletions lang/interpret_test/TestAstFunc1/changing-func.txtar
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ $fn2 = funcgen(false)
$out1 = $fn1()
$out2 = $fn2()

test $out1 {}
test $out2 {}
test "${out1}" {}
test "${out2}" {}
-- OUTPUT --
Edge: FuncValue -> call # fn
Edge: FuncValue -> call # fn
Expand Down
4 changes: 3 additions & 1 deletion lang/interpret_test/TestAstFunc1/classes-polyfuncs.txtar
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ import "fmt"

include c1([13, 42, 0, -37,])
class c1($b) {
test fmt.printf("len is: %d", len($b)) {} # len is 4
test [fmt.printf("len is: %d", len($b)),] {} # len is 4
}
-- OUTPUT --
Edge: FuncValue -> call # fn
Edge: FuncValue -> call # fn
Edge: call -> composite # 0
Edge: const -> composite # 0
Edge: const -> composite # 1
Edge: const -> composite # 2
Expand All @@ -17,6 +18,7 @@ Vertex: FuncValue
Vertex: call
Vertex: call
Vertex: composite
Vertex: composite
Vertex: const
Vertex: const
Vertex: const
Expand Down
8 changes: 7 additions & 1 deletion lang/interpret_test/TestAstFunc1/doubleclass.txtar
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ $some_value1 = 42 # or something more complex like the output of a slow function
class foo($num) {
# we should have a different `$inside` value for each use of this class
$inside = $some_value1 + $some_value2 + 4
test fmt.printf("test-%d-%d", $num, $inside) {} # some resource
test [fmt.printf("test-%d-%d", $num, $inside),] {} # some resource
}
$some_value2 = 13 # check that non-ordering works too!

Expand All @@ -24,6 +24,9 @@ Edge: FuncValue -> call # fn
Edge: FuncValue -> call # fn
Edge: FuncValue -> call # fn
Edge: FuncValue -> call # fn
Edge: call -> composite # 0
Edge: call -> composite # 0
Edge: call -> composite # 0
Vertex: FuncValue
Vertex: FuncValue
Vertex: FuncValue
Expand All @@ -42,6 +45,9 @@ Vertex: call
Vertex: call
Vertex: call
Vertex: call
Vertex: composite
Vertex: composite
Vertex: composite
Vertex: const
Vertex: const
Vertex: const
Expand Down
2 changes: 1 addition & 1 deletion lang/interpret_test/TestAstFunc1/doubleinclude.txtar
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
include c1("t1")
include c1("t2")
class c1($a) {
test $a {
test "${a}" {
stringptr => $foo,
}
}
Expand Down
4 changes: 2 additions & 2 deletions lang/interpret_test/TestAstFunc1/efficient-lambda.txtar
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ $prefixer = func($x) {
$out1 = $prefixer("a")
$out2 = $prefixer("b")

test $out1 {} # helloa
test $out2 {} # hellob
test "${out1}" {} # helloa
test "${out2}" {} # hellob
-- OUTPUT --
Edge: FuncValue -> call # fn
Edge: FuncValue -> call # fn
Expand Down

0 comments on commit 51cf1e2

Please sign in to comment.