Flow Control
Constructs to direct the flow of program execution.
Conditional branching
The basic if statement works as expected, but the condition is not enclosed in ()
. There can only be a conditional expression following the if
, so parsing of the condition stops when a newline is encountered or a comment.
All the if
options in one example.
if true // is it true?
conditional_code
else if false // or is it false?
never_get_here
else // or is it neither?
other_code
No assignment is permitted inside the
if
conditional expression.
Alternate / inline if
:
if true // conventional if statement
...
else
...
true ? ... : ... // ternary operator
true ? ... // ternary without 'else'
Allow in
keyword in if
statement?
arr := (1, 2, 3, 4, 5)
x := 2
if x in arr // can x be found in arr?
... // true
Parenthesis
When parenthesis are used they are always part of the expression, not of the if
statement. There is always a space after the if
statement - this is what sets it apart from a function call.
if (42 = 42) // if statement (+ space)
...
if(42) // function call (no space)
Note that if
is a reserved keyword and no named language element can have the same name as a reserved keyword. Maybe have an escape character?
Range Conditions
a := 42
if a in [0..100] // true
...
a := 42
if a not in [0..100] // false
if not a in [0..100] // ?
...
If Expressions
TBD
Use the if
keyword inside an expression.
a := 42
// newline/indent syntax does not work for expressions
v := if a > 42 -> 101 else a
v := a > 42 ? 101 : a // same
// more complex expressions?
v := if a > 42 -> 101 else if a < 10 -> 0 else a * a
Return
To return a value from a function.
Return Expressions
TBD
The idea here is that the last expression value in the function body is automatically the return (type and) value. See this used in functional languages, but also Rust.
fn1: (): Bool
true
fn2: (p: U8): Bool
p = 42
fn3: (): Bool
fn1()
Yield
todo
Co-routines.
Loops
A loop is an essential control mechanism in directing the execution flow of the code. There is only one keyword for making a loop: loop
.
The simplest form is an endless loop:
loop
endless_loop
A ‘while loop’ just adds a condition to the statement:
loop false
never_get_here
loop true
endless_loop
A do-until loop is not supported but can be easily constructed. A do-while loop has to be converted to a do-until loop (invert the condition).
a :=^ 42 // mutable
loop
// a = 42, 41, 40, 39 ... 3, 2, 1
a -= 1
// until condition
if a =< 0 -> break
Loop a certain number of times:
loop [0..10]
loop_10_times
A ‘for’ or ‘for-each’ loop is constructed using a Range
.
// n is mutable - but it's not explicit!
loop n in [0..10]
for_n_is_0_to_9
Separate the number of times to loop from the variable value used in the code?
loop
cannot be used with a range condition (as used in if). No way to tell the difference. Or do we need a different syntax for iteration?
// explicit to differ from range-condition
loop n in [0..10].GetEnumerator()
foreach_n_0_to_9
// or range condition in () ?
loop (n in [0..10])
n -= 1
Loop a number of times
loop 42
do_this_42_times
c := 42
loop c
do_this_42_times // c is available (value=42)
What happens when c is modified inside the loop?
Loop or else
TBD: Most cases can probably be refactored into simpler code..?
x := 0
loop n in [0..x]
... // empty loop
else
log("Nothing to see here...")
Reverse loop
loop n in -[0..5]
n_from_4_to_0
// or using step?
loop n in [0..5:-1]
...
TBD
Loop with more than one range/iter? How to control if ‘ranges’ are repeated or first one done ends the loop?
loop w in -[0..5], h in [0..10]
w_makes_two_rounds_and_h_one
w_makes_one_round_and_h_half
loop w in -[0..5] and h in [0..10]
w_makes_two_rounds_and_h_one
loop w in -[0..5] or h in [0..10]
w_makes_one_round_and_h_half
TBD
Access index during in-loop.
// n = value
// i = index
loop (n, i) in [0..5]
// n = i
Nested loops
loop h in [0..10]
loop w in [0..5]
w_times_h
Loop with lambda?
loop i in [0..10]
(i) -> log("Now at {i}.`n")
// short syntax?
loop [0..10] (i) -> log("Now at {i}.`n")
Loop with Function?
loop i in [0..10]
LogInt(i)
// short syntax?
loop [0..10] -> LogInt
Custom Iterator function (.NET Enumerable/Enumerator)
// iterator function
loop n in Iter()
work_with_n
TBD implicit casting?
// iterator function
loop n: OtherType in Iter()
work_with_n_of_OtherType
TBD Async loops?
// async iterator function
loop n in IterAsync()
work_with_n_awaited
TBD loops with CancellationTokens?
ct: CancellationToken = ...
// iterator function
loop n in Iter() and ct?
work_with_n
When a type has the ?
operator overloaded it can be used as a boolean expression by adding the ?
to the end.
Loop Expressions
loop // endless loop
loop <integer> // loop count
loop <var> in [-]<range> // (reverse) for-loop
loop <var> in <iter> // for-each loop
(loop <expression>) // access loop object
(loop <expression>).AsParallel() // call loop object function
Expressions can be combined using the and
and or
keywords.
The and
keyword requires both sides to finish. How to control if the shorter/faster side is to be restarted?
loop n in [0..3] and s in [2..9]
...
The ‘or’ keyword allows the fastest side to end the loop.
x := 101
loop n in [0..x] or 42 // 42 is the max loop count here
...
TODO
Parallel Loops
Only loops with function bodies (.NET: TPL and PLINQ). The inline loop-body could be compiled into a function.
// what operator? (implies AsParallel)
loop [0..10] ->> LogInt
// (static) partition function
loop [0..10].Partition(3) -> LogInt
loop [0..10].Partition(3) ->> LogInt
// AsParallel function (.NET)
loop [0..10].AsParallel() -> LogInt
Cancelling parallel processing (CancellationToken)?
c = CancellationTokenSource
loop IterObjects().WithCancellation(c.Token) ->> LogInt
loop IterObjects() and c.Token? ->> LogInt
Async Loops
// auto-detect (implicit await) based on enumerator type?
loop n in IterAsync()
...
c = CancellationTokenSource
loop n in IterAsync().WithCancellation(c.Token)
loop n in IterAsync() and c.Token?
...
Cycle<T> Type
An array of values that are cycled through each time it is read.
Cycle<T>
is a self-restarting iterator (enumerator).
l := (1, 2, 3, 4, 5)
c := Cycle(l)
c := Cycle(l, 3) // max 3 cycles (?)
m := c // 1
n := c // 2
o := c // 3
p := c // 4
q := c // 5
r := c // 1
s := c // 2
t := c // 3
For use in loops typically.
l := (1, 2, 3, 4, 5)
c := Cycle(l)
loop i in [0..10] or n in c
// i = 0, 1, 2, 3, 4, 5, 6, 7, 8, 9
// n = 1, 2, 3, 4, 5, 1, 2, 3, 4, 5
Of course a Cycle
can be constructed with a range.
Break
It is possible to break out of the execution of a loop at any time with the break
keyword.
Perhaps call it
leave
instead ofbreak
as it sounds less dreadful. Orexit loop
andexit if
andexit fn
?up
(as in up one scope/indent)?
Here’s an example of an endless loop that uses a conditional branch if
to break out of the loop:
loop
do_stuff_here
if true
break // this will exit the loop
The break
keyword only works on its immediate parent loop. In the case of nested loops, it will look like this:
loop // loop #1
do_stuff_here
loop // loop #2
do_other_stuff_here
if true
break // this will exit loop #2
Let
break
break out of a scope in general - not a loop specifically. This would requireif
statement scopes to be ignored?
c :=^ 0
[c] // capture scope
x := c * 42
if x = 0
break
// not executed when x = 0
// back to root scope
c = 42
Continue
The opposite of break
, the continue
keyword will allow you to skip one iteration of the loop:
loop
if true
continue // this will skip 'do_stuff_here'
do_stuff_here
As with break
, continue
only works on the immediate parent loop.
Exit
TBD: use
exit
as a keyword to replacebreak
,continue
,yield
andreturn
(and abort program)?
exit() // exits program
exit(fn) // exits function (return)
exit(co) // exits coroutine (yield)
exit(iter) // exits current iteration (continue)
exit(loop) // exits loop (break)
exit(scope) // exits current scope (1 up)
exit(err) // throw exception
fn
, co
, iter
etc.. are enum values.
fn: (p: U8): U8
if p == 0 -> exit() // abort program
c: U8
loop n in [0..p]
if n = 42 -> exit(iter) // continue
if n = 101 -> exit(loop) // break
c += 1
exit(fn) c // return c
Downside is that this does not work with nested loops for instance.
use
leave
instead ofexit
? break => escape? continue => skip?
TBD: a way to continue or break a specific outer loop in case of nested loops - or - specifying
exit()
with a variable / symbol name that identifies the instance of what to exit
loopFn: (p: U8)
#lp1: loop n in [0..p] // compile time label the loop?
loop i in [0..9]
if n + i = 42 -> exit(n) // continue
if n + i = 101 -> exit(lp1) // break outer
// alternate syntax - no labels required
if n + i = 101 -> exit(loop n) // break outer
if n = 42
exit(loopFn) // return from function
if n = 101
exit(Error("Too large")) // throw exception
TBD: some sort of syntax (operators?) that would allow for ‘conditional arithmetic’ in order to reduce branches as an optimization.
Does the .NET compiler (or runtime) already do branchless optimization?
// instead of
greater: (a: I8, b: I8): I8
if a > b
return a;
else
return b;
// branchless (plain syntax)
greater: (a: I8, b: I8): I8
return a * I8(a > b) + b * I8(b >= a)
// what syntax?
// or let our compiler do an auto tranform?
greater: (a: I8, b: I8): I8
return (a > b) -> a, (b >= a) -> b
// match?
greater: (a: I8, b: I8): I8
return match (a, b)
(a > b) -> a
(b >= a) -> b
_ -> 0 // default case if needed