4.6 Flow control statements

Program execution flow refers to the order in which program statements are run in. By default, the next statement to run after the current one is the next statement physically located after the current one.
Example:

a = b + c
printf(1, "The result of adding %d and %d is %d", {b,c,a})

In that example, b is added to c, assigning the result to a, and then the information is displayed on the screen using the printf statement.

However, there are many times in which the order of execution needs to be different from the default order, to get the job done. Euphoria has a number of flow control statements that you can use to arrange the execution order of statements.

A set of statements that are run in their order of appearance is called a block. Blocks are good ways to organize code in easily identifiable chunks. However it can be desirable to leave a block before reaching the end, or slightly alter the default course of execution.

The following flow control keywords are available.

break retry entry exit continue return goto end

4.6.1 exit statement

Exiting a loop is done with the keyword exit. This causes flow to immediately leave the current loop and recommence with the first statement after the end of the loop.

for i = a to b do
    c = i
    if doSomething(i) = 0 then
        exit -- Stop executing code inside the 'for' block.
    end if
end for

-- Flow restarts here.
if c = a then ...

But sometimes you need to leave a block that encloses the current one. Euphoria has two methods available for you to do this. The safest method, in terms of future maintenance, is to name the block you want to exit from and use that name on the exit statement. The other method is to use a number on the exit statement that refers to the depth that you want to exit from.

A block's name is always a string literal and only a string literal. You cannot use a variable that contains the block's name on an exit statement. The name comes after the label keyword, just before the do keyword.
Example:

integer b
  b = 0
  for i = 1 to 20 label "main" do
    for j = 1 to 20 do
      b  += i + j
      ? {i, j, b}
      if b > 50 then
        b  = 0
        exit "main"
      end if
    end for
  end for
  ? b

The output from this is ...

{1, 1,  2}
{1, 2,  5}
{1, 3,  9}
{1, 4, 14}
{1, 5, 20}
{1, 6, 27}
{1, 7, 35}
{1, 8, 44}
{1, 9, 54}
0

The exit "main" causes execution flow to leave the for block named main.

The same thing could be achieved using the exit N format...

integer b
  b = 0
for i = 1 to 20 do
    for j = 1 to 20 do
        b  += i + j
        ? {i, j, b}
        if b > 50 then
            b  = 0
            exit 2 -- exit 2 levels of depth
        end if
    end for
end for

? b

But using this method means you have to take more care when changing the program so that if you change the depth, you also need to change the exit statement.

Note:
A special form of exit N is exit 0. This leaves all levels of loop, regardless of the depth. Control continues after the outermost loop block. Likewise, exit -1 exits the second outermost loop, and so on.
For easier and safer program maintenance, the explicit label form is to be preferred. Other forms are variously sensitive to changes in the program organization. Yet, they may prove more convenient in short, short lived programs, and are provided mostly for this purpose.

For information on how to associate a string to a block of code, see the section Header Labels.

An exit without any label or number in a while statement or a for statement causes immediate termination of that loop, with control passing to the first statement after the loop.
Example:

for i = 1 to 100 do
    if a[i] = x then
        location = i
        exit
    end if
end for

It is also quite common to see something like this:

constant TRUE = 1

while TRUE do
    ...
    if some_condition then
        exit
    end if
    ...
end while

i.e. an "infinite" while-loop that actually terminates via an exit statement at some arbitrary point in the body of the loop.

Performance Note:
Euphoria optimizes this type of loop. At run-time, no test is performed at the top of the loop. There's just a simple unconditional jump from end while back to the first statement inside the loop.

4.6.2 break statement

Works exactly like the exit statement, but applies to if statements or switch statements rather than to loop statements of any kind. Example:

if s[1] = 'E' then
    a = 3
    if s[2] = 'u' then
        b = 1
        if s[3] = 'p' then
            break 0 -- leave topmost if block
        end if
        a = 2
    else
        b = 4
    end if
else
    a = 0
    b = 0
end if

This code results in:

  • "Dur" -> a=0 b=0
  • "Exe" -> a=3 b=4
  • "Eux" -> a=2 b=1
  • "Eup" -> a=3 b=1

The same optional parameters can be used with the break statement as with the exit statement, but of course apply to if and switch blocks only, instead of loops.

4.6.3 continue statement

Likewise, skipping the rest of an iteration in a single code block is done using a single keyword, continue. The continue statement continues execution of the loop it applies to by going to the next iteration now. Going to the next iteration means testing a condition (for while and loop constructs, or changing the for construct variable index and checking whether it is still within bounds.

for i = 3 to 6 do
    ? i
    if i = 4 then
        puts(1,"(2)\n")
        continue
    end if
    ? i * i
end for

This will print 3, 9, 4, (2), 5 25, 6 36.

integer b
  b = 0
for i = 1 to 20 label "main" do
    for j = 1 to 20 do
        b  += i + j
        if b > 50 then
            printf(1, "%d ", b)
            b  = 0
            continue "main"
        end if
    end for
end for

? b

The same optional parameters that can be used in an exit statement can apply to a continue statement.

4.6.4 retry statement

The retry statement retries executing the current iteration of the loop it applies to. The statement branches to the first statement of the designated loop, without testing anything nor incrementing the for loop index.

Normally, a sub-block which contains a retry statement also contains another flow control keyword, since otherwise the iteration would be endlessly executed.

errors = 0
for i = 1 to length(files_to_open) do
    fh = open(files_to_open[i], "rb")
    if fh=-1 then
        if errors > 5 then
            exit
        else
            errors += 1
            retry
        end if
    end if
    file_handles[i] = fh
end for

Since retry does not change the value of i and tries again opening the same file, there has to be a way to break from the loop, which the exit statement provides.

The same optional parameters that can be used in an exit statement can apply to a retry statement.

4.6.5 with entry statement

It is often the case that the first iteration of a loop is somehow special. Some things have to be done before the loop starts--they are done before the statement starting the loop. Now, the problem is that, just as often, some things do not need to, or should not, be done at this initialization stage. The entry keyword is an alternative to setting flags relentlessly and forgetting to update them. Just add the entry keyword at the point you wish the first iteration starts.

public function find_all(object x, sequence source, integer from)
    sequence ret = {}

    while from > 0 with entry do
        ret &= from
        from += 1
    entry
        from = find_from(x, source, from)
    end while

    return ret
end function

Instead of performing an initial test, which may crash because from has not been assigned a value yet, the first iteration jumps at the point where from is being computed. The following iterations are normal. To emphasize the fact that the first iteration is not normal, the entry clause must be added to the loop header, after the condition.

The entry statement is not supported for for loops, because they have a more rigid nature structure than while or loop constructs.

Note on infinite loops.
With eui.exe or eui, control-c will always stop your program immediately, but with the euiw.exe that has not produced any console output, you will have to use the Windows process monitor to end the application.

4.6.6 goto statement

goto instructs the computer to resume code execution at a place which does not follow the statement. The place to resume execution is called the target of the statement. It is restricted to lie in the current routine, or the current file if outside any routine.

Syntax is:

goto "label string"

The target of a goto statement can be any accessible label statement:

label "label string"

Label names must be double quoted constant strings. Characters that would be illegal in an Euphoria identifier may appear in a label name, since it is a regular string.

Header Labels do not count as possible goto targets.

Use goto in production code when all the following applies:

  • you want to proceed with a statement which is not the following one;
  • the various structured constructs wouldn't do, or very awkwardly;
  • you contemplate a significant gain in speed/reliability from such a direct move;
  • the code flow remains understandable for an outsider nevertheless.

During early development, it may be nice to have while the code is not firmly structured. But most instances of goto should melt into structured constructs as soon as possible as code matures. You may find out that modifying a program that has goto statements is usually trickier than if it had not had them.

The following may be situations where goto can help:

  • A routine has several return statements, and some processing must be done before returning, no matter from where. It may be clearer to goto a single return point and perform the processing only at this point.
  • An exit statement in a loop corresponds to an early exit, and the normal processing that immediately follows the loop is not relevant. Replacing an exit statement followed by various flag testing by a single goto can help.

Explicit label names will tremendously help maintenance. Remember that there is no limit to their contents.

goto-ing into a scope (like an if block, a for loop,...) will just do that. Some variables may be defined only in that scope, and they may or may not have sensible values. It is up to the programmer to take appropriate action in this respect.

4.6.7 Header Labels

As shown in the above section on control flow statements, most can have their own label. To label a flow control statement, use a label clause immediately preceding the flow control's terminator keyword (then / do).

A label clause consists of the keyword label followed by a string literal. The string is the label name.

Examples:

if n=0 label "an_if_block" then
    ...
end if

while TRUE label "a_while_block" do
    ...
end while

loop label "a_loop_block" do
    ...
   until TRUE
end loop

switch x label "a_switch_block" do
   ...
end switch

Note: If a flow control statement has both an entry clause and a label clause, the entry clause must come before the label clause:

while 1 label "top" with entry do -- WRONG

while 1 with entry label "top" do -- CORRECT