The .animated object format

■ Contents
1. Overview
2. Sections
3. List of infix notation operators
4. List of functions
5. List of variables
6. Performance
7. Tips
8. Example functions
9. Formal Grammar


■ 1. Overview
The ANIMATED object format is a container format allowing you to reference other objects (B3D/CSV/X) and to apply animation to them. It also allows to just group other objects (including other ANIMATED objects) without animating them.

Animated objects can be used in CSV/RW routes (unless explicitly disallowed by some commands), as train exterior objects via the extensions.cfg, and as 3D cabs via the panel.animated file.

● Basics
Animation is performed via the following primitives:

  State changes - basically allowing to switch between different objects at any time
  Translation - moving objects in three independent directions
  Rotation - rotating objects around three independent axes
  Texture shifts - allowing to shift the texture coordinates of objects in two independent directions

● A little formality
The file is a plain text file encoded in any arbitrary
encoding, however, UTF-8 with a byte order mark is the preferred choice. The parsing model for numbers is Strict. The file name is arbitrary, but must have the extension .animated. The file is interpreted on a per-line basis, from top to bottom.


■ 2. Sections
● The [Include] section

You can use the [Include] section to just include other objects, but without animating them. This allows you to use the ANIMATED object file as a container to group other objects. There can be any number of [Include] sections within the file.

[Include]
This starts the section.

FileName0
FileName1
FileName2
...
Defines a series of B3D/CSV/X/ANIMATED objects that should be included as-is.

Position = X, Y, Z
This defines the position of the objects, basically allowing you to offset them with respect to the rest of the ANIMATED object file.


● The [Object] section

You can use the [Object] section to create a single animation. This requires to set up at least one state via the States parameter, and to use any combination of functions you want, which provide control over the animation. There can be any number of [Object] sections within the file.

[Object]
This starts the section.

Position = X, Y, Z
Defines the position of the object. This basically corresponds to a final TranslateAll command in the respective CSV/B3D file, but is performed after any of the functions are performed. For example, if you want to use rotation, then keep in mind that rotation is done around the origin (0,0,0). The Position command allows you to reposition the object after the rotation is performed.

States = File0, File1, ..., Filen-1
Loads n objects of CSV/B3D/X extension. Please note that the first file indicated has state index 0. Use multiple files only if you want to use state changes.

StateFunction = Formula
This defines the function for state changes. The result of the Formula is rounded toward the nearest integer. If that integer is between 0 and n-1, where n is the number of states as defined via States, the respective state is shown, otherwise, no object is shown. You can make use of the latter if you want an object to toggle on/off with only one state.

TranslateXDirection = X, Y, Z
TranslateYDirection = X, Y, Z
TranslateZDirection = X, Y, Z
These define the directions for the TranslateXFunction, TranslateYFunction and TranslateZFunction, respectively. The default directions are:

TranslateXDirection = 1, 0, 0
TranslateYDirection = 0, 1, 0
TranslateZDirection = 0, 0, 1

This means that TranslateXFunction will move right by default, TranslateYFunction up and TranslateZFunction forward, which is also why TranslateXFunction and so on bear their names. If you define other directions, then simply think of the three functions and associated directions as three independent ways to move the object in that direction.

TranslateXFunction = Formula
TranslateYFunction = Formula
TranslateZFunction = Formula
These define the functions to move the object into the respective direction. The Formula needs to return the amount of meters to move from the initial position. The X, Y and Z parameters in the respective direction are multiplied by the result of Formula, so you could for example either multiple the formula by 2 or the direction by 2 if you want to double the speed of movement.

RotateXDirection = X, Y, Z
RotateYDirection = X, Y, Z
RotateZDirection = X, Y, Z
These define the directions for the RotateXFunction, RotateYFunction and RotateZFunction, respectively. The default directions are:

RotateXDirection = 1, 0, 0
RotateYDirection = 0, 1, 0
RotateZDirection = 0, 0, 1

This means that RotateXFunction will rotate around the x-axis by default, RotateYFunction around the Y-axis, and RotateZFunction around the z-axis, which is also why RotateXFunction and so on bear their names. If you define other directions, then simply think of the three functions and associated directions as three independent ways to rotate the object.

RotateXFunction = Formula
RotateYFunction = Formula
RotateZFunction = Formula
These define the functions to rotate along the respective direction in counter-clockwise order. The Formula needs to return the angle by which to rotate in radians. The order in which the rotations are performed is: RotateXFunction (first), RotateYFunction (then) and RotateZFunction (last). If you use more than one rotation function at a time, bear this order in mind. If necessary, overwrite the default directions for the rotations if you need a different order.

RotateXDamping = NaturalFrequency, DampingRatio
RotateYDamping = NaturalFrequency, DampingRatio
RotateZDamping = NaturalFrequency, DampingRatio
These define damping for the corresponding functions. If not used, damping will not be performed. NaturalFrequency is a non-negative value corresponding to the angular frequency of an assumed undamped oscillator in radians per second. DampingRatio is a non-negative value indicating the type of damping. Values between 0 and 1 represent under-damping, 1 represents critical damping, and values above 1 represent over-damping.

TextureShiftXDirection = X, Y
TextureShiftYDirection = X, Y
These define the directions for the TextureShiftXFunction and TextureShiftYFunction, respectively. The default directions are:

TextureShiftXDirection = 1, 0
TextureShiftYDirection = 0, 1

This means that TextureShiftXFunction will shift the texture right by default, and TextureShiftYFunction down, which is also why TextureShiftXFunction and so on bear their names. If you define other directions, then simply think of the two functions and associated directions as two independent ways to shift textures on the objects.

TextureShiftXFunction = Formula
TextureShiftYFunction = Formula
These define the functions to shift the texture in the respective direction. The texture is shifted by the return value of Formula in texture coordinates. The integer part of the result is ignored, and a fractional part of 0.5 represents moving the texture half way. The SetTextureCoordinate commands in the object file define the coordinates, which are then added the outcome of these formulas.

TrackFollowerFunction = Formula
This defines the function which moves an object along the path of Rail 0. Formula must return a distance in meters, for which the object is then moved, respecting the curves and height changes of Rail 0.

TextureOverride = Value
Value = Timetable: All faces will show the timetable bitmap as set up by CSV/RW routes.
Value = None: The original textures will be displayed on the faces (default behavior).

RefreshRate = Seconds
This defines the minimum amount of time that needs to pass before the functions are updated. A value of 0 forces the functions to be updated every frame. Please note that objects outside of the visual range might be updated less frequently regardless of this parameter. Use RefreshRate when you don't need a perfectly smooth animation (in order to optimize performance), or when you deliberately want the object to be only updated in fixed intervals.


    OpenBVE 2 compatibility note:
During the development of OpenBVE (v0.9) and during the development of the animated object format, there were certain commands in existance ending in RPN, such as TranslateXFunctionRPN. These commands never made it into any official release (v1.0) and were thus never meant to be used outside of development environments. While they are still available undocumentedly, they will be removed for OpenBVE 2. If you are using these commands, please get rid of them as soon as possible.
 


● About the formulas
First of all, infix notation, which is what you can enter for Formula, is converted into functional notation. Thus for every infix notation, there is a corresponding functional notation. Some functions do not have an infix operator and can thus only be entered in functional notation. For operators, precedence plays an important role. You can use parantheses to override the order of precedence just as in any usual mathematical formula. Names of functions are case-insensitive.

    Please note that if the result of any mathematical operation or function would be infinity, indeterminate or non-real, 0 is returned. Numeric overflow is not prevented, so you need to take that into account yourself.  


■ 3. List of infix notation operators
● Basic arithmetics
InfixFunctionalDescription
a + bPlus[a,b, ...]Represents addition
a - bSubtract[a,b]Represents subtraction
-aMinus[a]Negates the number
a * bTimes[a,b,...]Represents multiplication
a / bDivide[a,b]Represents division

● Comparisons
All comparisons return 1 for true and 0 for false.

InfixFunctionalDescription
a == bEqual[a,b]True (1) if a equals b
a != bUnequal[a,b]True (1) if a does not equal b
a < bLess[a,b]True (1) if a is less than b
a > bGreater[a,b]True (1) if a is greater than b
a <= bLessEqual[a,b]True (1) if a is less than or equal to b
a >= bGreaterEqual[a,b]True (1) if a is greater than or equal to b

● Logical operations
All operations treat 0 as false and any other value as true, and return 1 for true and 0 for false.

InfixFunctionalDescription
!aNot[a]True (1) if a is false
a & bAnd[a,b]True (1) if both a and b are true
a | bOr[a,b]True (1) if any of a or b are true
a ^ bXor[a,b]True (1) if either a or b is true

● Operator precedence
From highest precedence to lowest. Operators of same precedence are evaluated left to right in the order in they occur in the formula.

a[...]
- (Minus)
/
*
+, - (Subtract)
==, !=, <, >, <=, >=
!
&
^
|

    Please note that some combinations of prefix and infix operators are not recognized. For example a*-b is not accepted. Use a*(-b) or -a*b instead.  


■ 4. List of functions
● Basic arithmetics
FunctionDescription
Reciprocal[x]Returns the reciprocal, equal to 1/x
Power[a,b,...]Returns a raised to the bth power. b must be a non-negative number. For consistency, Power[0,b] always returns 1, even in the degenerate case Power[0,0], and a being negative always returns 0. Adding more arguments will create a chain. Power[a,b,c] will return abc.

● Numeric functions
FunctionDescription
Quotient[a,b]Divides a by b and rounds the result down, equal to Floor[a/b].
Mod[a,b]Returns the remainder of dividing a by b, equal to a-b*Floor[a/b].
Min[a,b,...]Returns the smallest of the terms.
Max[a,b,...]Returns the largest of the terms.
Abs[x]Returns the absolute value.
Sign[x]Returns the sign of x, which is either -1, 0 or 1.
Floor[x]Rounds down to the nearest integer.
Ceiling[x]Rounds up to the nearest integer.
Round[x]Rounds to the nearest integer. Numbers ending in .5 are rounded to the nearest even integer.
random[Minimum, Maximum]Returns a new random floating-point number between Minimum and Maximum.
randomInt[Minimum, Maximum]Returns a new random integer between Minimum and Maximum.

● Elementary functions
FunctionDescription
Exp[x]The exponential function, or e to the xth power.
Log[x]The natural logarithm, to base e.
Sqrt[x]The square root.
Sin[x]The sine (input in radians).
Cos[x]The cosine (input in radians).
Tan[x]The tangent (input in radians).
ArcTan[x]The inverse tangent (output in radians).

● Conditionals
FunctionDescription
If[cond,truevalue,falsevalue]If cond is != 0, returns truevalue, otherwise falsevalue


■ 5. List of variables
● Primitives
VariableDescription
valueThe value returned by the function in the last evaluation. At the beginning of the simulation, this is 0.
deltaThe time difference since the last evaluation of the function in seconds. Please note that there is no guaranteed time that elapses between successive function calls.
currentStateReturns the current numerical state of the object.

● Time and camera
VariableDescription
timeThe current in-game time measured in seconds since midnight of the first day.
cameraDistanceThe non-negative cartesian distance measured from the object to the camera in meters.
cameraModeReturns 0 if the camera is currently in a 2D or 3D cab, 1 otherwise.

● Trains
Generally, objects attached to a particular train and car return values for that train and car, unless stated otherwise. For scenery objects, the reference is the driver's car of the nearest train (not necessarily the player's train).

In some of the following variables, carIndex has the following meaning: 0 is the 1st car from the front, 1 is the 2nd car from the front, etc., while -1 is the 1st car from the rear, -2 is the 2nd car from the rear, etc. In general, car indices from -cars to cars-1 represent existing cars, where cars is the number of cars the train has, while values outside of this range represent non-existing cars. As all trains have at least 1 car, indices -1 and 0 are guaranteed to exist for any train.

● Trains (general)
VariableDescription
carsThe number of cars the train has.
speedThe signed actual speed of the current car in m/s. Is positive when the train travels forward, and negative when the train travels backward.
speed[carIndex]The signed actual speed of the car carIndex in m/s. Is positive when the train travels forward, and negative when the train travels backward.
speedometerThe signed perceived speed of the current car in m/s as it would appear to a speedometer on wheel slip and wheel lock.
speedometer[carIndex]The signed perceived speed of the car carIndex in m/s as it would appear to a speedometer on wheel slip and wheel lock.
accelerationThe actual acceleration of the current car in m/s².
acceleration[carIndex]The actual acceleration of the car carIndex in m/s².
accelerationMotorThe acceleration which the motor of the first motor car currently generates in m/s².
accelerationMotor[carIndex]The acceleration which the motor of the car carIndex currently generates in m/s².
distanceThe non-negative cartesian distance measured from the object to the closest car in meters. Only meaningful for scenery objects.
distance[carIndex]The non-negative cartesian distance measured from the object to the car carIndex in meters, or 0 if the car does not exist. Only meaningful for scenery objects.
trackDistanceThe signed track distance measured from the object to the closest end of the nearest train in meters. Is positive when the train is in front of the object, negative when behind, and zero when the object lies between the ends of the train.
trackDistance[carIndex]The signed track distance measured from the object to the car carIndex of the nearest train in meters. Is positive when the center of the car is in front of the object, and negative if behind. Returns 0 if the car does not exist. Only meaningful for scenery objects.
destinationThe currently set destination for this train. (Set via Track.Destination or the plugin interface)

● Trains (brake)
VariableDescription
mainReservoirThe current pressure in the main reservoir in this car, measured in Pa.
mainReservoir[carIndex]The current pressure in the main reservoir in car carIndex, measured in Pa.
emergencyReservoirThe current pressure in the emergency reservoir in this car, measured in Pa.
emergencyReservoir[carIndex]The current pressure in the emergency reservoir in car carIndex, measured in Pa.
brakePipeThe current pressure in the brake pipe in this car, measured in Pa.
brakePipe[carIndex]The current pressure in the brake pipe in car carIndex, measured in Pa.
brakeCylinderThe current pressure in the brake cylinder in this car, measured in Pa.
brakeCylinder[carIndex]The current pressure in the brake cylinder in car carIndex, measured in Pa.
straightAirPipeThe current pressure in the straight air pipe in this car, measured in Pa.
straightAirPipe[carIndex]The current pressure in the straight air pipe in car carIndex, measured in Pa.

● Trains (doors)
VariableDescription
doorsThe state of the doors. Returns 0 if fully closed, 1 if fully opened, or any intermediate value, biasing doors that are in a more open state.
doors[carIndex]The state of the doors of car carIndex. Returns 0 if fully closed, 1 if fully opened, or any intermediate value, biasing doors that are in a more open state.
leftDoorsThe state of the left doors. Returns 0 if fully closed, 1 if fully opened, or any intermediate value, biasing doors that are in a more open state.
leftDoors[carIndex]The state of the left doors of car carIndex. Returns a value between 0 and 1, biasing doors that are in a more open state, or -1 if the car does not exist.
rightDoorsThe state of the right doors. Returns 0 if fully closed, 1 if fully opened, or any intermediate value, biasing doors that are in a more open state.
rightDoors[carIndex]The state of the right doors of car carIndex. Returns a value between 0 and 1, biasing doors that are in a more open state, or -1 if the car does not exist.
leftDoorsTargetThe anticipated target state of the left doors. Returns either 0 (closed) or 1 (opened).
leftDoorsTarget[carIndex]The anticipated target state of the left doors of car carIndex. Returns either 0 (closed) or 1 (opened).
rightDoorsTargetThe anticipated target state of the right doors. Returns either 0 (closed) or 1 (opened).
rightDoorsTarget[carIndex]The anticipated target state of the right doors of car carIndex. Returns either 0 (closed) or 1 (opened).
leftDoorsButtonThe state of the left doors button. Returns either 0 (released) or 1 (pressed).
rightDoorsButtonThe state of the right doors button. Returns either 0 (released) or 1 (pressed).

● Trains (miscellaneous)
VariableDescription
reverserNotchThe state of the reverser, which is either -1 (backward), 0 (neutral), or forward (1).
powerNotchThe current power notch, i.e. 0 for N, 1 for P1, 2 for P2, 3 for P3, etc.
powerNotchesThe amount of power notches the train has.
brakeNotchThe current brake notch.
  For trains without the automatic air brake: 0 for N, 1 for B1, 2 for B2, 3 for B3, etc.
  For trains with the automatic air brake: 0 for REL, 1 for LAP and 2 for SRV.
brakeNotchesThe amount of brake notches the train has. For trains with the automatic air brake, this returns 2.
brakeNotchLinearA combination of brake notch, hold brake and emergency brake.
  For trains without the automatic air brake and without hold brake: 0 for N, 1 for B1, 2 for B2, 3 for B3, etc., up to BrakeNotches+1 for EMG.
  For trains without the automatic air brake but with hold brake: 0 for N, 1 for HLD, 2 for B1, 3 for B2, 4 for B3, etc., up to BrakeNotches+2 for EMG.
  For trains with the automatic air brake: 0 for REL, 1 for LAP, 2 for SRV or 3 for EMG.
brakeNotchesLinearThe highest value returned by brakeNotchesLinear.
  For trains without the automatic air brake and without hold brake, this is BrakeNotches+1.
  For trains without the automatic air brake but with hold brake, this is BrakeNotches+2.
  For trains with the automatic air brake, this returns 3.
locoBrakeThe current Loco Brake notch.
locoBrakeNotchesThe amount of Loco Brake notches the train has.
emergencyBrakeWhether the emergency brake is currently active (1) or not (0).
hasAirBrakeWhether the train has the automatic air brake (1) or not (0).
holdBrakeWhether the hold brake is currently active (1) or not (0).
hasHoldBrakeWhether the train has a hold brake (1) or not (0).
constSpeedWhether the const speed system is currently active (1) or not (0).
hasConstSpeedWhether the train has a const speed system (1) or not (0).
hasPluginWhether the train uses a plugin (1) or not (0).
pluginState[i]The state of the ith plugin variable, returning an integer depending on the plugin. Is the same as atsi in the panel2.cfg.
FrontAxleCurveRadius[carIndex]Returns the curve radius at the front axle position of car carIndex.
RearAxleCurveRadius[carIndex]Returns the curve radius at the rear axle position of car carIndex.
CurveCant[carIndex]Returns the cant value for car carIndex.
Pitch[carIndex]Returns the pitch value for car carIndex.
OdometerReturns a signed number representing the distance in meters travelled by the current car.
Odometer[carIndex]Returns a signed number representing the distance in meters travelled by car carIndex.
KlaxonReturns the currently playing horn (if any) as follows: (0) No horns are playing (1) The primary horn is playing (2) The secondary horn is playing (3) The music horn is playing. Note If multiple horns are playing, the lowest value will be returned.
PrimaryKlaxonReturns 1 if the primary horn is currently playing, 0 otherwise.
SecondaryKlaxonReturns 1 if the secondary horn is currently playing, 0 otherwise.
MusicKlaxonReturns 1 if the music horn is currently playing, 0 otherwise.

If pluginState[i] is used with the built-in safety systems ATS and ATC, the following mappings for i apply:

iEnglish日本語Return valuespluginState[271]Meaning
256ATSATS0 (unlit) or 1 (lit)0ATC not available
257ATS RUNATS 作動0 (unlit), 1 (lit) or 2 (flashing)10 km/h
258ATS RUNATS 作動0 (unlit / non-flashing), 1 (lit / flashing)215 km/h
259P POWERP 電源0 (unlit) or 1 (lit)325 km/h
260PTN APPROACHパターン接近0 (unlit) or 1 (lit)445 km/h
261BRAKE RELEASEブレーキ開放0 (unlit) or 1 (lit)555 km/h
262BRAKE APPLYブレーキ動作0 (unlit) or 1 (lit)665 km/h
263ATS PATS-P0 (unlit) or 1 (lit)775 km/h
264FAILURE故障0 (unlit) or 1 (lit)890 km/h
265ATCATC0 (unlit) or 1 (lit)9100 km/h
266ATC POWERATC 電源0 (unlit) or 1 (lit)10110 km/h
267ATC SRVATC 常用0 (unlit) or 1 (lit)11120 km/h
268ATC EMGATC 非常0 (unlit) or 1 (lit)12ATS is active
269CONST SPEED定速0 (unlit) or 1 (lit)
270EBEB0 (unlit) or 1 (lit)
271ATC speed indicator0 - 12, see table on the right

● Sections (signalling)
The section context is defined when the object is placed using Track.SigF.

VariableDescription
sectionThe value of the section aspect currently shown.
If this variable is used outside of a Track.SigF context, the behavior is currently undefined and subject to change.


■ 6. Performance
There are certain kinds of animation which are less expensive, and others which are more. Also, the underlying object plays a significant role. If you want to design your animated objects with as best performance as possible for future releases of OpenBVE, take a look at the following performance table:

Animation Object Performance
State changesHas only opaque facesGood
State changesHas partially transparent facesModerate
TranslationHas only opaque facesGood
TranslationHas partially transparent facesModerate
RotationHas only opaque facesGood
RotationHas partially transparent facesBad
Texture shiftsHas only opaque facesBad
Texture shiftsHas partially transparent facesBad

Performance is generally better if the result of a function only infrequently changes. So, even if you set the RefreshRate parameter to zero, performance is generally better if the outcome of your formula is constant over longer periods of time. On the other hand, if it changes every frame, performance is generally worse.

Generally, you should avoid using animation with partially transparent faces and stick to opaque faces when possible. Also, try to avoid texture shifts, and consider using state changes or translation where possible.


■ 7. Tips

  Generally speaking, try to keep the complexity of functions as low as possible. This is not the most critical aspect, though, as most of the performance impact will result from applying the results of a function, e.g. rotating the object, and not evaluating the function.

  Use the RefreshRate parameter when possible to optimize performance. Usually, you can use this parameter when you don't need a smooth animation, or when you deliberately want the functions to only update in intervals.

  Don't use functions which always evaluate to the same constant. For example, don't use RotateXFunction = 3.14159, but rotate the underlying CSV/B3D/X object directly.

  State changes are very cheap as long as the state doesn't actually change in between two executions of the StateFunction. If a change occurs, this is a relatively expensive operation, though.

  Try to optimize out if conditions. Especially try to avoid nested if functions. Often, there is an elegant mathematical solution.

  Certain functions, e.g. Exp, Sin, Cos, etc., are relatively expensive. Use them only if absolutely necessary for an effect. Don't include unnecessary operations. For example, the result of StateFunction is automatically rounded toward the nearest integer, so don't apply an additional explicit Round.

  When working with car objects, bear in mind that some variables have an optional car index. You should use this index if you want to query the state of a particular car (that is, not necessarily the one the object is attached to). If, however, you just want to query the value of the particular car the object is attached to, use the variable without the index. For scenery objects, you should not generally use car indices as you can't be sure how many cars the queried train has.


■ 8. Example functions

● Blinking light
Template for a blinking light:
States = OBJECT0, OBJECT1
StateFunction = value == 0
RefreshRate = SECONDS

● Rotating wheel
Template for the code used in an exterior car object:
States = OBJECT
RotateXFunction = value + delta * speedometer / RADIUS_OF_THE_WHEEL

● Cycling through a list of objects
Template for objects that are to be cycled through:
States = OBJECT0, OBJECT1, OBJECT2, ...
StateFunction = mod[value + 1, AMOUNT_OF_OBJECTS]
RefreshRate = TIME_PER_OBJECT

● Signal (3-aspect) for Track.Section(0; 2; 4)
States = RED_OBJECT, YELLOW_OBJECT, GREEN_OBJECT
StateFunction = section / 2

● Employing an approach-controlled delay in signals
If you want to create a signal that keeps being red until the train approaches it to some distance, then counts down a timer before it changes aspect to green, please refer to
this post on the forum for a detailed explanation. Once you understand the concepts, you can use this code template:
Template for an approach-controlled delay in a signal with two aspects:
States = RED_OBJECT, GREEN_OBJECT
StateFunction = if[trackDistance>DISTANCE | section==0, 0, min[value + 0.5*delta/DELAY, 1]]
Template for an approach-controlled delay in a signal with any number of aspects:
States = RED_OBJECT, ..., GREEN_OBJECT
StateFunction = if[trackDistance>DISTANCE | section==0, 0, if[value<0.5, value + 0.5*value/DELAY, section]]

■ 9. Formal Grammar
The formal grammar for the language may not match up perfectly with the implimentation included in OpenBVE. An example is a*-b which is valid under the grammar but the parser rejects it.
<expression>        ::= <xor_expression> "" <expression>     | <xor_expression>
<xor_expression>    ::= <or_expression>  "^" <xor_expression> | <or_expression>
<or_expression>     ::= <not_expression> "|" <or_expression>  | <not_expression>

<not_expression>    ::= "!" <equal_expression> | <equal_expression>

<equal_expression>  ::= <plus_expression> "==" <equal_expression> | <plus_expression> "!=" <equal_expression> |
                        <plus_expression> ">"  <equal_expression> | <plus_expression> "<"  <equal_expression> |
                        <plus_expression> "<=" <equal_expression> | <plus_expression> "<=" <equal_expression> | <plus_expression>

<plus_expression>   ::= <times_expression> "+" <plus_expression>  | <times_expression> "-" <plus_expression> | <times_expression>

<times_expression>  ::= <divide_expression> "*" <times_expression>  | <divide_expression>
<divide_expression> ::= <minus_expression>  "/" <divide_expression> | <minus_expression>

<minus_expression>  ::= "-" <function_call> | <function_call>
<function_call>     ::= <name> "[" <expression> ("," <expression>)* "]" | <term>

<term>   ::= "(" <expression> ")" | <name> | <number>
<number> ::= <digit>*
<name>   ::= <letter> (<letter> | <digit>)*

<letter> ::= "A" | "B" | "C" | "D" | "E" | "F" | "G" | "H" | "I" | "J" | "K" | "L" | "M" |
             "N" | "O" | "P" | "Q" | "R" | "S" | "T" | "U" | "V" | "W" | "X" | "Y" | "Z" |
             "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" | "i" | "j" | "k" | "l" | "m" |
             "n" | "o" | "p" | "q" | "r" | "s" | "t" | "u" | "v" | "w" | "x" | "y" | "z"
<digit>  ::= "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"