EWEScript language reference


This document

Sections in this document:


Introduction

This document is intended to be the most detailed available about the language EWEScript (aside from the source code of course ;). No background information for the decisions made is provided, just quite bare statements as to what is allowed and what is not.

It is assumed that the reader is familiar with a conventional procedural language, such as C, BASIC, Fortran and so on. Explanations of standard functions such as boolean AND will have to be sought elsewhere.

For more complete examples, see my page on EWEScript examples.

Typographical conventions

The document uses some conventions to show the type of information. Unfortunately as style sheets are not yet implemented universally, the conventions may not appear on your browser (or "user agent"). Hopefully the type of information is fairly obvious from the context anyway.

Examples that form complete lines that you can type into
the applet appear like this.

Abstract mathematically presented "rules" appear like this.

And results, or "answers" to presented examples appear like this.


General

EWEScript is intended to be a definitive type of notation, as opposed to procedural or functional, so may look a little unfamiliar to users who have not experienced this paradigm before. The structure is quite simple however - definitions, for example a IS b+c, and actions, for example TRIGGER catHungry DO feedCat, are contained within agents.

Line feeds, white space and splitting lines

White space (spaces and tabs) are ignored by the EWEScript parser, and so white space can be inserted anywhere in a script without changing its meaning.

Linefeeds (or carriage returns) are used as the separators between statements. Long input lines can be split into parts by using the line continuation character \ (slash). For example:

AGENT Long {
  largeDefinition IS 78.34524465656 - anotherAgent.longDefinitionName * \
    22.1
}

Comments: #

Any information preceded by a # (hash) character will be ignored by the parser. Comments run from a hash character to the end of the line, rather like UNIX shell languages (eg /bin/sh). Comments cannot include the line continuation character \ in order to construct a multi-line comment: each comment line must start with a hash character.

Case sensitivity

EWEScript is cAse sEnSitiVE. All keywords are in UPPER CASE. The user can define definitions using mixed case characters, but references to definitions must use the same case as the original, or the reference will fail.


Definitions: IS

Definitions are the basic entities within EWEScript. They are both the program and the data. A very basic definition is

a IS b+c

Now the value of a will always be ("IS") b added to c. If b or c are changed, a will be automatically updated. This update to a might then set off some changes to the values of other definitions that depend on a. You can see that a network-like structure of dependencies can be built.

More complex definitions are possible - the data types, operators and functions that can be used are described below.

Definition names (the a in this example) can consist of letters and digits, but the first character must be a letter.


Data types

These are the basic data types in EWEScript.

Type Examples Comments
Integer 1
42
Exponential format, eg 5e4 is not implemented (see limitations). Integer values are implemented as Java Integer objects, and so have a range of just over 2 billion, from -2147483648 to 2147483647 inclusive.
Floating point 1.0
1.02
.0004
2.
Again, exponential format is not implemented (see limitations). The values are stored in Java Double objects, and so can represent 15 significant digits: roughly a range of +/-1.79769313486231570E+308.
Boolean TRUE
FALSE
Must be in UPPER CASE, as are all keywords. If a boolean is used in a multiplication, then it is translated to the equivalent value. TRUE becomes 1 and FALSE becomes 0.
String "Yes, sheep"
""
Strings must be surrounded by double quotation marks. There is no way to include a double quote (see limitations) in the string.
List {2,3}
{1, 3.2, "Larry"}
{}
{{1,2},{3,4}}
Lists are similar to "array" types in other languages, but one list can contain data with many varied types.
Undefined UNDEFINED Must be in UPPER CASE. This should never really be required in a script - it is here so that the internal state of undefined can be represented externally. It might be useful for testing. If a definition uses an expression that is UNDEFINED, the result will also be UNDEFINED. For example, a IS 23+UNDEFINED sets the value of a to UNDEFINED.
Angle 90.0 Angles are not implemented as a full data type in EWEScript as it is easier for the user to use integers and floating point definitions. However, the parts of EWE that use angles do so in a common manner. All angles provided by EWE (for example, as results from the SEE() function) will be numbers between 0 and just less than 360, in degrees. Angles are positive when moving anti-clockwise, and a heading of 0 is pointing straight up the display.

More complex types must be represented by combinations of these types, formed into lists. For example, the SEE() function returns a list including three integers, which are used to represent colour.


Operators: +, -, AND etc

EWEScript implements a few operators, which are really just sugared syntax for functions (for example, 2+3 is translated to ADD(2,3)). Some operators are infix (are placed in between two expressions), some are prefix (are placed before just one expression).

Definition types are dealt with automatically. For example, adding a floating point to an integer automatically makes the result floating point. The "casting" possible in other languages is not implemented in EWEScript (see limitations).

Operator Examples Comments
+ 1+2
(== 3)
42+2.1
(== 44.1)
Addition.
- 23-21
(== 2)
{45.2, 6} - 5
(== {40.2, 1})
-22
(== -22)
Subtraction, also Negation, when used in prefix form.
* 21*2
(== 42)
UNDEFINED * 8.2
(== UNDEFINED)
Multiplication.
/ 84/2
(== 42.0)
6 / {2.5,3}
(== {2.4, 2.0})
30/100
(== 0.3)
Division. Note that dividing two numbers will always result in a floating point number - the third example might successfully be used to calculate a percentage for example. Integer division is not currently possible (see limitations). Dividing a number by zero produces an internal representation of infinity. The behaviour of the system when a value of infinity is present is defined by the IEEE Standard for Binary Floating-Point Arithmetic, ANSI/IEEE Standard 754-1985 (IEEE, New York). There is currently no key word representing infinity (see limitations).
< 2<3
(== TRUE)
TRIGGER 4.2 < 5 DO something
"Less than". Results in a boolean value.
> 2>34.1
(== FALSE)
UNDEFINED > 7
(== UNDEFINED)
"Greater than".
<= 2<=2
(== TRUE)
29.6 <= .0002
(== FALSE)
"Less than or equal".
>= {45} >= {23}
(== {TRUE})
10.2 >= 22
(== FALSE)
"Greater than or equal".
== 42 == 42
(== TRUE)
{1,2} == {3,2}
(== {FALSE, TRUE})
"Equal to". Results in a boolean value. Note that two equals characters are required: just one equals character = has a different meaning.
!= 22 != 12
(== TRUE)
TRUE != FALSE
(== TRUE)
"Not equal to".
AND TRUE AND TRUE
(== TRUE)
2<3 AND 4.2==1
AND boolean operator.
OR TRUE OR FALSE
(== TRUE)
1==1 OR g
OR boolean operator.
NOT NOT FALSE
(== TRUE)
NOT me
NOT boolean operator.

Functions: SIN(n), RANDOM([n]), SEE() etc

A number of functions exist in EWEScript, some of which are designed for direct use by the user. A function name must be followed by a pair of brackets, which surround the arguments to the function. As with all keywords in EWEScript, the function name must be entered in UPPER CASE.

Function Examples Comments
SIN(n) SIN(0)
(== 0.0)
SIN(90)
(== 1.0)
Mathematical function Sine. Requires just one argument, which can be an integer or floating point. The units of the input number should be degrees, not radians as in other languages. As always with computers, beware of imprecision in floating point: SIN(360) will not exactly equal SIN(0).
COS(n) COS(0)
(== 1.0)
COS(-180)
(== -1.0)
Mathematical function Cosine: comments for SIN(n) apply equally here.
TAN(n) TAN(0)
(== 0.0)
Mathematical function Tangent: comments for SIN(n) apply equally here.
MOD(n,n) MOD(1,3)
(== 1)
MOD(6,6)
(== 0)
MOD(8,6)
(== 2)
Modulus function: requires two arguments, which can be integers or floating point. This function gives the remainder after division.
RANDOM([n]) RANDOM()
RANDOM(100)
Random number function: can optionally take one argument, which can be an integer or floating point. If no arguments are given, this function gives a random number between 0 and 0.999999999 - just below 1. It will never exactly give the answer 1. If an argument is given, this function gives a random number between 0 and the value of the argument. Theoretically it will never give exactly the value of the argument, but floating point rounding means that this is possible.
SUM(l) SUM({1,2})
(== 3)
SUM({{1,2},{3,4}})
(== {3,7})
SUM({1,{2,3,{4,5}}})
(== {1,{2,3,9}})
SUM({{1.5,2}})
(== {3.5})
Innermost list running sum function. This requires one argument, which must be a list. The function finds the inner-most list and runs from left to right along the list, summing up the components in that list. The summed list is replaced with the sum total, and the whole list with this replacement is returned. The examples should make this clearer. To find the "inner-most list" involves computing the nesting levels of each list. The inner list with the greatest nesting level, or "depth" is evaluated. If there are more than one lists at the deepest level, then the replacement is performed on all of them.
LENGTH(l) LENGTH({})
(== 0)
LENGTH({42,42,3.1})
(== 3)
LENGTH({1,{2,3}})
(== 2)
List length function. This requires one argument, which must be a list. This function finds the number of elements in the outer-most list.
INDEXOFMAX(l) INDEXOFMAX({1,3.4,2})
(== 2)
List index of maximum value function. This requires one argument, which must be a list. This function looks through the list, comparing values to find the maximum. The index (note list indexes in EWEScript start from 1, not 0) of this maximum value is then returned. If the maximum value exists at several points in the list, then the lowest (left-most) matching index is returned. The list must not contain any inner lists.
INDEXOFMIN(l) INDEXOFMIN({1,3.4,2})
(== 1)
List index of minimum value function. Details are as for INDEXOFMAX(l), but the minimum valued argument is searched for.
PLAY(s) d IS PLAY("chewgulp")
Play a sound function. This requires one argument, which must be a string. The sound is started when the function is evaluated, which in practise means that this function is only useful inside an action block. The sounds available are limited to those installed on the EWE website (see limitations). For the sounds currently available, see the section on resources. The function returns a dummy value which should be ignored.
SEE() view IS SEE()
Describe what an agent can see, from the point of view of the agent. This function must not be passed any arguments, and it is only useful for SEE() to be used within an "ANIMAL" agent definition. The function returns a list of sightings. A sighting is a list containing these values, in this order:
  1. Distance. A floating point value describing the distance to the object. The distance is measured in units of this agents size: "me" units.
  2. Bearing. A floating point value ranging from 0 to just under 360, describing the bearing angle in degrees of the object. A bearing, of course, takes into account this agent's heading - if this agent rotates clockwise, the bearings of its sightings will increase (unless the value goes above 360 of course!).
  3. Size. A floating point value describing the size of the object, relative to this agents size. If the object is twice as large as this agent, this value will be 2.0.
  4. Red. An integer value ranging from 0 to 255 describing the red component of the colour of the object.
  5. Green. An integer value ranging from 0 to 255 describing the green component of the colour of the object.
  6. Blue. An integer value ranging from 0 to 255 describing the blue component of the colour of the object.
For example, a result of
{{2.0, 270.0, 1.0, 202, 202, 202},
{1.7983, 90.0, 0.6188, 0, 255, 0}}

from the SEE() function could be interpreted as a view of two objects. The first is directly to the right of this agent, two "me" lengths away, exactly the same size and a dirty white colour. These observations might be interpreted as a sheep. The second is slightly nearer than the first, is directly to the left, a bit over half this agents size and is a pure green colour - maybe it is a bush. The values for colour and size are determined directly from the actual image: colour is the average colour of the image, size is the square root of the number of pixels. In both cases, transparent pixels are left out of the calculation.

Other functions also exist in EWEScript, but are probably not so useful to the user. Sugared syntax exists for the functions ADD, SUBTRACT, MULTIPLY, DIVIDE, OR, AND, EQUALITY, INEQUALITY, LESS_THAN, GREATER_THAN, LESS_THAN_OR_EQUAL, GREATER_THAN_OR_EQUAL, MINUS and NOT. Some "internal functions" also exist, currently ACTION and DEREF.

If any function is passed UNDEFINED as an argument, it will return UNDEFINED.


List processing

Lists were added to EWEScript in an attempt to add generality of representation to the language. The methods of list processing are an attempt to add computational power to the language.

All the operators described in the operators section can process lists as well as the more basic data types. All infix operators handle lists in the same way. Unary operators have a similar scheme.

Unary operators

Unary operators descend through the list, looking into inner lists, and each basic value is evaluated with the operator. In this example, unaryTest will evaluate to be {-1, {-2, -7}, -3}.

myList IS {1,{2,7},3}
unaryTest IS -myList

This might be written as the general rule:

  1. op {a, b} -> {op a, op b}

Infix operators

The general rule for list processing in infix operators is an attempt to overlay like onto like. The rules are given below. Note that the operators are missing from the result to save space.

  1. {a,b} op c -> {ac,bc}
  2. {a,b} op {c,d} -> {ac,bd}

And for lists with a depth of one inner list, which might be thought of as two-dimensional:

  1. {{a,b},{c,d}} op e -> {{ae,be},{ce,de}}
  2. {{a,b},{c,d}} op {e,f} -> {{ae,bf},{ce,df}}
  3. {{a,b},{c,d}} op {{e,f},{g,h}} -> {{ae,bf},{cg,dh}}

Continuing this scheme, for three dimensions, these rules follow:

  1. {{{a,b},{c,d}},{{e,f},{g,h}}} op i -> {{{ai,bi},{ci,di}},{{ei,fi},{gi,hi}}}
  2. {{{a,b},{c,d}},{{e,f},{g,h}}} op {i,j} -> {{{ai,bj},{ci,dj}},{{ei,fj},{gi,hj}}}
  3. {{{a,b},{c,d}},{{e,f},{g,h}}} op {{i,j},{k,l}} -> {{{ai,bj},{ck,dl}},{{ei,fj},{gk,hl}}}
  4. {{{a,b},{c,d}},{{e,f},{g,h}}} op {{{i,j},{k,l}},{{m,n},{o,p}}} -> {{{ai,bj},{ck,dl}},{{em,fn},{go,hp}}}

This scheme continues on into multiple dimensions, beyond the three dimensional examples given here. The reverse is also true in each case, for example rule 2 also means that:

  1. c op {a,b} -> {ca,cb}

The above examples happen to be symmetrical. The general scheme is followed for non-symmetrical examples, though:

  1. {{a,b},{c,d},{e,f}} op {g,h} -> {{ag,bh},{cg,dh},{eg,fh}}

All this, combined with the SUM(l) function, is very handy for applying "weightings" to lists. For example:

# SEE() returns {{distance, bearing, size, red, green, blue}, {distance...
# eg {{2.0, 270.0, 1.0, 202, 202, 202},
#     {1.798375003164016, 90.0, 0.6188082691734271, 0, 255, 0}}
view IS SEE()

# Form a weighting appropriately for one sighting. In this example, we
# are looking for something to eat, and so a large distance is bad, the
# bearing is not important, the bigger the size the better, and the
# green-ness of the object is very important.
weight IS {-0.5, 0, 0.5, 0, 1, 0}

# weightedViews is thus a list of weighting sightings
# eg {{-1.0, 0.0, 0.5, 0, 202, 0},
#     {-0.899187501582008, 0.0, 0.30940413458671356, 0, 255, 0}}
weightedViews IS view*weight

# Now sum each inner list (each sighting) to produce a list of "importance"
# results, one for each sighting.
# eg {201.5, 254.4102166330047}
summedViews IS SUM(weightedViews)

# And find the index of the sighting with the maximum "importance"
# eg 2
indexOfFavourite IS INDEXOFMAX(summedViews)

Agents: AGENT

What is an agent?

An "Agent" in the Empirical Modelling context means "an instigator of change". Agents trigger chains of cause and effect. It is useful to organise the observables (modelled by definitions) in the system using agents as a basis. Hence, in EWE, agents are containers for definitions, and can also perform actions. Note this is not the same definition as in some other branches of Computer Science, like "multi-agent systems" and "intelligent agents".

Defining agents

A definition for an agent looks like this:

AGENT Cheese {
  odour IS 2.3
}

This defines a new agent, whose name is Cheese. The definition odour is contained within this agent. Note that the line breaks must occur exactly where shown - saying AGENT Cheese { odour IS 2.3 } all one line is not possible (see limitations).

If we want some representation of this agent to appear visually on the screen, we must define the agent to be of a certain "agent type". One particular agent type is "VEGETABLE", so for example:

AGENT Prickly IS "VEGETABLE" {
  image IS "bush"
  x IS 0
  y IS 0
  h IS 0
  scale IS 1

  numberOfFlowers IS 22
}

This definition will cause an image of a bush to appear, at coordinates (0, 0). The heading, h is 0, and so the image will not be rotated, and scale is 1, so the image will be at the normal size. The definitions for image, x, y, h and scale are actually included automatically in a "VEGETABLE" agent, so there is actually no need to type them in as above. The definition numberOfFlowers, however, is not a default, and has been added as an observable of the agent Prickly.

There are a few agent types:

Agent type Comments
"DEFAULT" This agent type is what you get if none is specified, as in the example definition Cheese above. This type of agent will not appear on the screen.
"VEGETABLE" "VEGETABLE" is intended for fairly inanimate things that appear on the screen. The default image is that of a bush - for the images available, see the section on resources. A "VEGETABLE" agent includes these definitions by default:
  • image IS "bush"
  • x IS 0
  • y IS 0
  • h IS 0
  • scale IS 1

"ANIMAL" "ANIMAL" is for more animate things than "VEGETABLE". It was intended that only "ANIMAL" agents could use the SEE() function for example - however this restriction is not currently implemented (see limitations). The default image is of Larry the lamb - for the images available, see the section on resources. These are the default definitions in a "ANIMAL" agent.
  • image IS "larry"
  • x IS 0
  • y IS 0
  • h IS 0
  • scale IS 1

Any of these definitions can be redefined, with the corresponding effect on the image. Note that double quotes are required around the agent type, as it is a string. Note also that, as in the rest of EWEScript, the agent name and agent type is case-sensitive.

Short circuits

It is not actually necessary for all definitions to be contained within an agent - definitions can be typed in without a surrounding agent, for example:

test IS 3+22.5

However this is only recommended for testing purposes. Definitions entered using this "short circuit" are added to the overall parent agent, which cannot be easily referenced.

The system agent

The system agent is added whenever EWE is initialised, and so should always exist. The current definition is:

AGENT system {
  clock IS 0
  tick IS clock==clock
  TRIGGER tick PRIORITY 99 DO nextState

  nextState: clock=clock+1

  seeDepends IS {}
}

The definitions useful to the user here are system.clock and system.tick. The value of clock will increment on every "step", and tick will always have been "touched" and have the value TRUE.


Actions: TRIGGER, DO etc

Why are actions needed?

Definitions can be added or changed (redefined) in several different ways. They can be added by the system, like the system agent. They can be added by the user. Scripts can also be written that modify themselves. If this phrase makes you shudder as you think of self-modifying code (perhaps in assembler?), do not worry - this is a designed part of the language.

Why do scripts need to be able to make changes to themselves? Well, without this facility, we can only construct what I call static definitions. For example:

AGENT Left IS "ANIMAL" {
}
AGENT Right IS "ANIMAL" {
  x IS left.x + 25
}

Now the sheep Right will always be 25 units away from the sheep Left (see the section on references for an explanation of left.x). But the distance will always be 25, unless the user redefines it. We could make the value dependant on time, by using system.clock for example, and maybe use functions like SIN(n) to make a more complex relationship, but this is all a little limited.

Using actions

An action is a set of redefinitions. Actions are triggered by changes to "guarded commands", a little like Edsger Dijkstra's guarded command language of 1976.

Take a look at the following example:

AGENT TestActions {
  a IS 0
  guard IS FALSE

  TRIGGER guard DO changeA

  changeA: {
    a IS 1
  }
}

As this example stands, the value of a will stay at 0. However, if, say the user changes the value of guard to TRUE, then the action block changeA will be triggered, and a will be redefined to the value 1.

The keyword TRIGGER is meant to imply that two things must happen in order for an action block to be evaluated. "The guard must change to TRUE" is a simplification. The guard must be reevaluated, or "touched" (it might be redefined from TRUE to TRUE for example), and it must evaluate to TRUE.

Action blocks can include more than one redefinition:

AGENT TwoRedefines {
  TRIGGER someGuardOrOther DO redefines

  redefines: {
    a IS 3.2
    b IS a-4
    c IS "dinky doo"
  }
}

If an action block contains just one redefinition, it can be written in short form without the surrounding brackets:

AGENT QuickToType {
  TRIGGER go DO doIt

  doIt: x IS 69
}

Note that this is only possible with action blocks, and not AGENT definitions. Also note that a DO statement must have a correspondingly named action block within the agent currently being defined, and all action blocks within an agent definition must have a corresponding DO statement (see limitations).

Procedural redefinitions

So we can now create automatic agents. However, action blocks can only make standard "IS" redefinitions. This is no good if we want to model a procedural style change in state. For example, we cannot say a IS a + 1. Remember, IS means "will always be". So, as a definition, this is clearly meaningless: a cannot "always be" one more than itself. In fact, entering the definition a IS a + 1 will cause an error about cyclic dependencies - a definition cannot depend upon itself. It is also possible to create cyclic dependencies in more complex ways: consider a IS b together with b IS a for example.

The single equals, or procedural redefinition syntax, = has been introduced into EWEScript to solve this problem. The redefinition a = a + 1 will increment a by one, this one time only, and then the redefinition will be forgotten: the opposite of IS and "will always be". This syntax can only be used within action blocks, as it is clearly concerned with time, which standard IS definitions are not (they hold for all time).

One change can set off a stream of changes. We can now set up loops - this example defines a clock mechanism.

AGENT myClock {
  time IS 0

  TRIGGER time==time DO incrementTime

  incrementTime: {
    time = time + 1
  }
}

Priorities: PRIORITY

EWE maintains a queue of actions waiting to be performed (ie those whose guard has been "touched" and evaluate to TRUE). If the queue contains more than one action, then by default their ordering is not defined. However, in some circumstances, it is useful to be able to force a certain ordering.

For example, in the system agent, it is useful to force the incrementing of system.clock to happen at a certain time. Say that some other definitions use the value of system.clock. If the change to system.clock happens at the wrong time, some definitions could see the old value, and some the new. So it is useful to be able to force the updating to happen either before all the user defined actions have occurred, or after - preferably not somewhere in the middle.

The PRIORITY keyword can be used to assign a numeric priority to an action definition. The syntax for the entire TRIGGER definition is now TRIGGER guard [PRIORITY priValue] DO action. The square brackets [] imply that the PRIORITY section is optional. priValue can be an integer or a floating point value - the default if none is supplied is 0.5. The action with the highest priority will be performed first, running in order through to the action with the lowest priority.


References

Referencing definitions

Most of the examples so far have just used number and list literals. It is of course possible to reference other definitions to refer to their values. A simple reference could be a IS b. However, consider multiple agents, each with a definition for b. In this simple reference, b implicitly refers to the b in the current agent. To refer to a definition in another agent, we use the dot notation, for example Cheese.b, which references the value of b in the agent Cheese. We have already seen this example when we use the system agent, for example system.tick.

Referencing list elements

The other type of reference is to individual elements within lists. This is achieved by the syntax element IS a[3], which references the third item in the list a. List indexes start from 1, not 0, in an attempt to be nice to non-computer scientists. If there was no item 3 in the example, element would be set to UNDEFINED.

Multi-dimensional lists (or lists with inner lists - same thing, just a different way of looking at it) can be indexed by using the syntax shown in this example:

AGENT Lists {
  list IS {1, 2, 3, {4, {5, 6}}}
  one IS list[1]            # 1
  four IS list[4]           # {4, {5, 6}}  
  inner IS list[4,1]        # 4
  innermost IS list[4,2,1]  # 5
}

Evaluation

Precedence

This is the order of precedence used within expressions, highest first.

Type
Literals: integer, floating point, boolean, undefined, string, list.
NOT, unary negation, unary plus.
*, /.
+, -.
<, >, <=, >=.
==, !=.
AND
OR

EWE is not lazy. In some systems, an expression such as b OR c would mean that c is not evaluated if b is TRUE, as this is clearly wasted computation. This feature is not present in EWE.

Grouping brackets

Grouping brackets, ( and ) can of course be used to force a certain order of evaluation. A simple example is z IS (2+3)*5.


Resources

Currently the images available are "bush", "ewelogo", "larry", "moddinside", "puddle" and "wmb".

The sounds currently available are "baa", "chewgulp", "glug", "multibaa", "rapturebaa", "singbaa" and "zzzz".


Language definition in BNF

The BNF below is automatically generated from the parser input file, and so is the most accurate statement of the language syntax available.

script ::= ( ( agentScript | definition ) )+ <EOF>
agentScript ::= ( <CRLF> )? <AGENT> name ( <IS> stringLiteral )? "{" ( <CRLF> )? ( definition | trigger | <COMMENT> | actionSpec )* "}" ( <CRLF> | <EOF> )
trigger ::= <TRIGGER> expression ( <PRIORITY> ( floatLiteral | integerLiteral ) )? <DO> <IDENTIFIER> ( <COMMENT> | <CRLF> )
actionSpec ::= <IDENTIFIER> ":" ( ( "{" ( <CRLF> )? ( action | <COMMENT> )* "}" <CRLF> ) | action | <COMMENT> )
action ::= ( assignment | definition )
definition ::= name <IS> expression ( <CRLF> | <COMMENT> )
assignment ::= name "=" expression ( <CRLF> | <COMMENT> )
expression ::= conditionalAndExpression ( <OR> conditionalAndExpression )*
conditionalAndExpression ::= equalityExpression ( <AND> equalityExpression )*
equalityExpression ::= relationalExpression ( <EQUALITY> relationalExpression | <INEQUALITY> relationalExpression )*
relationalExpression ::= additiveExpression ( <LESSTHAN> additiveExpression | <GREATERTHAN> additiveExpression | <LESSTHANOREQUAL> additiveExpression | <GREATERTHANOREQUAL> additiveExpression )*
additiveExpression ::= multiplicativeExpression ( <PLUS> multiplicativeExpression | <MINUS> multiplicativeExpression )*
multiplicativeExpression ::= unaryExpression ( <MULTIPLY> unaryExpression | <DIVIDE> unaryExpression )*
unaryExpression ::= <PLUS> unaryExpression
| <MINUS> unaryExpression
| "NOT" unaryExpression
| primaryExpression
primaryExpression ::= primaryPrefix ( primarySuffix )?
primaryPrefix ::= literal
| listLiteral
| name
| "(" expression ")"
primarySuffix ::= "[" ( expression ) ( "," expression )* "]"
| arguments
name ::= <IDENTIFIER> ( "." <IDENTIFIER> )*
arguments ::= "(" ( expression ( "," expression )* )? ")"
literal ::= integerLiteral
| floatLiteral
| boolLiteral
| undefLiteral
| stringLiteral
integerLiteral ::= <INTEGER_LITERAL>
floatLiteral ::= <FLOAT_LITERAL>
boolLiteral ::= <TRUE>
| <FALSE>
undefLiteral ::= <UNDEFINED>
stringLiteral ::= <STRING_LITERAL>
listLiteral ::= "{" ( expression ( "," expression )* )? "}"

"Limitations", "problems", or just plain bugs

Here are the ones I know about. If you find more, please let me know - thanks.