cwal

s2: Flow Control
Login

s2: Flow Control

(⬑Table of Contents)

The Flow-Control Keywords

Jump to...

But what if...

s2 supports conventional if/else constructs:

if( condition ) { body }
else if( otherCondition ) { body }
else { body }

The braces are optional for single-expression bodies, but such expressions always require a trailing semicolon:

if( condition ) a=b, c=d;
else a=d, c=b;

The scope created by an if/else block begins at the first parenthesis and ends at the end of the final block in the group. This means variables declared in a (condition) part are in scope throughout the remaining of the if/else group:

if(!var x = getSomeValue()){ … }
else {
 s2out << 'getSomeValue() says:' << x << '\n';
}

Because heredocs are internally very closely related to script blocks, they can be used as bodies for if/else (as well as loops):

if(true)<<<_FI s2out<<'if in heredoc\n' _FI
/* minor achtung: a heredoc in this context works like a
   {script block}, so a semicolon after the heredoc ends
   the 'if' block, causing a syntax error when the following
   'else' is subsequently seen outside of an if/else block: */
else <<<_ESLE s2out<<'else in heredoc\n' _ESLE;
/* OTOH, a semicolon at the end of the if/else construct
   is legal, but an EOL also works like a semicolon in
   that particular context. */

An if/else block, like everything else in s2, constitutes an expressionmisref. It evaluates to true if any of the if(condition) parts evaluates to a truthy value, otherwise it evaluates to false. e.g. (if(0) 1;else 2)===false.

doing a loop for a while...

The for, do, and while keywords offer conventional loops using the same syntax as in most other languages. Each loop starts a new scope as follows:

Examples:

var i;
for( i = 0; i < 10; ++i) { … } // or…
for( var i = 0; (var x = i) <10; i+=2) { … }
// ^^^ *loop-local* i, *iteration-local* x

var x = 10;
while( x > 0 ) { … x -= 1; }
while( … ) { var y = ….; } // *iteration-local* y

Note that the structure of do/while loops effectively makes any variables defined either in the body or the while(...) part iteration-local. Vars defined in the body of do/while loop are visible in the while(condition) part, but not the other way around.

Note that the body part of the loop need not be contained in {squiggly braces} if it is a single expression, but such expressions require a terminating semicolon or EOF(-like construct). An empty body, represented by either {} (an empty block) or simply a semicolon, is legal for all three loop types, but avoiding endless loops is the scripter's responsibility.

As an exception to the semicolons rule (made for reasons of usability and conventions conformance), for/while loops which have a {braced body} will sometimes treat a newline at the end of their body block as an implicit semicolon. Those with an unbraced single-expression body require a semicolon like any other expression, as do those which are the RHS of a large expression. Some examples of when semicolons are required and not:

for( var x = 0; x < 10; ++x );
// empty body is legal[^26] ^^^, but semicolon required

for( var x = 0; x < 10; ++x ){}
// newline acts as implicit   ^^^ optional EOX here (solely for conventions purposes)

var i = while(…) {
 … if(…) break x; …
}; // ← semicolon is required here to terminate the LHS var decl expression

Like the loop constructs, each if/else block has its own scope, also starting at the first parenthesis1. Thus variables declared inside them are not visible once the scope has exited (and have, unless propagated out, been destroyed by that point).

foreach() Loops

(Added 20160227. String iteration support added 20180620.)

s2 has historically not had a for-each loop construct because (A) there are various types of containers and it wasn't clear how/whether to consolidate them into one construct and (B) Object.eachProperty(), Array.eachIndex(), Hash.eachEntry(), and Enum.eachEnumEntry() all did that job. Adding a loop construct which does those jobs simplifies client code notably in some places, though, and is more efficient because it doesn't require creating and calling a callback function. It also provides an in-language approach which can be used when s2 is run without those prototype methods.

foreach allows one to iterate over:

... all with the same interface. Arrays and Hashes can hold both properties and indexed resp. hash entries, and this keyword lets the caller specify (in the form of a syntactical flag) which of those data sources to iterate over.

Basic syntax:

foreach( container|string => key ) EXPR | {block}
foreach( container|string => key, val ) EXPR | {block}

Where container is the value being iterated over and key/val are identifiers of the user's choice. It throws an exception if all of typeinfo(iscontainer container), typeinfo(istuple container), and typeinfo(isstring container) are false. Unlike for/while/do loops, foreach does not permit an empty loop expression/body (because it would be useless, whereas empty traditional loop bodies are useful on rare occasion).

foreach iterates over any properties belonging to the given container (not including hidden properties nor those inherited via its prototypes) or (for strings) the characters of a string, defining the iteration-local variables key resp. val to the key resp. value of the property (or character), e.g.:

foreach( container => k, v ) print(k, '=', v);
foreach( container => k ) print(k, '=', container[k]);

// during iteration, fetching properties works, but assigning them is prohibited:
assert 'CWAL_RC_IS_VISITING'===catch{
 foreach( container => k ) container[k] = 1
}.codeString();

typeinfo(mayiterate EXPR) may be used to determine beforehand if iteration is (currently) legal.

foreach throws if its operand is not legal.

The body may use the continue and break keywords, exactly as with for/do/while loops. Like such loops, foreach evaluates to the undefined value unless a value is passed to break within the loop, in which case that value is the result of the foreach loop. Any empty input set never iterates, and thus cannot trigger a break, thus its result is always the undefined value.

String iteration iterates over each character in the input string, with each character provided to the loop body as a length-one string. foreach is much more efficient (faster) than using, e.g. for(var i = 0, n=aString.#; i<n; ++i) {...aString[i]...}, because that approach has to (for non-ASCII input) re-traverse the whole string on each indexed access, whereas foreach must iterate over each character only once. Examples of iterating over strings:

foreach( "abc" => val ) assert 1 === val.#;
foreach( "abc" => index, val ) {
  assert typeinfo(isinteger index);
  assert 1 === val.#;
}

Array entries, which are distinct from its key/value properties, can be iterated over by prepending the container expression with an @-sign:

foreach( @myArray => val ) EXPR | {block}
foreach( @myArray => index, val ) EXPR | {block}

Note, however, that the key/val part is interpreted slightly differently than object properties: if only one identifier is provided, it refers to the value at the current index, and if both are provided then both the current array index and the value at that index (in that order) are exposed in the loop.

Trivia: if the C-level Array contains any NULL entries, script code receives those as the undefined value (which is also a legal script-level value, i.e. undefined does not mean that the entry in question really is a NULL pointer - it might have been assigned the undefined value). (More trivia: prior to 20160225, NULL entries were skipped over, but that was likely an artifact intended to keep th1ish running (which is no longer a concern).)

Likewise, hash entries can be iterated over by using a #-sign prefix:

foreach( #myHash => key ) EXPR | {block}
foreach( #myHash => key, val ) EXPR | {block}

The @- and #-forms throw if the expression does not evaluate to a value which has an list resp. hash somewhere in its prototype chain.

Tuples, though similar to arrays, have no properties which can be iterated over, so @ is not needed to disambiguate. To simplify script code, however, the @ is optionally permitted:

foreach( myTuple => val ) ...; // identical to:
foreach( @myTuple => val ) ...;
foreach( myTuple => index, val ) ...; // identical to:
foreach( @myTuple => index, val ) ...;

Enums are a special case: enums internally use hashes for their property storage but the # modifier is ignored. When iterating over enum entries, only the "forward mappings" of their string keys to unique values is iterated over:

foreach( enum {a,b,c} => k ) print(k); // outputs 3 lines: a, b, and c
foreach( enum {a,b,c} => k,v ) print(k,v); // outputs 3 lines: a <something>, b …

The "reverse" (unique-values-to-names) mappings are not iterated over.

Keep in mind that the values exposed by the loop for enums are the unique identities of the enum entries, not their associated values (if any). Getting at their values requires one more level of indirection:

foreach( enum {a:1,b:2,c:3} => k,v ) print(k, v, v.value);
// Outputs something like:
a unique@0x55910999a070 1
b unique@0x559109999fd0 2
c unique@0x559109999f30 3

See the enum page for more information.

Note that containers may not be modified during iteration (a limitation of cwal's internal property storage mechanism). Doing so is legal for list types, but the Object/Hash property model cannot support modification during iteration (and an exception is thrown if it is attempted). In practice this has never proven to be problematic.

Let's continue our break...

For each of the above-described loop types, the continue keyword stops the current iteration and skips to the next iteration (if any). The break keyword ends the loop, optionally with a result value.

Examples:

assert 3 === for(var i = 0; i < 2; ++i) break 3;
assert undefined === while(true) break; // no value (===undefined)
assert 2 === do{ break 2 } while(true);

Footnotes


  1. ^ Corner case bug: we re-use one scope for all (if/else/else if) condition blocks and the one body/expr which gets run, so we cannot declare the same var in multiple conditions. Low-prio to-fix someday. We could simply clear the scope before each condition after the first, but the current semantics would seem to have more practical use.
  2. ^ Misreference
  3. [^ 26 ]
    Be careful to avoid infinite loops.