Execution, Statements and Control Flow

Statements

Inq script controls execution through statements, which have a value and may also have an effect. A statement contains one or more expressions, which include node references, combinations of operators and any of the built-in functions. Having a value as it does, a statement is also an expression. Here are some examples:

// Returns the value at the given path. Has no effect
x.y.z;

// Returns the square root of the value at the given path.
sqrt(x.y.z);

// Has the effect of creating a new variable r.s.t that
// is p raised to the power q. The result is returned
// and is of type double.
any r.s.t = p ^^ q;

// Evaluate the path $this.a.b.c using it to compute the
// path to node z. Convert the result to a boolean and either
// call function foo() or set m.n.o to 3, j.k.l to 6 and
// call bar() passing the argument barval.
// The result is the return value of foo() or bar().
if (x.{$this.a.b.c}.z)
  call foo();
else
{
  m.n.o = 3;
  call bar(barval = j.k.l = 6);
}

A statement is either a single expression, an expression group or a statement group, also known as a block statement. A block statement is a set of statements contained within matched braces { ... }. Statements comprising expression (groups) are delimited by the semi-colon, which on its own is the null statement.

An expression group is any number of expressions combined by operators, as arguments to built-in functions or calls to scripted functions. An expression group is, itself, an expression and the recursive semantics of Inq creates some small grammar ambiguities. The following statement (an example of the Regular Expression matches operator) returns true:

("A shot in the dark" ~~ "^A.*dark$");

The block statement version has to be written like this:

{ ("A shot in the dark" ~~ "^A.*dark$"); }

in order to differentiate the { token from its use in paths of the form x.{$this.a.b.c}.z.

Secondly, numeric constants can be prefixed with the minus sign "-". The construct -3 is parsed to a constant, whereas -a is the unary negation operator applied to the value resolved at the path $stack.a. This gives rise to the following, which rarely cause problems in practice and are included for completeness:

  • 4-3 causes a parse error - a constant followed by another constant is an illegal expression. This can legally be written using white space as 4 - 3 to become an expression of the binary subtraction operator.
  • 4+3 is legal expression which, when evaluated, yields the value 7.
  • a-b is legal and syntactically equivalent to a - b, because identifiers must begin with an alpha character.

Finally, there are similar issues with the multiplication operator * and that symbol's use in a lazy path specification:

  • 4*3 is the expression four times three, and so is 4 * 3.
  • a*b is the lazy path between $stack.a and the first discovered descendant named b, whereas a * b is $stack.a multiplied by $stack.b

An expression group that generates a value creates a new variable. For example, the statement shown above

any r.s.t = p ^^ q;

the value at r.s.t is a new double. The same variable would have resulted from the declaration

double r.s.t;

Block Statements

The result of a block statement is that of the last statement within it that was actually executed. Consider the following:

any vars.cunning = "fox";
any vars.lazy    = "dog";
string s =
{
  if (vars.lazy == "dog")
    renderf("The quick brown {0} jumps over the lazy {1}", vars.cunning, vars.lazy);
  else
    "The " + vars.lazy + " is not a lazy animal";
};

As shown, the string s is initialised to The quick brown fox jumps over the lazy dog. Callable functions have as their body a block statement, so this aspect of the Inq language is most frequently relevant as determining a function's return value.

The values yielded by Inq built-in functions are documented in the function reference [TODO]. Where a function does not have a meaningful value, the null constant is returned.

Conditional Statements

The if Statement

The if statement allows conditional execution of one statement or a conditional choice of two, executing one or the other but not both:

if (<expression>)
  <statement>
[ else <statement> ]

The condition expression is evaluated and converted to the boolean type. If true the associated statement is executed, if false it is not. In this case, if the else clause is present it is executed. The if statement returns the value of the statement it executed or false when no statement is executed.

In common with other languages, where there are cascaded if statements, Inq associates an else clause with the nearest if:

if (a.b)
  if (e.f)
    if (x.y)
      writeln("a.b, e.f and x.y are all true");
    else
      writeln("a.b and e.f are true, while x.y is false");

If the conditions in the above example are not as stated in the writeln statements then this example outputs nothing and the outermost if statement returns false.

The switch Statement

The switch statement allows conditional execution of one and only one statement amongst a choice of many:

switch
{
  when (<expression>) <statement>
  [ when (<expression>) <statement> ...]
  [ otherwise <statement> ]
}

The when clauses are evaluated in order until the first to convert to boolean true, whose statement is then executed. Any subsequent when clauses are ignored.

If an otherwise clause is present and none of the when clauses returned true it is executed. There must be at least one when clause, in which case the switch statement is equivalent to if [else].

Loop Constructs

Inq supports looping with while, do ... while and for statements. In addition, script can loop over the children of a specified node using foreach.

The while Statement

The while construct executes its statement zero or more times based on a condition which is tested prior to each execution. While the condition converts to boolean true execution of the statement repeats. When the condition converts to boolean false execution passes to any statement following.

while (<expression>)
  <statement>

The result is that of the statement, or false if the statement was never executed.

The do Statement

The do...while construct executes its statement one or more times based on a condition which is tested subsequent to each execution. While the condition converts to boolean true execution of the statement repeats. When the condition converts to boolean false execution passes to any statement following.

do
  <statement>
while (<expression>);

The result is that of the statement.

The for Statement

The for construct executes its initial expression. Its statement is executed zero or more times based on its condition which is tested prior to each execution. While the condition converts to boolean true execution of the statement repeats. When the condition converts to boolean false execution passes to any statement following. After each execution the for statement's final expression is executed.

for (<initial-expression> ; <condition-expression> ; <final-expression>)
  <statement>

The result is that of the statement, or false if the statement was never executed.

The foreach Statement

The foreach construct executes its statement once for each child of the specified node:

foreach (<expression> ["," <boolean-literal>])
  <statement>

There is no condition in the conventional sense, rather execution passes to any following statement when the iteration over the specified node's immediate children is complete.

The statement does not execute at all if the expression:

  1. does not resolve;
  2. resolves to a node that does not support children;
  3. resolves to a node that has no children.

During each execution of the statement, $loop resolves to the current child node. Here is an example node space fragment and script:

foreach node space
any swapPosLatest = new(SwapPos);
any tradeMargin = new(xy:SwapPosLatest.Margin, 0);
foreach ($this.vars.tradeList)
{
  // total up the TradeQty...
  swapPosLatest.TradeQty += $loop.Trade.Quantity;

  // ...and the Margin
  tradeMargin += ($loop.Trade.Quantity * $loop.Trade.Price * $loop.Trade.FXRate);
}
Note
This kind of structure is an example of a node set made by Inq when applying typedef non-unique keys. The name of each node set child, denoted in the above diagram as <k>, is the unique key value of the primary (and in this example only) typedef instance contained in the child. We will be looking at node sets when discussing Building Node Structures.

When the statement is executing the following special paths are available

@name
The name of the current child node (returned by $loop) in the loop parent. Referring to the above diagram, the current value of <k>.
@count
The number of times the statement has executed. On the first iteration this value is zero and on the last it is one less than the number of children.
@first
true when the statement is executing for the first time, false for all other executions.
@last
true when the statement is executing for the last time, false for all other executions.

These paths are unavailable when the loop has terminated, whether normally or abnormally (see below). When foreach loops are nested, the paths (and $loop always refer to the enclosing statement and any values relevant to an outer loop are unavailable until the inner one terminates. If they are required by the inner loop statement then they must be explicitly aliased:

foreach(outerNode)
{
  .
  .
  any outerCount = @count;
  any outerChild = $loop;

  foreach($loop.innerNode)
  {
    .
    .  // now we can see @count and $loop from the outer loop
    .  // as outerCount and outerChild
    .
  }
}

Although the prominent type of container node is a map, foreach will iterate over the types set and array also, but @name is unavailable in these cases.

The foreach statement returns true if the loop statement executed at least once (even if this execution was only partial) and false if it never executed.

Removing The Current Iteration

Within the loop body, the current child node can be removed from the iteration set with the removeiter([<boolean-literal>]) statement. If the optional argument is true and the child is capable of raising events then a remove event will be raised on the child.

Concurrent Safe Iteration

If the iteration set must be altered other than by removeiter(), for example if new children must be added, concurrent-safe iteration can be performed by specifying true as foreach's optional second argument. In this case the iteration will proceed across the children present at the start of the loop. Any children added will not be visited. If a child that has not yet been visited is removed the result is undefined.

Abnormal Termination

A loop makes the prescribed number of iterations and its statement executes completely each time unless any of the abortive actions of break, continue or return are taken.

The break Statement

The break statement causes both the loop statement and the loop itself to terminate. There will be no further iterations and execution passes to any following statement. The syntax of break is

break( [<expression>] );

If an expression is specified, its value becomes the return value of the loop, overriding the values of true or false as stated above.

The continue Statement

The continue statement causes the current execution of the loop statement (that is, iteration of the loop) to terminate. For while and do...while the condition is evaluated to determine if the loop itself has terminated naturally and foreach sets its special variables if there are still children remaining. The syntax of continue is

continue;

The break and continue statements are only valid inside a loop statement. Note, however, that this is enforced only at run-time. Should these statements be executed outside of a loop context an exception is generated.

The return Statement

Use of the return statement is not restricted to the context of a loop statement, however it is included here for completeness and to affirm that, in this context, the loop and loop statement are terminated as for break, as well as terminating the enclosing function.

Function Call and Return

A function call has the syntax

"call" <function-name> "(" [ [<argument>] "," <argument> ... ] ")";

<function-name> = [ <name-space>":" ]<identifier>

<name-space> = ( <package-specification>
               | <package-import-alias>
               | "global"
               )

<argument> = ( <identifier> "=" <expression>
             | <path>
             )

An argument using the <path> form assumes that the last path component is also the argument name. Here are some examples:

call foo(arg1 = x.y.z, arg2 = r.s.t);

call bar(a.b.c, arg2 = length(f.g.h));

call from.elsewhere:fred(arg1 = 3, arg2 = call foo(arg1 = x.y.z, t = a));

Execution passes to a function's statement when a call statement is successfully executed. The target function is located prior to evaluating any arguments:

  1. No <name-space> qualifier is present and there is a function of the specified name defined with the local qualifier in the referring parse module.
  2. No <name-space> qualifier is present, there is no local function in the referring parse module however there is a function with the specified name defined in the current parse module or another parse module with the same package.
  3. When <package-specification> or <package-import-alias> is present the referred function is that defined in a parse module with the specified package.
  4. The global name-space qualifier can be used to force case 2, above, when a local function would be preferred otherwise.

It is a run-time error if the target function cannot be resolved. Arguments, when present, are then evaluated and processed.

The result of a call statement is the result of the referred function's statement, which may terminate normally or abnormally, with the use of a return statement.

Function Variables

Any statement (frequently a call statement, but there is no restriction) can be held as a variable of type func or cfunc. Such a variable is initialised with a statement, rather than the value that execution of the statement would produce. The statement can then be passed as an argument in function calls or service requests.

Both func and cfunc have the same declaration form. Here are some examples:

func textCallback = call myFunc(text);

cfunc window.menu.closeMenu.callback = call eventClose();

cfunc block = {
                $this.vars.total = total;
                send rollDate(total);
              };

func and cfunc

The func and cfunc data types differ only in that, when executed, a func statement runs with the context node prevailing when the func variable was defined, whereas a cfunc statement runs in the context the statement was invoked in. The choice is made clear by the usage circumstances. In the discussion below the term func to mean both func and cfunc.

Invoking the Statement

A func's statement executes when invoked from script using an xfunc statement or by built-in functions that can accept (or require) a statement as an argument.

Using xfunc

The xfunc statement has the syntax:

xfunc(<func> [, <call-args> ]);

where <func> is an expression that evaluates to a func variable and <call-args> are as documented for function arguments. If arguments are specified, these are evaluated and placed on a new stack frame. If there are no arguments then no new stack frame is created, see further discussion below.

Control passes to the func's statement, establishing the context in the case of func. The value of xfunc is the value of the func statement.

func Variables as Built-in Function Arguments

When scripted functionality is specified or required for a built-in function it is passed as a func argument. One of the built-ins used to manipulate node structures is the groupby function. Here is an example of its use:

hmap grouped;

groupby(generateFrom,
        cfunc distinctF = $loop.CETrade.Instrument,
        cfunc startF  =
        {
          // Create an empty CETrade, initialise important fields and
          // store under the given name
          any newCETrade = new(xy:CETrade);
          newCETrade.Instrument = $loop.CETrade.Instrument;
          newCETrade.Quantity   = 0;
          newCETrade.Price      = 0;
          any grouped.{@name}.CETrade = newCETrade;

          // Put the Instrument into the grouped set - we need it to display in
          // the generated trades table.
          any grouped.{@name}.Instrument = $loop.Instrument;
        },
        foreach = cfunc foreachF =
        {
          // sum the qty/px within the current group, the px will be weighted
          grouped.{@name}.CETrade.Quantity += $loop.CETrade.Quantity;
          grouped.{@name}.CETrade.Price    += $loop.CETrade.Quantity * $loop.CETrade.Price;
        },
        end = cfunc enfF =
        {
          // weight the price
          grouped.{@name}.CETrade.Price /= grouped.{@name}.CETrade.Quantity;
        });

The groupby function is discussed in the section on building node structures. This example transforms a list of CETrade instances that comprise (at least) the fields Instrument, Quantity and Price. To further explain its operation the arguments are as follows:

  1. The first argument, generateFrom in this example, is the node set the group-by function will be applied to.
  2. A func argument to return the value that defines the distinct-ness for the grouping operation. This example groups the CETrade instances by their Instrument field.
  3. A func argument executed once for every distinct value returned by argument 2 and on its first occurrence. This example allocates a new instance of CETrade, initialises the relevant fields and places it in the output list. The @name symbol evaluates to the current distinct value.
  4. A func that is executed for every child of the input list. The @name symbol evaluates to the current distinct value. The purpose of this example is to sum the Quantity and find an average Price, weighted by the Quantity.
  5. A func that is executed after the input list has been processed, once for each distinct value. The required average is calculated by dividing the weighted total by the weighting total, Quantity.

Statement arguments to built-ins are often seen when the built-in performs some sort of iteration, as in this example. They are executed without arguments, so a new stack frame is not created. In this way, the statements have access to the current local scope, as illustrated in this example where the output list is built on the hmap locally declared as grouped.

Arguments to funcs and Stack Frames

Unlike a defined function, a func's statement has no formal definition of its parameters. When arguments are passed in an xfunc invocation they are placed on a new stack frame. If no arguments are passed, no new stack frame is created and instead the func statement has access to the contents of the current stack.

Arguments are most often passed in an xfunc when the func statement is a call expression:

func callf = call somefunc(arg1, arg2);

Of course, the expressions arg1 and arg2 are the path references $stack.arg1 and $stack.arg2. For these to resolve successfully the invocation of callf would look like this:

xfunc(callf, arg1, arg2);

This pattern of func and xfunc is common when one context is establishing an entry point and providing it for use to another via the variable callf.

However a func's (or cfunc's) statement can be any expression and references to the stack can be satisfied by arguments passed in the xfunc. If no arguments are specified in the xfunc then the expression is assuming that any stack references are resolved by the contents of the current stack frame. This pattern is more common when funcs are passed as arguments to built-in functions, such as the groupby example above. In cases like these there is no explicit xfunc, however the same rules apply. Not creating a new stack frame is especially relevant to groupby because it supports multiple cfunc arguments, where those that are called first typically set up stack-based variables for those called later.