Up | TOC | Index | |||||
<< 3 Using Euphoria | < 4.3 Assignment statement | Up: 4 Language Reference | 4.5 Loop statements > | 5 Formal Syntax >> |
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]] case <val>[, <val2>, ...] then [code block] [[break [label]] case <val>[, <val2>, ...] then [code block] [[break [label]] ... [case else] [code block] [[break [label]] 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. 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...
But if opt is 2 and alt is "Y" then it runs ...
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_0 - Major and Minor Euphoria Version
- EU4_0_4 - 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.0.5 is released, EU4_0_5 will be defined and EU4_0 will still be defined, but EU4_0_4 will no longer be defined. When 4.1 is released, EU4_0 will no longer be defined, but EU4_1 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 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.4 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.5 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