exp.html has no links or anything in it. As they didn't include it, it has not changed from the winmugen days and it's relatively short.
Spoiler, click to toggle visibilty
Expressions
===========
M.U.G.E.N, (c) Elecbyte 2002
Documentation for version 2002.04.14
Beta-release documentation
Updated 27 October 2001
====================================================================
0. Contents
====================================================================
MUGEN supports the use of arithmetic expressions in the CNS and CMD files. This document gives a complete description of
expression syntax.
I. Data types: Ints and Floats
II. Arithmetic operators
III. Precedence and associativity of operators
IV. Expression syntax
V. Triggers
VI. Trigger redirection
VII. SC values
VIII. More on function-type triggers
IX. Expressions in state and state controller parameters
X. (Advanced) Optimizing for speed
====================================================================
I. Data types: Ints and Floats
====================================================================
An int is a whole number, such as 0, 1, -4, etc. Ints can assume values
between about -2 billion and 2 billion. A float is a real number which
has a "decimal part", such as 5.24, or -4247.44. Note that a whole number such as 55 can also be represented as a float, thus: 55.0. Floats offer only 7 significant figures of precision.
The behavior of arithmetic expressions depends heavily on the underlying data types used to represent numbers. Also, state controllers may expect their input to be given as a certain type, and will give errors if the wrong type is supplied.
====================================================================
II. Arithmetic operators
====================================================================
In addition to the familiar arithmetic operators like +, -, *, and /,
several other operators exist which act on their input values in
different ways. This section describes the behavior of these operators.
+
The familiar addition operator. x + y evaluates to an int if x and y
are both ints, or a float if x and y are both floats. If one of x or y
is a float, then both are converted to floats (with possible loss
of precision in the process) and then x + y is evaluated as a sum
of floats.
-
The familiar subtraction operator. As with addition, x - y evaluates
to an int if x and y are both ints, or a float if they are both floats.
If one of x or y is a float, they are both converted to floats and the
subtraction is performed as a subtraction of floats.
-
If - appears in a context where the subtraction operator is not
appropriate, it is interpreted as the negation operator. That is, given input x, "-x" represents the value -x. Returns the same type as x's.
*
The multiplication operator. The behavior is similar to the + and -
operators.
/
The division operator. If x and y are both ints, then x/y gives the
number of times that y goes evenly into x. For instance, 7/2 = 3,
because 2 goes into 7 three times (with a remainder of 1 which is
discarded). If x and y are both floats, then x/y returns a float. For
instance, 7.0/2.0 = 3.5. Finally, if one of x or y is a float, they are both converted to floats before evaluation.
The result of a division by zero will be discussed in the section on
SC values.
%
The remainder or mod operator. If x and y are both ints, x%y returns
an int representing the remainder after performing a division x/y. For
instance, 7%2 = 1, and 23 % 17 = 6. It is an error to use the % operator on float values, or to compute x%0. The result of such operations will be discussed in the section on SC values.
**
The exponentiation operator. If x and y are both ints >= 0, then x**y
gives an int representing x raised to the power of y (we define 0**0 =
1). Otherwise, x**y is computed as an exponentiation of real numbers
(converting x and y to floats first if necessary). The result of an
invalid exponentiation such as -1^.5 will be discussed in the section on SC values.
!
The logical NOT operator. !x evaluates to 0 (int) if x is nonzero,
and 1 (int) if x is zero.
&&
The logical AND operator. x && y evaluates to 1 (int) if x and y
are both nonzero, and to 0 (int) otherwise.
||
The logical OR operator. x || y evaluates to 1 (int) if one or more
of x and y is nonzero, and to 0 (int) otherwise.
^^
The logical XOR operator. x ^^ y evaluates to 1 (int) if exactly one
of x and y is nonzero, and to 0 (int) otherwise.
~
The bitwise NOT operator. ~x inverts the bits of x's binary (two's
complement) representation. It is an error to apply this to a float --
for the result of such an operation, see the section on SC values.
&
The bitwise AND operator. The nth bit of x&y is set if and only if the
nth bits of both x and y are set. Returns an SC value if either of x or y is a float.
|
The bitwise OR operator. The nth bit of x|y is set if and only if the
nth bit of either x or of y (or both) is set. Returns an SC value if either of x or y is a float.
^
The bitwise XOR operator. The nth bit of x^y is set if and only if the
nth bit of exactly one of x and y is set. Returns a SC value if either of x or y is a float.
=
The equality operator. If x and y are both ints or both floats, x = y
evaluates to 1 (int) if x and y are equal, and 0 otherwise. If exactly
one of x or y is a float, then they are both converted to floats before
testing for equality.
:=
The assignment operator. An unredirected variable name (var(n) or fvar(n) for suitable values of n) must appear on the left-hand side. If the left-hand side contains an integer variable, then the right-hand side is truncated to an integer before assignment. If the left-hand side contains a float variable, then the right-hand side is converted to float if necessary before assignment. In both cases, the value of the expression is the value that is assigned to the variable.
!=
The inequality operator. If x and y are both ints or both floats,
x != y evaluates to 1 (int) if x and y are not equal, and 0 otherwise.
If exactly one of x or y is a float, then they are both converted to
floats before testing for equality.
<
The less-than operator. If x and y are both ints or both floats, x <
y evaluates to 1 (int) if x < y, and 0 otherwise. If exactly one of x or y is a float, then they are both converted to floats before testing for equality.
<=
Similar to <, with the exception that if x = y, then x <= y returns
1 (int).
>
The greater-than operator. If x and y are both ints or both floats,
x > y evaluates to 1 (int) if x > y, and 0 (int) otherwise. If exactly
one of x or y is a float, then they are both converted to floats before testing for equality.
>=
Similar to >, with the exception that if x = y, then x >= y returns
1 (int).
=[,]
!=[,]
=[,)
!=[,)
=(,]
!=(,]
=(,)
!=(,)
Interval operators. These take three arguments, x, y, and z. If any of
x, y, or z is a float, they are all converted to floats. After conversion if necessary, x = [y,z] is equivalent to (x >= y) && (x <= z). Similarly, x = (y,z) is equivalent to (x > y) && (x < z). The half-open intervals have the obvious meaning.
The negated interval operators work as follows: x != [y,z] is equivalent (after conversion if necessary) to (x < y) || (x > z).
x != (y,z) is equivalent to (x <= y) || (x >= z). The half-open intervals again have the obvious meaning.
You can view the interval operators as producing the appropriate open, closed, or half-open intervals on the ints or the floats. The = symbol means set membership in this context.
Some restrictions apply on where intervals may be placed in an
expression. See the section on syntax for details.
====================================================================
III. Precedence and associativity of operators
====================================================================
If you consider an expression like 3+2*5, the result is different
depending if you evaluate the * first (yielding 13) or if you evaluate
the + first (yielding 25). To disambiguate expressions such as this,
operators are assigned distinct precedence levels. In this case, the
precedence of * is higher than the precedence of +, so the * is
evaluated first, then the + is applied to the result. So the correct
answer is 13.
If two operators share the same precedence, then the expression is
evaluated from left to right, except for the unary operators and the
assignment operator, which associate right to left. For instance, * and / share the same precedence, so 5.0*5/6 evaluates to 25.0/6, which evaluates to 4.166667. On the other hand, 5/6*5.0 evaluates to 0*5.0, which evaluates to 0.0. In contrast, because unary operators associate right to left, -!0 is grouped as -(!0), which evaluates to -(1), which then evaluates to -1.
If part of an expression is grouped in parentheses (), then that part of the expression is evaluated first. For instance, in the expression
(3+2)*5, the + is evaluated first, giving 5*5, which then evaluates to
25. If parentheses are nested, the innermost parentheses are evaluated
first.
Operator precedence is basically the same as in C. The complete list of operator precedence, from highest to lowest, is as follows:
! ~ - (Unary operators)
**
* / %
+ -
> >= < <=
= != intervals
:=
&
^
|
&&
^^
||
Programmers are encouraged to parenthesize as necessary to maintain
clarity. Otherwise, bugs due to subtle misunderstanding of operator
precedence are almost assured.
====================================================================
IV. Expression syntax
====================================================================
Basically, any normal arithmetic expression is allowable. In addition,
since the relational operators (>, <=, etc.) are viewed as returning
ints, it is possible to operate on their return values, giving some
unusual-looking expressions like
1.0 = (2 = (1 > 0) + !(0 < 1))
The 1 > 0 term evaluates to 1, and the 0 < 1 term evaluates to 0. Hence
!(0 < 1) evaluates to 1, so the expression simplifies to
1.0 = (2 = 1 + 1)
Since 2 = 1 + 1, the term in parentheses evaluates to 1, so the
expression further simplifies (after type conversion) to
1.0 = 1.0
which evaluates to 1 (int), since the equality holds.
A notable restriction in expression syntax is that interval operators
are only allowed to appear on the rightmost side of an expression. If
part of an expression is enclosed in parentheses, then that part is
considered a subexpression, and an interval is allowed to appear on the
right side of that subexpression. So the following is a well-formed
expression, which evaluates to 0:
(1 = [0,2]) = (0,1)
But the following is not well-formed:
1 = [0,2] = (0,1)
In addition, no operator symbols other than = or != may appear before an interval. So an expression like 5 > [0,2], or 4 + [1,4), is not allowed.
In comma-separated parameter lists, such as the arguments to some
function-type triggers or parameters to state controllers, each expression in the list is considered a separate subexpression, and therefore intervals may appear at the end of those subexpressions.
====================================================================
V. Triggers
====================================================================
For historical reasons, two distinct constructs are both called
"triggers." The first is what might be more properly called a
condition-type trigger, and the second is what might be more properly
called a function-type trigger. For instance, in the CNS, a typical
state controller might look like
[State 1234, 5]
type = ChangeState
trigger1 = time = 0
value = 0
The entire line "trigger1 = time = 0" is a condition-type trigger. If
the expression "time = 0" evaluates to a nonzero value, then the
ChangeState controller is executed. If the expression "time = 0"
evaluates to zero, then the ChangeState controller is not executed.
Thus whether the condition is zero or nonzero affects whether the
controller is triggered.
On the other hand, the word "time" appearing in the expression is a
function-type trigger. It returns a value, namely, the amount of time
that the player has been in state 1234. Note that a function-type
trigger doesn't "trigger" anything. It just gives a value that can be
acted on within the expression.
To further illustrate the difference, let us consider a different state controller:
[State 1234, 5]
type = VarSet
trigger1 = 1
v = 0
value = time + 5
Note that the condition-type trigger "trigger1 = 1" now contains no
function-type triggers within it. Since the expression "1" always
evaluates to 1, the controller will be triggered every frame. To
determine what value to assign var0, the expression "time + 5" is
evaluated. The function-type trigger "time" returns the player's
statetime. Then 5 is added and the result is stored in var0.
A complete list of function-type triggers can be found in trigger.doc.
In general, which of the two types of triggers is meant is clear from
context. Where there is some ambiguity, the terms "condition-type
trigger" and "function-type trigger" will be used.
====================================================================
VI. Trigger redirection
====================================================================
In the above example, the time trigger returned the statetime of the
player. But sometimes, one might wish to check the statetime of the
player's target, or the player's parent (if the player is a helper),
etc. This can be accomplished by preceding the trigger name by a keyword indicating whose information should be returned. This process is known as trigger redirection. For example,
5 + parent, time
returns 5 + the player's parent's statetime.
The complete list of redirection keywords is the following:
*) parent
Redirects the trigger to the player's parent. (Player must be a
helper.)
*) root
Redirects the trigger to the root.
*) helper
Redirects the trigger to the first helper found. See the related
trigger "NumHelper" in the trigger documentation.
*) helper(ID)
ID should be a well-formed expression that evaluates to a positive
integer. The trigger is then redirected to a helper with the
corresponding ID number.
*) target
Redirects the trigger to the first target found.
*) target(ID)
ID should be a well-formed expression that evaluates to a non-
negative integer. The trigger is then redirected to a target with
the corresponding targetID. The targetID is specified in the "ID"
parameter of a HitDef controller.
*) partner
Redirects the trigger to the player's partner. Normal helpers and
neutral players are not considered opponents. See the related
trigger "numpartner" in the trigger documentation.
*) enemy
Redirects the trigger to the first opponent found. Normal helpers
and neutral players are not considered opponents. See the related
trigger "numenemy" in the trigger documentation.
*) enemy(n)
n should be a well-formed expression that evaluates to a non-
negative integer. The trigger is redirected to the n'th opponent.
*) enemyNear
Redirects the trigger to the nearest opponent.
*) enemyNear(n)
n should be a well-formed expression that evaluates to a non-
negative integer. The trigger is redirected to the n'th-nearest
opponent.
*) playerID(ID)
n should be a well-formed expression that evaluates to a non-
negative integer. The trigger is redirected to the player with
unique ID equal to ID. See the "ID" and "PlayerExistID" triggers
in the trigger documentation.
If the trigger is redirected to an invalid destination (for instance,
if it is retargeted to a helper when none exist), then an error is
returned. See the section on SC values.
Note: multiple redirection (e.g., root,target,time) is not currently supported.
====================================================================
VII. SC values
====================================================================
There are several sources of unrecoverable error in expressions. For
instance, one could attempt to divide by 0, evaluate the square root of a negative number, or attempt to redirect a trigger to a nonexistent destination. In these situations, SC values are used as a way to complete expression evaluation gracefully.
An SC value is a special kind of int which can only take on the values 1 or 0, called SC true (STrue) and SC false (SFalse). The result of any operation or trigger evaluation on an SC value is that SC value. (In other words, you can think of the SC values as variants on the "bottom" element in computation theory. Semantically speaking, CNS expression evaluation is strict in bottom.) If two SC values are given to the same operator, then the leftmost one takes priority.
For instance, the expression
4 + (1/0)*2
evaluates to 4 + (SFalse) * 2, which evaluates to 4 + SFalse, which
evaluates to SFalse. On the other hand,
STrue / SFalse
would evaluate to STrue, because STrue comes on the left. Finally,
helper, time
would evaluate to SFalse if called with no helpers.
Currently, STrue is unused. Only SFalse is returned in error conditions. This means that any condition-type trigger that causes an error during evaluation will not trigger. So, in
type = ChangeState
trigger1 = helper, statetype = A
value = 0
the ChangeState controller would never be executed if no helpers
existed, because the expression "helper, statetype = A" would evaluate
to SFalse, which is 0.
The documentation on function-type triggers explains exactly when those
triggers will return SFalse.
SC values were originally called short-circuit values, because evaluation on an SC value "short-circuits" and does not act as normal. However, this nomenclature had the potential to cause confusion with short-circuit logical evaluation, so it had to be changed, though not so drastically as to cause further confusion. Hence short-circuit values became SC values. At the moment, SC officially stands for "skip and carry", because when an SC value is encountered, normal calculation is skipped and the SC value is simply carried on. Other possibilities for SC include "semantic codomain" and "so-called". We welcome better suggestions for what SC should stand for.
====================================================================
VIII. More on function-type triggers
====================================================================
Most function-type triggers either take no arguments or take arguments
in a parameter list. For instance, the time trigger takes no arguments, whereas the ifelse trigger takes three arguments
ifelse(exp1,exp2,exp3)
where exp1, exp2, and exp3 are all valid expressions. In this kind of
situation, exp1, exp2, and exp3 are all considered separate subexpressions, so intervals can appear on the rightmost end of each of those subexpressions. The order of evaluation of parameter lists is from left to right.
Due to irregular syntax, some old function-type triggers cannot take
expressions as their arguments. Because of this, they cannot be
integrated into expressions in the standard manner. For nonstandard
triggers of this type, the triggers can only appear with certain sets of operators and arguments (which are outlined in trigger.doc). In
particular, these triggers cannot take expressions as their arguments. For instance,
trigger1 = AnimElem = (1+1)
is an invalid expression.
Old-style function-type triggers appear only in "clauses" (trigger, relational operator, argument). These clauses are treated as a single unit (specifically, a single nullary trigger) for the purposes of expression evaluation. This means, among other things, that the concept of operator precedence is not applicable to operators appearing within an old-style function-type trigger clause. For instance, in
trigger1 = AnimElem = 5 + 4
the expression is broken down into three units:
{AnimElem=5} {+} {4}
The "AnimElem=5" unit is treated as the name of a nullary trigger, hence the + operator does not have precedence over the = appearing within the name "AnimElem=5". In other words, this expression means something like "Execute the trigger called `AnimElem=5', then add 4 to the result."
The list of old-style function-type triggers, and expression-capable replacements if applicable, is as follows:
AnimElem, superseded by AnimElemTime
P1Name, P2Name, P3Name, P4Name
StateType, P2StateType
Command
MoveType, P2MoveType
TimeMod, superseded by the % operator
ProjHit, ProjContact, ProjGuarded; superseded by ProjHitTime,
ProjContactTime, and ProjGuardedTime
====================================================================
IX. Expressions in state and state controller parameters
====================================================================
For the most part, any parameter to the statedef or a state controller
can be an expression. The exceptions are parameters that are given as
strings. For instance, hit attributes, guardflags, etc. cannot be
specified as expressions. Also, the ForceFeedback controller, as well as the ignorehitpause and persistent parameters to all controllers, are irregular in that they cannot take expressions.
State controller parameters are evaluated at the time the controller is triggered, and are not subsequently re-evaluated unless the controller is triggered again. Parameters that are given as comma-separated lists are evaluated from left to right. To achieve continual evaluation of controller parameters, the controller must be continually triggered.
In the case of certain controllers such as HitDef, it is not always wise to use continual triggering, since this decreases performance and also may lead to undesired behavior. In this case, the programmer may wish to try to delay triggering the HitDef as long as possible, so as to evaluate the HitDef parameters right before they are used.
====================================================================
X. (Advanced) Optimizing for speed
====================================================================
MUGEN evaluates condition-type triggers for a state controller in the following order: First it evaluates triggeralls, from top to bottom. If any of the triggeralls evaluates to 0, the rest of the triggers are skipped and evaluation proceeds to the next controller. If all the triggeralls evaluate to nonzero, then the engine starts to evaluate trigger1's, from top to bottom. If any of these evaluate to 0, then evaluation skips to the first trigger2, and so on. If all the triggers in a block (besides triggerall) evaluate to nonzero, then the state controller parameters are evaluated and the controller is triggered.
In other words, the logical evaluation of triggers is short-circuited.
In C-like notation, this setup might be denoted
triggerall,1 && triggerall,2 && ... && ((trigger1,1 && trigger1,2
&& ...) || (trigger2,1 && trigger2,2 && ...) || ... )
where (e.g.) trigger1,2 denotes the second trigger1 line, trigger2,1 denotes the first trigger2 line, etc. The logical evaluation of this trigger group would then be short-circuited as in C.
Because of this system, considerable performance gains can be attained by organizing expressions so that the condition-type triggers are as simple, and as few in number, as possible. The bulk of the "work" can be offloaded to the state controller parameters, which are only evaluated once at trigger time, instead of every frame that the player is in the state. For instance,
[State -1]
type = ChangeState
trigger1 = command = "a"
trigger1 = power < 1000
value = 3000
[State -1]
type = ChangeState
trigger1 = command = "a"
trigger1 = power >= 1000
value = 3001
[State -1]
type = ChangeState
trigger1 = command = "a"
trigger1 = power >= 2000
value = 3002
could be more compactly expressed as
[State -1]
type = ChangeState
trigger1 = command = "a"
value = 3000 + (power >= 1000) + (power >= 2000)
Further speedups are possible if triggeralls that are most likely to be false are placed highest in the triggerall block. Similarly, the trigger1 block should be the most likely block to trigger, but within the trigger1 block itself, the triggers that are most likely to evaluate to 0 should be placed highest. For state controllers with many triggers containing duplicated conditions, it may be faster to break the controllers up into two separate blocks, each with its own set of triggeralls.
If you have a complex condition which is being used as the trigger condition for many consecutive state controllers, you may wish to save the value of the condition in a variable, then use that variable as a trigger to the subsequent controllers. For instance,
trigger1 = (command="abc" && command!="holddown" && power>=1000) ||
(command="abc" && command!="holddown" && var(5)) ||
((command != "abc" || command = "holddown") && power>=2000)
could be written as (assuming var(0) is available):
trigger1 = (var(0):=(command="abc" && command !="holddown") && power>=
1000) || (var(0) && var(5)) || (!var(0) && power>=2000)
Finally, since the expression parser performs no optimization on expressions, an expression like
trigger1 = ctrl
is slightly faster than
trigger1 = ctrl = 1