High-level structure

Certain statements are only permitted within certain structures, in order to defined a consistent and intuitive control flow.

To provide more intuitive error messages, the reference grammar is more liberal about where various statements may occur. The following restrictions must be enforced; an implementation may enforce them in the parser directly.

Top-level statements

Following module pragmas, the top-level may contain:

Function-level statements

Within a function body but outside a loop, the following are valid:

Note: a func statement at the function level is a nested function definition.

Iteration control

Within a loop, the following are valid:

Within a loop

  1. Starting at an iteration control statement, identify the enclosing block.

  2. If it is a for statement, stop, the iteration control is within a loop.

  3. If it is a func statement, func expression or the top-level, stop, the iteration control is not within a loop.

  4. If it some other body, a try, catch, switch, if or else body, continue outwards to the enclosing block and repeat from 2.

Special blocks

Control flow and scope

Module-level control flow is defined in that section.

While Tenet is a functional language, there is a logical control flow that governs what is in scope and where failure can occur. Statements are classified by how they affect the names in scope.

Implementations may alter control flow if they can guarantee it produces the same results.

Let statements

A let statement is a declarative statement that first evaluates its right-hand side.

Augmented assignment may fail at this point, e.g. let x //= 0.

Next, lensing operators, if any, are applied to the value of the left-hand side, and lensing of ? tag or [index] may fail.

Next, destructuring is applied, but destructuring can never fail at run-time.

Next, the name, or names if destructuring, are updated with the new values.

Finally, control passes to the next statement.

If a failure is detected at any point, the names are not defined nor updated, and control is handled by a surrounding try block.

Function definition

A func statement may occur at the top level or within another function. Control flow behaves the same.

Logically, a function definition is equivalent to the assignment of a lambda expression.

func normal_definition(a, b, c) {
    ...
}

def by_assignment := func(a, b, c) {
    ...
}

When the function is defined

A function definition is a declarative statement that assigns a function value to a name. A function definition is a total operation. If the definition references names in its containing scope, a closure is constructed at the point the function is defined with those values.

The closure will contain a copy of those values; modifying a name closed over at a later point will not affect the closure’s value.

Control then passes to the next statement.

Upon application

Upon the application of a function, the formal arguments are defined and assigned the values in the application.

Control flow begins at the beginning of the function body, and steps through the statements until it reaches a return statement. The application is then complete, and flow returns to the expression evaluation.

Return statements

A return statement is a control statement that first evaluates its argument. If this fails, control must be handled by a surrounding try block.

Otherwise, the function terminates and returns the value specified in the argument. All local variables and formal arguments are now out of scope.

A return statement must terminate all control paths. No statement may logically follow a return statement.

If statements

The if statement begins an if / else if / else chain. It is a control statement. It steps through the conditionals in order, then enters a body, and exits the entire chain, and passes to the next statement sequentially.

All conditionals must evaluate to a Bool value. The body that will be entered is the first one whose conditional evaluates to true, or the else body if none do. If the else branch is missing, control passes immediately to the next statement.

Only names defined in the executed body are passed on to the next scope.

Switch statements

A switch statement is a transient statement. It first evaluates a the value to be switched over. If this fails, control is handled by a surrounding try block.

Otherwise, the switch statement considers each case in turn. Pattern matching will never raise a failure.

If a case pattern is found that matches, or the default clause is encountered, assignments to any pattern variables are made. These assignments never fail.

Control enters the first statement after the case / default statement, and proceeds until another case / default statement or the close of the switch block. There is no fall through.

The pattern variables are then out of scope, and control resumes after the close of the switch body.

Try statements

A try / catch chain is a control statement. It evaluates the statements in the body of the try statement.

If there is no failure, control passes to the statement following the last catch block.

If there is a failure, control exits statements containing the failure and exits the try block. It then passes to the first catch block that specifies the failure type.

If no catch block matches the failure, control must exit to a surrounding try block. Note that it is not possible for a failure to exit a function entirely; any failure must be handled by a try statement.

Upon completion of the catch block, control passes to the statement following the last catch block.

For statements

A for loop is a transient statement. It first evaluates an expression returning an iterable container value. If this fails, control is handled by a surrounding try block.

The loop then begins to iterate through the iterable. If the iterable is empty, control passes immediately to the statement following the for loop.

Otherwise, the next value of the iterable is assigned to the iterator name, and control is passed to the body of the for loop.

The body may be exited in one of five ways:

Upon exiting the body, the iterator name goes out of scope. If the iteration continues, it will be redefined and assigned the next value.

Pass statements

The pass statement should be removed from control flow entirely, alternatively, control must pass directly to the next statement or out of its surrounding block.

A pass is a total statement and will never transfer control to a catch block.