Control flow¶
Ü has some constructions for control flow.
Conditions¶
if
operator allows to execute some code part conditionally.
The condition expression inside if
operator must be of bool
type.
if( x > y )
{
++x;
}
It’s possible to specify alternative action if condition in if
is false:
if( x > y )
{
++x;
}
else
{
++y;
}
It’s also possible to specify several conditions and actions for them:
if( x > y )
{
++x;
}
else if( y > x )
{
++y;
}
else if( z )
{
return;
}
else
{
++x;
++y;
}
Static conditions¶
There is a special form of the conditional operator - static_if
.
It accepts only constexpr
conditions.
The branch which will be executed is determined entirely in compile-time based on provided condition(s).
Branches that should not be executed are skipped by the compiler entirely.
static_if( typeinfo</size_type/>.size_of == 8s )
{
return 0; // Either this branch will be compiled
}
else static_if( typeinfo</size_type/>.size_of == 4s )
{
return 1; // Or this
}
else
{
// In strange cases even this branch may be compiled
static_assert(false); // but compilation will fail because of this assert
halt;
}
Conditions combining¶
Different conditional operators - if
, static_if
and also if_coro_advance may be combined together in any way.
After else
of any of these operators may follow any other.
An usage of any conditional operator after else
is equivalent to nesting of the second conditional operator inside a block after else
of the first operator.
For example this code:
static_if( static_condition )
{
Action0();
}
else if( dynamic_condition )
{
Action1();
}
else if_coro_advance( x : some_gen )
{
Action2(x);
}
else
{
Action3();
}
Is equivalent to this code:
static_if( static_condition )
{
Action0();
}
else
{
if( dynamic_condition )
{
Action1()
}
else
{
if_coro_advance( x : some_gen )
{
Action2(x);
}
else
{
Action3();
}
}
}
switch operator¶
Ü has switch
operator, which allows to transfer control flow to a block depending on a value of some variable.
An example of simple switch
:
switch(x)
{
0 -> { return -1; },
1 -> {},
2 -> { halt; },
// other handles following
}
This operator works only with values of integer and enum types.
Values to compare must be constexpr
.
switch
operator allows to specify several values for single block of code - via comma.
It’s also possible to specify value ranges. Control flow will be transferred to a block if a value is greater or equal to minimum range value and less or equal to maximum range value. It’s allowed to skip specifying lower/upper range values, in such cases minimum/maximum value of the type will be used.
For not listed values default
handler may be used.
It may not be the last, it is only important to have no more than one default handler.
switch( x )
{
33, ... -7, 66 ... 78, 999 ... -> { return 777; }, // value 33, range [-int_max; -7], range [66; 78], range [999; int_max]
96 ... 108, 80 -> { return 888; }, // range [96; 108], value 80
82, 200 ... 300 -> { return 999; }, // value 82, range [200; 300]
default -> { return 1000; }, // all other values outside values/ranges listed above
}
The compiler checks that switch
operator handles all possible cases.
If this is not true - an error will be produced.
enum E{ A, B, C }
fn Foo( E e )
{
switch( e )
{
E::A -> {},
E::B -> {},
// error - E::C is not handled
}
}
If a default
handler exists it is assumed that it handles all values not listed explicitly.
But if default
is unnecessary, the compiler will produce an error.
fn Foo( u8 x )
{
switch( x )
{
0u8 -> {},
1u8 -> {},
2u8 ... -> {}, // Handles all values from 2 to the maximum
default -> {}, // error - this handler is unreachable
}
}
while loop¶
while
operator allows to repeat some operations until the condition is true.
The condition should be of bool
type.
while( x > 0 )
{
--x;
}
It’s possible to break from a loop early with usage of break
operator:
while( x > 0 )
{
x /= 5;
if( x == 1 )
{
break;
}
}
It’s also possible to continue to the next loop operation with usage of continue
operator:
while( x > 0 )
{
x /= 3;
if( x == 5 )
{
continue;
}
--x;
}
while
loop (and other loop kinds) may have a label (with usage of label
keyword) and use it in break
and continue
operators.
This allows to continue to the next iteration of outer loop or break from it.
while( Cond0() ) label outer
{
while( Cond1() )
{
if( Cond2() )
{
continue label outer; // continue outer loop
}
if( Cond3() )
{
break label outer; // break from outer loop
}
}
}
for loop¶
There is also for
loop in Ü, that is similar to such loop in C++.
It consists of three parts - variables declaration part, condition part, iteration part.
Parts are separated by ;
.
Each part is optional.
If no condition is specified a loop is considered to be unconditional - ends only with break
or return
.
for
loop allows to perform some actions always at the and of any iteration, each continue
operator will jump to the iteration part.
for
loop examples:
auto mut x= 0;
for( auto mut i= 0; i < 10; ++i ) // Declare a variable via "auto"
{
x+= i * i;
}
auto mut x= 0;
for( var i32 mut i= 0, mut j= 2; i < 5; ++i, j*= 2 ) // Declare variables via "var", more that one action in iteration part
{
x+= i * j;
}
for( auto mut i = 1; ; i <<= 1u ) // Unconditional loop
{
if( i < 0 ){ break; }
}
for( ; ; ) // A loop without any elements
{
break;
}
for( var u32 mut x= 0u; x < 100u; ++x )
{
if( SomeFunc(x) ){ continue; } // After "continue" "++x" will be executed
SomeFunc2(x);
}
simple loop¶
Ü has simple unconditional loop - loop
It is (almost) equivalent to the while
loop with always true condition.
The only way to end this loop is to use break
, return
or continue
to some other outer loop.
There is a reason to use it in cases when a loop end/continue condition may be calculated only inside the body of the loop.
loop
{
// Some code
if( SomeCondition() )
{
break;
}
}
It’s important to know that if loop
has no break
(for this loop) any code after this loop is considered to be unreachable.
loop
{
if( SomeCondition() )
{
return;
}
}
auto x = 0; // The compiler will produce here an error, because this code is unreachable.
return from function¶
The execution of a function that returns no value ends when control flow reaches the end of the function.
But if it necessary to end it earlier, return
operator may be used.
fn Clamp( i32 &mut x )
{
if( x >= 0 )
{
return;
}
x= 0;
}
Functions that return a value should always end with return
with a value.
A type of return
expression must be the same as function return type (or convertible to it).
fn Add( i32 x, i32 y ) : i32
{
return x + y;
}
The compiler ensures that a function returns always. Otherwise an error will be generated.
fn Clamp( i32 &mut x ) : bool
{
if( x >= 0 )
{
return false;
}
x= 0;
// Error, function returns not always.
}
fn Clamp( i32 &mut x ) : bool
{
if( x >= 0 )
{
return false;
}
else
{
x= 0;
return true;
}
// All ok - function always returns.
}