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.
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.
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.
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 }
#
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.
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.
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.
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.
+
, -
,
AND
etcEWEScript 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. |
SIN(n)
,
RANDOM([n])
, SEE()
etcA 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:
{{2.0, 270.0, 1.0, 202, 202, 202}, 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
.
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 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:
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.
And for lists with a depth of one inner list, which might be thought of as two-dimensional:
Continuing this scheme, for three dimensions, these rules follow:
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:
The above examples happen to be symmetrical. The general scheme is followed for non-symmetrical examples, though:
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)
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".
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:
|
"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.
|
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.
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 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
.
TRIGGER
, DO
etcDefinitions 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.
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).
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 } }
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.
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
.
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 }
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, (
and )
can of course be
used to force a certain order of evaluation. A simple example is z IS (2+3)*5
.
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"
.
The BNF below is automatically generated from the parser input file, and so is the most accurate statement of the language syntax available.
Here are the ones I know about. If you find more, please let me know - thanks.
DO
s must come in pairs. It would be
nice to be able to pre-define, say the moveForward action block
commonly used in "ANIMAL"
agent definitions, but it is
not possible yet.list IS
{1,2,-1,-2} * 5
fails with an unexpected type error. This can
be worked around. For example, say a IS {1,2,-1,-2}
and
list IS a * 5
instead.SEE()
function should only be re-evaluated if
something on the screen has moved. There is a minor problem with
this, and instead I've made SEE()
re-evaluate every
system.tick
which obviously slows things down.DIV
function. Tell me if you want it.PLAY(s)
and the definition
image
are limited to those that I install on the website
here. Java security would prevent the use of resources on the client
workstation (which is running the applet). I could add the
possibility of referencing URLs if required."VEGETABLE"
agents can use the SEE()
function. It would be fairly easy to restrict this.TRIGGER
statements and
action blocks within an agent without redefining the entire
agent.AGENT one { } AGENT two { } AGENT init { list IS {one, two} list.x IS 2 }
x
in both agents
one
and two
to the value
2
. This example does not work as intended at the moment,
though - lists of complex types like references need more
thought.
SEE()
seems to ignore the setting of h
if
it is negative.