Table of Contents

Expression Syntax in EPIC

About "expression mode" and "text mode"

EPIC has two “modes” it executes your code in. “Text mode” is the standard mode that every command starts out in.

Text mode Expression mode
Everything is plain text Everything is operator or operand
First word is command, rest is arg list Can escape to text mode with []s
$'s start “expandos” (macro substitution) $'s are not permitted before vars

“Expression mode” occurs in all of the following text-mode commands: (Technically, @ and parenthesis are not really commands, they just look like it)

@ ...			(return value is ignored -- see below)
(...)			(return value is ignored -- see below)
${...}			(may be used anywhere in text mode)
IF (...)
UNLESS (...)
WHILE (...) 
UNTIL (...) 
DO {<commands>} WHILE (...)
FOR (<command>, ... ,<command>)
REPEAT (...)

When you are in an expression, you can escape to 'text mode' by using the square brackets operator ([…]), the single quotes operator ('…') or the double quotes operator (“….”).

When you are in an expression, and you call a function, like so:

@foo = function(...)

the function parameters are always in 'text mode'.

Operators and operands

When EPIC evaluates an expression, it breaks the entire expression into “operators” and “operands”. Operators are the mathematical actions that you want to do (+, -, *, /, &&, ||, etc). Operands are the “arguments” to the operators. Some operators are unary (take only one operand) and some operators are binary (take two operands), and one operator is tertiary (takes three operands)

The process of evaluating an expression is performing each of the operations one at a time until there is only one operand and no operators left. The operators have a precedence, so all of the operators with precedence 1 go before any operator with precedence 2, and so on. If there is more than one operator with the same precedence, you have to know whether to do the right one or the left one first. This is the associativity. L→R means when there is more than one of the same operator in an expression, the left one is done first, and R→L means the right one is done first.

Each operator “reduces” the expression by removing itself and one or more operands and replacing it with a new operand. When you have executed all of the operators in an expression, you should have only one operand left, and that is the result of the expression.

Example:

Let's solve the expression:

3 - 4 * 5 - 7

Since * has precedence of 4 and + has precedence of 5 (see the table below), the * goes first. 4 * 5 is 20, so that reduces to

3 - 20 - 7

Since there are two -'s, then we look at the associativity, and it is L→R which means the left - is done first, which reduces to

	-17 - 7

and performing the last operation yields

	-24

as the result of the expression.

List of operators and associativity

PRECEDENCE OPERATOR USAGE REDUCES TO ASSOCIATIVITY
1 Sub-expression ( op ) op L→R
2 Logical NOT ! bool bool R→L
2 Bitwise NOT ~ int int R→L
2 Prefix Decrement -- lval int R→L
2 Prefix Increment ++ lval int R→L
2 Suffix Decrement lval -- int R→L
2 Suffix Increment lval ++ int R→L
2 Unary Plus + float float R→L
2 Unary Minus - float float R→L
2 String length @ [op] int R→L
2 Word Count # [op] int R→L
2 Variable Dereference * [rval] lval R→L
2 Variable Dereference * [number] rval R→L
2 Double Expansion ** [op] op R→L
3 Exponent float ** float float R→L
4 Multiplication float * float float L→R
4 Division float / float float L→R
4 Modulus float % int float L→R
5 Addition float + float float L→R
5 Subtraction float - float float L→R
5 String Catenation op ## op op L→R
6 Bitwise shift left int << int int L→R
6 Bitwise shift right int >> int int L→R
7 Less Than op < op int L→R
7 Less than or equal to op <= op int L→R
7 Greater than op > op int L→R
7 Greater than or equal to op >= op int L→R
8 Pattern match op =~ op bool L→R
8 Pattern doesn't match op !~ op bool L→R
9 Equal, ignore case op == op bool L→R
9 Not equal, ignore case op != op bool L→R
9 Equal op === op bool L→R
9 Not equal op !== op bool L→R
10 Bitwise AND int & int int L→R
11 Exclusive OR int ^ int int L→R
12 Bitwise OR int | int int L→R
13 Logical AND bool && bool bool L→R
14 Logical XOR bool ^^ bool bool L→R
15 Logical OR bool || bool bool L→R
16 If-then-else bool ? op : op op R→L
17 Assignment lval = op op R→L
17 Addition-assign lval += float float R→L
17 Subtraction-assign lval -= float float R→L
17 Multiplication-assign lval *= float float R→L
17 Division-assign lval /= float float R→L
17 Modulus-assign lval %= float float R→L
17 Bitwise AND-assign lval &= int int R→L
17 Exclusive OR-assign lval ^= int int R→L
17 Bitwise OR-assign lval |= int int R→L
17 Bitshift left-assign lval <<= int int R→L
17 Bitshift right-assign lval >>= int int R→L
17 Logical AND-assig lval &&= bool bool R→L
17 Logical OR-assign lval ||= bool bool R→L
17 Logical XOR-assign lval ^^= bool bool R→L
17 Exponent-assign lval **= floatfloat R→L
17 strcat-assign lval #= op op R→L
17 String prefix-assign lval #~ op op R→L
17 Swap values lval <=> lval lval R→L
17 Last Value op , op op L→R

[1] The operand must be an lval
[3] Short circuit operator.
[4] You do not have to give an explicit operand. If you omit the operand, then [$*] is used as the operand.

How operands are handled

There are four different kinds of operands

Escapes to text mode

[…] {…} '…' “…”

Epic-only operators

Operators that behave different in epic

How errors in expressions are handled

The string concatenation operators, ##, #=, and #~, are a special case, as they are not present in C or C++. As their name indicates, they are used to join two or more strings together, end to end. For example:

 @ foo  = [foo] ## [bar]              /* sets $foo to "foobar" */
 @ foo #= [blah]                      /* sets $foo to "foobarblah" */
 @ foo #~ [hmm]                       /* sets $foo to "hmmfoobarblah" */

Also like C/C++, parentheses may be used to force certain parts of the expression to be evaluated first (mainly in the event that the user wishes for it to evaluate in an order other than that of operator precedence). Parentheses may be nested. For example, if some variable $foo is set to 3:

 foo * 4 + 5                          /* returns 17 */
 foo * (4 + 5)                        /* returns 27 */
 4 + ((foo + 9) / 3)                  /* returns 8 */

All assignment operators always return the value assigned, which allows for the assignment of multiple variables at once. Keep in mind that expressions are evaluated right to left. For example, if $foo is 12 and $bar is 11:

 @ foo += bar *= 2                    /* $bar is 22, $foo is 34 */

Since the release of the EPIC4 pre-betas, the client has been growing ever more perlish. Like perl, the =~ and !~ operators match with wildcards. =~ is a direct opposite of !~, where it returns true if the patterns patch, while !~ returns false. In this example, $bar is “epic”:

 @ foo = bar =~ [*pi*]               /* returns 1 */
 @ foo = bar !~ [*z*]                /* returns 1 */

The various bitwise operators are of special interest also. Assuming $foo is 12 and $bar is 11:

 foo & bar                            /* returns 8 */
 foo | bar                            /* returns 15 */
 foo ^ bar                            /* returns 7 */

The exponential operator takes numbers to various powers. It is especially useful, since many script writers create a $power() function for this purpose. It supports negative and fractional exponents as long as the system's math library (libm) does. Assuming $foo is 9:

 foo ** 2                             /* returns 81 */
 foo ** 0.5                           /* returns 3 */

The {pre,post}fix {in,de}crement operators are big timesavers that C and C++ users everywhere swear by. They have also been known to swear at them, for reasons you will soon see. Assume $foo is 5, each column shows 3 ways of doing the same thing, from least efficient to most efficient:

 @ foo  = foo + 1                     @ foo  = foo - 1
 @ foo += 1                           @ foo -= 1
 @ foo++                              @ foo--

However, these operators have pitfalls, which are mainly discovered by those who do not understand how they work. Both may either prefix or postfix a variable; prefix causes it to evaluate before the operation, postfix causes it to evaluate aster. For the examples shown above, it makes no difference. However, it does make a difference in this example:

 while ( foo++ < 10 ) { ... }

The expression is evaluated for whether $foo is less than 10, and then $foo is incremented. If the autoincrement operator was instead used in prefix form, $foo would be incremented before the expression was evaluated, which would cause the loop to have one less iteration.

Another pitfall of the autoincrement and decrement operators is the ambiguity introduced by insufficient whitespace when used in conjunction with addition and subtraction operators. Consider the following:

 @ foo    = 4
 @ bar    = 8
 @ foobar = foo+++bar

How should one interpret the last assignment? Should it really look like ${foo++ + bar} or ${foo + ++bar}? It's hard to tell. The best solution is to not write code that looks so silly and unreadable. Add a couple spaces, and there is no ambiguity. (The answer is, the first one.)

Another popular operator familiar to most C/C++ programmers is the tertiary operator (sometimes referred to as the alternation operator). It performs a function similar to IF, except is much more compact and efficient. We'll let $foo be 5 again:

 @ bar = foo > 3 ? 1 : 0              /* sets $bar to 1 */
 @ bar = foo > 8 ? 1 : 0              /* sets $bar to 0 */

Functions (built-in and scripted) can also be used within expressions. The function will be evaluated, and its return value is used in the expression:

 @ foo = pattern(b* foo bar blah)     /* sets $foo to "bar blah" */

All functions implicitly use a special operator, (). That is, the pair of parentheses themselves compose an operator, though of course it is somewhat different in nature from more traditional operators like '+' or '<' or '&'. Functions (aliases with return values) require the () to function properly.

A similar operator is [], which is used for alias and variable structures. We've already seen that it can be used to explicitly switch the evaluation context to text. This can be extended to structure elements, such that they can be expanded on the fly:

 @ foo.1.1 = [foo]
 @ foo.1.2 = [bar]
 alias blah echo $foo[1][$0]
 /blah 2                              /* expands to $foo.1.2 -> "bar" */

The same can be applied to aliases and functions as well. Because of the nature of the [] operator, anything may be expanded inside it, variables and functions alike.