routine or constant name search

4.4 Branching Statements

4.4.1 if statement

An if statement tests a condition to see whether it is true or false, and then depending on the result of that test, executes the appropriate set of statements.

The syntax of if is

IFSTMT  ==:  IFTEST [ ELSIF ...] [ELSE] ENDIF
 IFTEST  ==:  if ATOMEXPR [ LABEL ] then [ STMTBLOCK ]
 ELSIF   ==:  elsif ATOMEXPR then [ STMTBLOCK ]
 ELSE    ==:  else [ STMTBLOCK ]
 ENDIF   ==:  end if

Description of syntax

  • An if statement consists of the keyword if, followed by an expression that evaluates to an atom, optionally followed by a label clause, followed by the keyword then. Next is a set of zero or more statements. This is followed by zero or more elsif clauses. Next is an optional else clause and finally there is the keyword end followed by the keyword if.
  • An elsif clause consists of the key word elsif, followed by an expression that evaluates to an atom, followed by the keyword then. Next is a set of zero or more statements.
  • An else clause consists of the keyword else followed by a set of zero or more statements.

In Euphoria, false is represented by an atom whose value is zero and true is represented by an atom that has any non-zero value.

  • When an expression being tested is true, Euphoria executes the statements immediately following the then keyword after the expression, up to the corresponding elsif or else, whichever comes next, then skips down to the corresponding end if.
  • When an expression is false, Euphoria skips over any statements until it comes to the next corresponding elsif or else, whichever comes next. If this is an elsif then its expression is tested otherwise any statements following the else are executed.

For example:

if a < b then
    x = 1
end if

if a = 9 and find(0, s) then
    x = 4
    y = 5
else
    z = 8
end if

if char = 'a' then
    x = 1
elsif char = 'b' or char = 'B' then
    x = 2
elsif char = 'c' then
    x = 3
else
    x = -1
end if

Notice that elsif is a contraction of else if, but it's cleaner because it does not require an end if to go with it. There is just one end if for the entire if statement, even when there are many elsif clauses contained in it.

The if and elsif expressions are tested using short_circuit evaluation.

An if statement can have a label clause just before the first then keyword. See the section on Header Labels. Note that an elsif clause can not have a label.

4.4.2 switch statement

The switch statement is used to run a specific set of statements, depending on the value of an expression. It often replaces a set of if-elsif statements due to it's ability to be highly optimized, thus much greater performance. There are some key differences, however. A switch statement operates upon the value of a single expression, and the program flow continues based upon defined cases. The syntax of a switch statement:

switch <expr> [with fallthru] [label "<label name>"] do
    case <val>[, <val2>, ...] then
        [code block]
        [[break [label]]|fallthru]
    case <val>[, <val2>, ...] then
        [code block]
        [[break [label]]|fallthru]
    case <val>[, <val2>, ...] then
        [code block]
        [[break [label]]|fallthru]
    ...

    [case else]
        [code block]
        [[break [label]]|fallthru]
end switch

The above example could be written with if statements like this ..

object temp = expression
object breaking = false
if equal(temp, val1) then
    [code block 1]
    [breaking = true]
end if
if not breaking and equal(temp, val2) then
    [code block 2]
    [breaking = true]
end if
if not breaking and equal(temp, val3) then
    [code block 3]
    [breaking = true]
end if
 ...
if not breaking then
    [code block 4]
    [breaking = true]
end if

The <val> in a case must be either an atom, literal string, constant or enum. Multiple values for a single case can be specified by separating the values by commas. The same symbol (or literal) may not be used multiple times as a case for the same switch. If two different symbols used as case values happen to have the same value, they must be in the same case...then statement, or an error will occur. If the parser can determine all values when the switch is parsed, then a compile time error will be thrown. Otherwise, the error will occur the first time that the switch is encountered. Likewise, when translating code, if the parser cannot determine all values at the time when the case values are parsed, the compilation will fail due to mulitple case values in the emitted C code (it is assumed that the programmer should work out this sort of bug in interpreted mode).

By default, control flows to the end of the switch block when the next case is encountered. The default behavior can be modified in two ways. The default for a particular switch block can be changed so that control passes to the next executable statement whenever a new case is encountered by using with fallthru in the switch statement:

switch x with fallthru do
    case 1 then
        ? 1
    case 2 then
        ? 2
        break
    case else
        ? 0
end switch

Note that when with fallthru is used, the break statement can be used to jump out of the switch block. The behavior of individual cases can be changed by using the fallthru statement:

switch x do
    case 1 then
        ? 1
        fallthru
    case 2 then
        ? 2
    case else
        ? 0
end switch

Note that the break statement before case else was omitted, because the equivalent action is taken automatically by default.

switch length(x) do
    case 1 then
        -- do something
        fallthru
    case 2 then
        -- do something extra
    case 3 then
        -- do something usual

    case else
        -- do something else
end switch

The label "name" is optional and if used it gives a name to the switch block. This name can be used in nested switch break statements to break out of an enclosing switch rather than just the owning switch.
Example:

switch opt label "LBLa" do
    case 1, 5, 8 then
        FuncA()


    case 4, 2, 7 then
        FuncB()
        switch alt label "LBLb" do
           case "X" then
                FuncC()
                break "LBLa"

           case "Y" then
                FuncD()

           case else
                FuncE()
       end switch
       FuncF()

    case 3 then
       FuncG()
       break

    case else
        FuncH()
end switch
FuncM()

In the above, if opt is 2 and alt is "X" then it runs...

FuncB() FuncC() FuncM()

But if opt is 2 and alt is "Y" then it runs ...

FuncB() FuncD() FuncF() FuncM()

In other words, the break "LBLa" skips to the end of the switch called "LBLa" rather than the switch called "LBLb".

4.4.3 ifdef statement

The ifdef statement has a similar syntax to the if statement.

ifdef SOME_WORD then
 --... zero or more statements
elsifdef SOME_OTHER_WORD then
 --... zero or more statements
elsedef
 --... zero or more statements
end ifdef

Of course, the elsifdef and elsedef clauses are optional, just like elsif and else are option in an if statement.

The major differences between and if and ifdef statement are that ifdef is executed at parse time not runtime, and ifdef can only test for the existence of a defined word whereas if can test any boolean expression.

Note that since the ifdef statement executes at parse time, run-time values cannot be checked, only words defined by the -D command line switch, or by the with define directive, or one of the special predefined words.

The purpose of ifdef is to allow you to change the way your program operates in a very efficient manner. Rather than testing for a specific condition repeatedly during the running of a program, ifdef tests for it once during parsing and then generates the precise IL code to handle the condition.

For example, assume you have some debugging code in your application that displays information to the screen. Normally you would not want to see this display so you set a condition so it only displays during a 'debug' session. The first example below shows how would could do this just using the if statement, and the second example shows the same thing but using the idef statement.

-- Example 1. --
if find("-DEBUG", command_line()) then
    writefln("Debug x=[], y=[]", {x,y})
end if
-- Example 1. --
ifdef DEBUG then
    writefln("Debug x=[], y=[]", {x,y})
end ifdef

As you can see, they are almost identical. However, in the first example, everytime the program gets to this point in the code, it tests the command line for the -DEBUG switch before deciding to display the information or not. But in the second example, the existence of DEBUG is tested once at parse time, and if it exists then, Euphoria generates the IL code to do the display. Thus when the program is running then everytime it gets to this point in the code, it does not check that DEBUG exists, instead it already knows it does so it just does the display. If however, DEBUG did not exist at parse time, then the IL code for the display would simply be omitted, meaning that during the running of the program, when it gets to this point in the code, it does not recheck for DEBUG, instead it already knows it doesn't exist and the IL code to do the display also doesn't exist so nothing is displayed. This can be a much needed performance boost for a program.

Euphoria predefines some words itself:

4.4.3.1 Euphoria Version Definitions

  • EU4 - Major Euphoria Version
  • EU4_1 - Major and Minor Euphoria Version
  • EU4_1_0 - Major, Minor and Release Euphoria Version

Euphoria is released with the common version scheme of Major, Minor and Release version identifiers in the form of major.minor.release. When 4.1.1 is released, EU4_1_1 will be defined and EU4_1 will still be defined, but EU4_1_0 will no longer be defined. When 4.2 is released, EU4_1 will no longer be defined, but EU4_2 will be defined. Finally, when 5.0 is released, EU4 will no longer be defined, but EU5 will be defined.

4.4.3.2 Platform Definitions

  • CONSOLE - Euphoria is being executed with the Console version of the interpreter (on windows, eui.exe, others are eui)
  • GUI - Platform is Windows and is being executed with the GUI version of the interpreter (euiw.exe)
  • WINDOWS - Platform is Windows (GUI or Console)
  • LINUX - Platform is Linux
  • OSX - Platform is Mac OS X
  • FREEBSD - Platform is FreeBSD
  • OPENBSD - Platform is OpenBSD
  • NETBSD - Platform is NetBSD
  • BSD - Platform is a BSD variant (FreeBSD, OpenBSD, NetBSD and OS X)
  • UNIX - Platform is any Unix

4.4.3.3 Architecture Definitions

Chip architecture:

  • X86
  • X86_64
  • ARM

Size of pointers and euphoria objects. This information can be derived from the chip architecture, but is provided for convenience.

  • BITS32
  • BITS64

Size of long integers. On Windows, long integers are always 32 bits. On other platforms, long integers are the same size as pointers. This information can also be derived from a combination of other architecture and platform ifdefs, but is provided for convenience.

  • LONG32
  • LONG64

4.4.3.4 Application Definitions

  • EUI - Application is being interpreted by eui.
  • EUC - Application is being translated by euc.
  • EUC_DLL - Application is being translated by euc into a DLL file.
  • EUB - Application is being converted to a bound program by eub.
  • EUB_SHROUD - Application is being converted to a shrouded program by eub.
  • CONSOLE - Application is being translated, or converted to a bound console program by euc or eub, respectively.
  • GUI - Application is being converted to a bound Windows GUI program by eub.

4.4.3.5 Library Definitions

  • DATA_EXECUTE - Application will always get executable memory from allocate even when the system has Data Execute Protection enabled for the Euphoria Interpreter.
  • SAFE - Enables safe runtime checks for operations for routines found in machine.e and dll.e
  • UCSTYPE_DEBUG - Found in include/std/ucstypes.e
  • CRASH - Found in include/std/unittest.e

More examples

-- file: myproj.ex
puts(1, "Hello, I am ")
ifdef EUC then
    puts(1, "a translated")
end ifdef
ifdef EUI then
    puts(1, "an interpreted")
end ifdef
ifdef EUB then
    puts(1, "a bound")
end ifdef
ifdef EUB_SHROUD then
    puts(1, ", shrouded")
end ifdef
puts(1, " program.\n")

C:\myproj> eui myproj.ex
Hello, I am an interpreted program.
C:\myproj> euc -con myprog.ex
... translating ...
... compiling ...
C:\myproj> myprog.exe
Hello, I am a translated program.
C:\myproj> bind myprog.ex
...
C:\myproj> myprog.exe
Hello, I am a bound program.
C:\myproj> shroud myprog.ex
...
C:\myproj> eub myprog.il
Hello, I am a bound, shrouded program.

It is possible for one or more of the above definitions to be true at the same time. For instance, EUC and EUC_DLL will both be true when the source file has been translated to a DLL. If you wish to know if your source file is translated and not a DLL, then you can

ifdef EUC and not EUC_DLL then
    -- translated to an application
end ifdef

4.4.3.6 Using ifdef

You can define your own words either in source:

with define MY_WORD       -- defines
without define OTHER_WORD -- undefines

or by command line:

eui -D MY_WORD myprog.ex

This can handle many tasks such as change the behavior of your application when running on Linux vs. Windows, enable or disable debug style code or possibly work differently in demo/shareware applications vs. registered applications.

You should surround code that is not portable with ifdef like:

ifdef WINDOWS then
   -- Windows specific code.
elsedef
   include std/error.e
   crash("This program must be run with the Windows interpreter.")
end ifdef

When writing include files that you cannot run on some platform, issue a crash call in the include file. Yet make sure that public constants and procedures are defined for the unsupported platform as well.

ifdef UNIX then
     include std/bash.e
end ifdef

-- define exported and public constants and procedures for
-- OSX as well
ifdef WINDOWS or OSX then
    -- OSX is not supported but we define public symbols for it anyhow.

The reason for doing this is so that the user that includes your include file sees an "OS not supported" message instead of an "undefined reference" message.

Defined words must follow the same character set of an identifier, that is, it must start with either a letter or underscore and contain any mixture of letters, numbers and underscores. It is common for defined words to be in all upper case, however, it is not required.

A few examples:

for a = 1 to length(lines) do
    ifdef DEBUG then
        printf(1, "Line %i is %i characters long\n", {a, length(lines[a])})
    end ifdef
end for

sequence os_name
ifdef UNIX then
    include unix_ftp.e
elsifdef WINDOWS then
    include win32_ftp.e
elsedef
    crash("Operating system is not supported")
end ifdef

ifdef SHAREWARE then
  if record_count > 100 then
     message("Shareware version can only contain 100 records. Please register")
     abort(1)
  end if
end ifdef

The ifdef statement is very efficient in that it makes the decision only once during parse time and only emits the TRUE portions of code to the resulting interpreter. Thus, in loops that are iterated many times there is zero performance hit when making the decision. Example:

while 1 do
    ifdef DEBUG then
        puts(1, "Hello, I am a debug message\n")
    end ifdef
    -- more code
end while

If DEBUG is defined, then the interpreter/translator actually sees the code as being:

while 1 do
    puts(1, "Hello, I am a debug message\n")
    -- more code
end while

Now, if DEBUG is not defined, then the code the interpreter/translator sees is:

while 1 do
    -- more code
end while

Do be careful to put the numbers after the platform names for Windows:

-- This puts() routine will never be called
-- even when run by the Windows interpreter!
ifdef WINDOWS then
     puts(1,"I am on Windows\n")
end ifdef