saker.build Documentation TaskDoc JavaDoc

Foreach expression

The foreach expression can be used to iterate over lists and maps, execute other expressions based on their elements, and generate new data based on them. Important to note that the foreach expression is not considered to be a loop, but a substitute of recurring expressions.

The simplest example of a foreach expression:

foreach $item in [1, 2, 3] {
	example.task($item)
}

In the above example we iterate over the list [1, 2, 3] with a loop variable of $item. For every item in the list we execute the example.task() with the item as parameter. The above is semantically the same as the following:

example.task(1)
example.task(2)
example.task(3)

The iterable object must not be a literal expression, other expressions can be used as well:

$mapvariable = {
	Key1: val1,
	Key2: val2,
}
foreach $key, $val in $mapvariable {
	example.task(Key: $key, Value: $val)
}

The foreach expression can be used to iterate over maps as well. In that case two loop variables need to be declared separated by a comma. The first one will be the key of the current map entry and the second will be the associated value. The entries of a map may be iterated over without any specific order.

The above example is semantically equivalent to the following:

example.task(Key: Key1, Value: val1)
example.task(Key: Key2, Value: val2)

Local variables

The foreach expression allows declaring local variables which are private to the scope of the foreach expression:

foreach $item in [1, 2, 3] 
	with $local = $item * 3 {
	example.task($local)
}

The local variables can be declared in a comma separated list after the iterable expression, and optionally specified with an initializer that assigns the value of the variable. If an initializer is not present, the variable may be initialized at most once in the body of the loop.

If the with keyword is included in the expression, at least one local variable declaration must follow. The declarations allow and ignore extraneous commas.

The local variables can be accessed in the same way as the loop variables.

The above example is equivalent to the following when unrolled:

example.task(1 * 3)
example.task(2 * 3)
example.task(3 * 3)

Some other notable examples are the following:

# initialize local in loop body
foreach $item in [1, 2, 3] 
	with $local {
	$local = $item * 3
	# ...
}
# multiple local variables
foreach $item in [1, 2, 3] 
	with $local1 = $item * 3, $local2, $local3 = $item * 11 {
	$local2 = $item * 7
	# ...
}

Expression result

foreach expressions can be specified to produce a result. This can be done by appending the declaration by a result statement as follows:

foreach $item in [1, 2, 3] {
	# ...
} : [ $item * 3 ]

The result declaration is specified by a separating colon (':') character and the result expression. In the above, the result is declared to be a list with the elements muliplied by 3. If the body of the loop is empty, it will be equivalent to the same:

[1 * 3, 2 * 3, 3 * 3]

The body of the foreach may be omitted if it contains no expressions. The following declaration is the same:

foreach $item in [1, 2, 3] : [ $item * 3 ]

The result expression may be only one of the following expression types:

  • []: list, in which case the results will be aggregated in a resulting list
    • This is semantically same as if the addition operator have been used for the declared result lists.
  • {}: map, in which case the results will be aggregated in a map
    • The key-value entries declared in the result will be added to a common result map, and that will be the expression value. If there are recurring keys, the implementation is required to throw an exception.
  • "": compound literal, in which case the results will be concatenated
    • The resulting strings of each iteration will be concatenated after each other.

Some notable examples of using result expressions:

# results in [3, 6, 9]
foreach $item in [1, 2, 3] : [ $item * 3 ]
# same with local variable
foreach $item in [1, 2, 3] with $local = $item * 3 : [ $local ]

# results in { K1: 3, K2: 6 }
foreach $k, $v in { K1: 1, K2: 2 } : { $k: $v * 3 }
# results in { K1: 1, K2: 2, K3: 3 }
foreach $item in [1, 2, 3] : { "K{ $item }": $item }

# results in "3,6,9,"
foreach $item in [1, 2, 3] : "{ $item * 3 },"

# results in "xxx"
# the result expression is not required to include loop variables
foreach $item in [1, 2, 3] : "x"

# results can be used like any other expression
$variable = foreach $item in [1, 2, 3] : "x"

Important to note that while both the body and the result expression may be omitted, not both can be at the same time. foreach expressions without a body and a result declaration is erroneous.

Nesting loops

foreach loops may be nested in each other. When nested loops are declared, the names of loop and local variables may not collide, and they may not shadow each other. Any such declaration is erroneous.

foreach $item in [1, 2] 
	with $local = $item * 3 {
	foreach $inneritem in [4, 5] 
		with $innerlocal = $local + $inneritem {
		example.task($innerlocal)
	}
}

All loop and local variables declared in an enclosing loop are visible for inner foreach expressions. The above example is semantically the same as the following:

example.task(1 * 3 + 4)
example.task(1 * 3 + 5)

example.task(2 * 3 + 4)
example.task(2 * 3 + 5)

Accessing foreach variables

Both the loop and local variables can be accessed by using the $ dereference operator. It has similarities with the variables, with imposes a strict requirement for the access declarations.

The foreach variables cannot be accessed via dereferencing a complex expression. If you have a foreach variable named var, then the accessing syntax must be $var. Any other format will access the variable in the enclosing build target scope. Some examples for this:

# a variable with the name $item in the build target scope
$item = # ...
foreach $item in [1, 2, 3]
	with $local {
	# accesses the loop variable
	$item 
	# accesses the $item variable in the build target scope
	$"item"
	
	# accesses and assigns the local variable
	$local = # ...
	# accesses the variable $local in the build target scope
	$"loc{ al }"
	
	foreach $inneritem in [4, 5] 
		with $inlocal {
		# accesses the outer loop variable
		$item
		# accesses the inner loop variable
		$inneritem
		# accesses the $item variable in the build target scope
		$"item"
		
		# accesses and assigns the local variable for the inner loop
		$inlocal = # ...
		# accesses the local variable of the outer loop
		$local
		# accesses the variable $local in the build target scope
		$"local"
	}
}