home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Fresh Fish 8
/
FreshFishVol8-CD1.bin
/
new
/
game
/
role
/
amigamud
/
amdoc1
/
programming.txt
< prev
next >
Wrap
Text File
|
1994-08-01
|
66KB
|
1,785 lines
AmigaMUD, Copyright 1994 by Chris Gray
Programming in the AmigaMUD System
Introduction
The AmigaMUD system contains a complete programming system for a
structured programming language. This language is used to program any
MUD scenarios, quests, puzzles, etc. The AmigaMUD system contains
facilities for parsing, executing, storing, retrieving and pretty-
printing code in the language. Although intended for writing MUD
scenarios, the language can easily be used for other purposes.
Although the interpreter is reasonably efficient, it is by no means as
fast as true compiled code. Programmers should keep this in mind when
using the language - things that are practical in, say, C, may not be
practical in AmigaMUD.
AmigaMUD programming is accessed though one of the client programs,
just like playing a scenario is. Only characters with status 'wizard'
or 'apprentice' can directly access the programming features.
Characters enabled as builders can indirectly access them by writing
actions as described in the building document.
The AmigaMUD programming language does not contain any language
constructs for doing input/output, graphics, sound, etc. Instead, the
system contains a large number of builtin functions which perform
these activities. Other builtin functions perform utility operations
such as returning the length of a string, taking a substring,
returning the current time and date, etc.
Most AmigaMUD programming involves writing functions. These functions
are stored in the system database and are automatically retrieved as
needed (e.g. to call them or print them out). AmigaMUD functions can
have parameters and can return a result. They can also be recursive,
although this is quite rare in a MUD scenario.
All AmigaMUD code executes in the server program, MUDServ, on the host
computer. Lexical scanning, parsing, and editing all happen on the
client computers. Parsed functions are sent from the client to the
server as a "compiled" bytestream. Pretty-printing is done on the
host computer, and the results are sent to the client as text. Because
the server computer can never wait for any client (the human player
might get a phone call in the middle of typing in a function!), some
operations, such as input, are done differently than in other
languages.
When using the standard scenario, a wizard or apprentice can go into
programming, or wizard mode, by typing the 'wizard' command. The
prompt will change to "> ". In this mode, input typed in is assumed to
be statements in the programming language, definitions of functions,
or one of a few simple wizard-mode commands. For reference, a
statement to get out of wizard mode consists of:
Normal().
This, of course, is only valid when the prompt is "> ", i.e. when no
partial statement has been entered, and the user is not in the middle
of typing in a function definition.
If the input statement does not fit on a single line, it can be
continued on as many lines as needed. On lines other than the first,
the prompt will change to ": ". The input statement is ended with a
period, as in the above example. If the input statement is in fact an
expression, i.e. it returns a value, then the system will print out
that value. For example:
> 2 + 3.
==> 5
Not all values can be printed in any meaningful way. Function and
thing (database entity) values are printed either symbolically (if the
system can find a symbol for them) or as a hexadecimal number.
Each input line entered by any user is subject to an execution time
limit, which can be controlled by the MUD administrator (SysAdmin).
Running out of time will result in the execution being stopped, and a
traceback of the called functions being produced. Other run-time
errors, such as dividing by zero, produce a similar abort and
traceback.
The following sections of this document give some examples of AmigaMUD
programming. It is assumed that the reader already knows something
about programming, or is a fast learner - this document does not try
to teach programming to a novice. Readers are not expected to fully
understand these examples - they are just a random sampling to show
the flavour of AmigaMUD programming.
Example - the Towers of Hanoi
Here is a log of entering, printing and running the classic "Towers of
Hanoi" function. Commentary is separated off and enclosed in square
brackets. For those not familiar with this computer programming
classic, the story goes something like this:
There is a monastery near Hanoi where there live a group of monks
dedicated to keeping the universe running. They do this by
continually moving flat disks around on a set of three pegs. The
disks are round, and have a hole in the middle so that they can
slide onto the pegs. At the beginning of the universe, all 64
disks were on the left-most peg, with the widest disk on the
bottom, and the narrowest one on the top. When the monks have
succeeded in moving all of the disks to the right-hand peg, the
universe will end. They can only move one disk at a time, and must
never place a wider disk on top of a narrower one. The monks must
move the disks as fast as they can.
input> wizard
[I was initially in normal playing mode. I used the 'wizard'
command to get into wizard mode so I can program.]
> private proc hanoi(int n; string from, to, using)void:
*** expecting identifier for proc parameter name
*** expecting ',', ';' or ')' after proc parameter
: .
*** expecting 'corp' to end proc definition
Errors detected, proc "hanoi" not created.
[I accidentally tried to use a reserved word as a parameter name.
I decided to abort the entering of the function by typing a '.'.
This doesn't always work - sometimes closing parentheses or
constructs are also needed. Note that the prompt changed to ": "
while I was entering the function definition.]
> private proc hanoi(int n; string fromStr, toStr, usingStr)void:
Postman has arrived from the north.
: if n ~= 0 then
Postman heads into the passageway.
: hanoi(n - 1, fromStr, usingStr, toStr);
: Print("Move disk " + IntToString(n) + " from " + fromStr + " peg to " +
: toStr + " peg.\n");
: hanoi(n - 1, usingStr, toStr, fromStr);
: fi;
: corp
[Being in wizard mode doesn't take your character out of the
scenario, hence I still saw messages about Postman passing
through. It is often wise to go somewhere private when doing
serious wizarding, so that you aren't interrupted.]
> describe hanoi.
hanoi: proc, owner Merlin, useCount 2:
proc hanoi(int n; string fromStr, toStr, usingStr)void:
if n ~= 0 then
hanoi(n - 1, fromStr, usingStr, toStr);
Print("Move disk " + IntToString(n) + " from " + fromStr + " peg to "
+ toStr + " peg.\n");
hanoi(n - 1, usingStr, toStr, fromStr);
fi;
corp
[The exact form in which I entered the function is not preserved.
The function is stored internally as a byte stream, and is
"pretty-printed" by the system as needed. I used the 'describe'
wizard-mode command to display the function.]
> hanoi(0, "left", "right", "center").
[Now I call the function. This is a directly entered statement in
the programming language. With 'n' equal to 0, the function does
nothing, and, since it returns nothing ("void"), nothing is
printed.]
> hanoi(1, "left", "right", "center").
Move disk 1 from left peg to right peg.
[With 'n' equal to 1, there is one line of output.]
> hanoi(2, "left", "right", "center").
Move disk 1 from left peg to center peg.
Move disk 2 from left peg to right peg.
Move disk 1 from center peg to right peg.
> hanoi(3, "left", "right", "center").
Move disk 1 from left peg to right peg.
Move disk 2 from left peg to center peg.
Move disk 1 from right peg to center peg.
Move disk 3 from left peg to right peg.
Move disk 1 from center peg to left peg.
Move disk 2 from center peg to right peg.
Move disk 1 from left peg to right peg.
[The number of lines of output is equal to 2 to the power of 'n',
minus one. Thus, we need not fear for the imminent end of the
world. Even if the monks can move one disk per second, the time
needed to complete the task, 2 to the power of 64, minus one, is a
very long time indeed!]
> Normal().
input> bye
[I go back into normal mode, and exit.]
Example - Code to Draw the Streets Area
define tp_streets proc drawStreets()void:
if not KnowsEffect(nil, STREETS_ID) then
DefineEffect(nil, STREETS_ID);
GSetImage(nil, "streets");
IfFound(nil);
GShowImage(nil, "", 0, 0, 160, 100, 0, 0);
Else(nil);
GSetPen(nil, C_DARK_GREY);
GAMove(nil, 0, 0);
GRectangle(nil, 159, 99, true);
GSetPen(nil, C_LIGHT_GREY);
GAMove(nil, 0, 0);
GRectangle(nil, 69, 41, true);
GAMove(nil, 89, 0);
GRectangle(nil, 70, 41, true);
GAMove(nil, 0, 57);
GRectangle(nil, 69, 42, true);
GAMove(nil, 89, 57);
GRectangle(nil, 70, 42, true);
GSetPen(nil, C_DARK_BROWN);
GAMove(nil, 0, 0);
GRectangle(nil, 62, 35, true);
GAMove(nil, 96, 0);
GRectangle(nil, 63, 35, true);
GAMove(nil, 0, 63);
GRectangle(nil, 62, 36, true);
GSetPen(nil, C_WHITE);
GAMove(nil, 79, 0);
GRDraw(nil, 0, 35);
GAMove(nil, 0, 49);
GRDraw(nil, 62, 0);
GAMove(nil, 97, 49);
GRDraw(nil, 62, 0);
GAMove(nil, 79, 63);
GRDraw(nil, 0, 36);
GSetPen(nil, C_BLACK);
GAMove(nil, 62, 13);
VerticalDoor();
GAMove(nil, 96, 1);
VerticalDoor();
GAMove(nil, 96, 13);
VerticalDoor();
GAMove(nil, 96, 25);
VerticalDoor();
GAMove(nil, 101, 35);
HorizontalDoor();
GAMove(nil, 122, 35);
HorizontalDoor();
GAMove(nil, 143, 35);
HorizontalDoor();
GAMove(nil, 62, 65);
VerticalDoor();
GAMove(nil, 62, 77);
VerticalDoor();
GAMove(nil, 62, 89);
VerticalDoor();
GAMove(nil, 5, 63);
HorizontalDoor();
GAMove(nil, 26, 63);
HorizontalDoor();
GAMove(nil, 47, 63);
HorizontalDoor();
GSetPen(nil, C_LIGHT_GREEN);
GAMove(nil, 96, 63);
GRectangle(nil, 56, 30, true);
GSetPen(nil, C_LIGHT_GREY);
GAMove(nil, 121, 63);
GRectangle(nil, 5, 36, true);
GAMove(nil, 111, 69);
GRectangle(nil, 25, 18, true);
GSetPen(nil, C_BLUE);
GAMove(nil, 118, 74);
GRectangle(nil, 11, 8, true);
GSetPen(nil, C_GOLD);
GAMove(nil, 123, 78);
GCircle(nil, 1, true);
GAMove(nil, 124, 78);
GCircle(nil, 1, true);
GSetPen(nil, C_DARK_BROWN);
GAMove(nil, 103, 72);
GRectangle(nil, 7, 12, true);
GAMove(nil, 137, 72);
GRectangle(nil, 8, 12, true);
GSetPen(nil, C_BLACK);
GAMove(nil, 110, 74);
VerticalDoor();
GAMove(nil, 137, 74);
VerticalDoor();
GSetPen(nil, C_FOREST_GREEN);
GAMove(nil, 100, 67);
GCircle(nil, 5, true);
GAMove(nil, 135, 67);
GCircle(nil, 4, true);
GAMove(nil, 145, 68);
GCircle(nil, 5, true);
GAMove(nil, 114, 91);
GCircle(nil, 3, true);
GAMove(nil, 107, 90);
GCircle(nil, 3, true);
GAMove(nil, 99, 91);
GCircle(nil, 3, true);
GAMove(nil, 149, 80);
GCircle(nil, 3, true);
GAMove(nil, 148, 87);
GCircle(nil, 3, true);
GAMove(nil, 142, 91);
GCircle(nil, 3, true);
GAMove(nil, 133, 90);
GCircle(nil, 3, true);
Fi(nil);
EndEffect();
fi;
CallEffect(nil, STREETS_ID);
corp;
Recall from earlier documents that the "MUD" client program can cache
effects, such as the graphics for the streets area. The server program
keeps track of which active clients currently know which effects. The
code here is asking the server if the current client knows the effect
identified by 'STREETS_ID'. If it doesn't, then that effect is
defined. The effect is actually like a little program itself. A
graphics image called "streets" is requested. If it is found on the
client machine, then it is shown, otherwise a rough rendering of the
image is produced using rectangles, lines, circles, etc. Note that
this decision is made in the client program, since the server cannot
wait for the result of that decision. The rough drawing of the streets
area can be summarized as:
- fill the entire image area with dark grey
- draw four rectangles of light grey as sidewalks
- draw three smaller rectangles of dark brown as the buildings
- draw lines of white down the middle of the roads
- draw a bunch of black doors. 'VerticalDoor' and 'HorizontalDoor'
are scenario functions which in turn use effects to draw the
standard vertical and horizontal doors.
- draw a light green rectangle to be the body of the park
- draw a pair of overlapping light grey rectangles to be the
sidewalks in the park
- draw the blue fountain with the gold mermaid in it
- draw two dark brown buildings with black doors
- draw a bunch of forest green trees
After the STREETS_ID effect is defined (if it wasn't already), it is
called up. The result of all of this is that the first time a client
program needs to draw this area, there is a pause as the effect code
is sent from the server. On subsequent uses, however, the server knows
that the client knows the effect, so only a short request to run it is
sent over.
Example - Code for the 'Inventory' Verb
define tp_verbs proc v_inventory()bool:
int cash;
thing me;
me := Me();
cash := me@p_pMoney;
if cash = 0 then
Print("You are broke.\n");
else
Print("You have ");
IPrint(cash);
if cash = 1 then
Print(" bluto.\n");
else
Print(" blutos.\n");
fi;
fi;
if ShowList(me@p_pCarrying, "You are carrying:\n") then
Print("You are not carrying anything.\n");
fi;
if not me@p_pHidden and CanSee(Here(), me) then
OPrint(FormatName(me@p_pName) + " takes inventory.\n");
fi;
true
corp;
Verb0(G, "inventory", 0, v_inventory).
Synonym(G, "inventory", "inv").
Synonym(G, "inventory", "i").
This routine returns a 'bool' (true/false) value, like other direct
parsing routines. This is done so that if an error is encountered, the
system can abort the handling of user input which contains several
commands on one line, such as:
go north. go east. west. get rope. go south. tie rope to rail
If such a parsing routine returns 'false', then the successive
commands on the same line are not executed. In the case of
'inventory', there is nothing that can go wrong, so the function
always returns 'true'.
This function has some local variables. They are valid only while the
function is executing, and do not have to have names unique from local
variables in other functions.
First, a local copy of the pointer to the current character, as
yielded by calling the builtin "Me", is created. This is done since it
is quicker to reference a local variable than to call a function.
Next, the amount of money the character has is obtained. The '@'
operator takes a 'thing' on the left and a property on the right, and
returns the value of that property attached to the thing. Much more
will be said about this later. The function then prints out an
appropriate comment based on that amount. Next, it calls "ShowList",
which is another routine in the scenario which prints the names of
objects in a list, one per line, slightly indented. It is used here,
when describing a room, and when looking inside a container. If the
list is empty, it does not print anything, and returns 'true'. If the
list is not empty, it prints its second parameter (here "You are
carrying:\n") before printing the items in the list, and then returns
'false'. Thus, the code here will either print the list of objects the
character is carrying (property 'p_pCarrying' on the character) headed
by "You are carrying:\n", or will print "You are not carrying
anything.\n" if the character is empty-handed.
The next 'if' statement is a bit more complicated. It's purpose is to
allow other characters in the same room as the one doing the inventory
to see what the first is doing, if appropriate. A character can be
"hidden" (only wizards can do this in the current scenario), so that
others cannot see them or what they are doing. 'CanSee' is another
routine in the scenario, that determines whether or not there is light
in the given room. There will be no light if the room is dark, no
object in the room is emitting light, no character in the room is
emitting light (wizards can make themselves glow), and no character in
the room is carrying an object which is emitting light. 'OPrint' is a
builtin function which displays the passed string to all characters in
the same room as the active one. 'FormatName' is a builtin function
which reformats a string from the AmigaMUD internal form into a more
normal external form (e.g. turns "frog;small,green" into "small green
frog"). Thus, if the active player is not a hidden wizard, and there
is light in the current room, then all players in the current room
will see the message "XXX takes inventory.\n", where XXX is the
character's name.
The three lines after the function definition are directly executed
statements which add the verbs "inventory", "inv" and "i" to the main
grammar, as abbreviated by 'G'. 'Verb0' tells the system that there
are no arguments expected for the verb. Other possibilities are
'Verb1' as in "examine <object>" and 'Verb2' as in "put the <object>
into the <object>". The main grammar, G, is the one which is used to
parse normal user commands when not in wizard mode. Other grammars are
used for the building commands, etc. More details on parsing will be
given later.
Example - Code for the 'ShowList' Utility Routine
define t_util proc utility public ShowList(list thing lt;
string starter)bool:
int i;
thing object;
string s;
bool first;
first := true;
for i from 0 upto Count(lt) - 1 do
object := lt[i];
if not object@p_oInvisible then
if first then
first := false;
Print(starter);
fi;
Print(" " + FormatName(object@p_oName) + "\n");
fi;
od;
first
corp;
A list in AmigaMUD can be indexed like a one-dimensional array. The
builtin 'Count' takes any kind of list as its argument, and returns
the number of elements in it. This routine simply runs down the
elements in the passed list, and looks for any objects that are not
marked as invisible. It prints each such one out, indented by two
spaces, after the header passed by the caller. If no visible objects
were found, 'ShowList' returns true, else it returns false.
Example - the Code for Killing a Monster
define t_fight proc KillMonster(thing monster)void:
string monsterName;
thing me, here;
list thing lt;
int i;
me := Me();
monsterName := FormatName(monster@p_pName);
Print(monsterName + " is killed!\n");
if me@p_pHidden then
OPrint(monsterName + " is killed!\n");
else
OPrint(FormatName(me@p_pName) + " kills " + monsterName + "!\n");
fi;
me -- p_pCurrentTarget;
lt := monster@p_pCarrying;
if lt ~= nil then
here := Here();
i := Count(lt);
while i ~= 0 do
i := i - 1;
ignore DoDrop(here, monster, lt[i]);
od;
fi;
if monster ~= me then
i := monster@p_pMoney;
if i ~= 0 then
FindLoot((i + 1) / 2 + Random((i + 1) / 2));
fi;
fi;
ignore ForceAction(monster, DoUnShowIcon);
corp;
This routine is executed whenever a character in the combat area (the
"Proving Grounds") successfully vanquishes a monster. The routine
prints informative messages to the player and anyone else around,
causes all of the monster's possessions to be dropped, and gives the
character a possible monetary reward. The final line of the function
needs a bit more explaining. The builtin function 'ForceAction' forces
the indicated character or NPC to execute the function passed as the
second argument. This means that any code that affects "everyone else
in the room" will also affect whoever is killing the monster. In this
case, the routine called is responsible for removing the icon for the
monster from the displays of everyone else in the room.
Wizard-Mode Commands
Most input entered while in wizard mode is either function definitions
or statements in the programming language to be directly executed. A
few special commands are available for convenience, however. They are:
END-OF-FILE - an end-of-file condition will cause the client to
exit.
bye - this command will cause the client to exit. Note that the
server keeps track of whether a character is in wizard mode
or not, so on the next connection, it will enter wizard mode
if that is the mode the character was last in. When not in
wizard mode, special scenario actions can be taken when a
character exits the game and when the character re-enters the
game. These are not performed when in wizard mode. Thus,
things like the initial display of the current location will
not happen automatically, and it may be necessary to look
around and/or move around to get it to appear.
public
private
define/def - these three commands are used to define a symbol.
'public' will put the symbol into the global public symbol
table. 'private' will put the symbol into your private symbol
table, and 'define' will put the symbol into whatever table
you specify. There are two kinds of symbol definitions that
can be made. The first kind consists of a name for the symbol
followed by the definition of it, followed by a period.
Examples:
public G CreateGrammar().
public p_pName CreateStringProp().
private HIT_LIMIT 72.
private room_table CreateTable().
define room_table westRoom CreateThing(genericRoom).
define room_table eastRoom CreateThing(genericRoom).
The second kind of symbol definition defines a function. This
is done by using the reserved word 'proc' followed by the
function definition. See the previous example sections for
some of these. They will be discussed in more detail later.
delete/del <table-name> <symbol> - this command will delete the
given symbol from the given table, if it can. It is similar
to running the 'DeleteSymbol' builtin. <table-name> can be
"private" or "public" as well as the name of a table.
use <table-expression> - this command adds the specified symbol
table to the set of currently in-use tables. It is equivalent
to calling the 'UseTable' builtin. See the discussion of
tables in the 'Building' document.
unuse <table-expression> - this command removes the specified
symbol table from the set of currently in-use tables. It is
equivalent to calling the 'UnUseTable' builtin.
source <filename> - this command causes the contents of the named
file (on the client machine) to be read and processed. Files
sourced in this way can in turn contain other 'source'
commands. Doing this allows a large scenario to be split up
into a number of logical (and smaller) pieces. The filename
can be any legal AmigaDOS file path. Examples:
source st:all.m
source df0:AmigaMUD/examples/speech.m
When using the "MUD" client program, the 'Source' menu item is
equivalent to the 'source' command. Do not try to switch
between wizard mode and normal mode while sourcing files,
however, since the operation is asynchronous and will
probably not occur when you want it to.
describe/desc/d <expression>. - this command prints out
information about the value of the requested expression. It is
the wizard mode command most often used interactively. The
output will depend on the type of the expression:
void - a void value, i.e. no value, is printed as '<VOID>'
status - a status value is printed symbolically, as
'succeed', 'continue' or 'fail'
character - output for a character value is quite lengthy.
It consists of:
- the character's sponsor - this is the character
who promoted this character to its current
status, if any.
- the character's current location. This will be
the name of the location (room) the character
is in, if that room has a name, and a table
containing that name is in-use. Otherwise it
will just be '<THING>'.
- the character's input action. This is the action
that all non-wizard-mode text input from the
player is passed to. It is normally some
general scenario parsing routine. The output
will be the name of the function if a table
containing that function is in-use, otherwise
it will be '<ACTION>'.
- the character's raw key action. This is the
action which is called to process raw key
events occuring when the player hits a special
key, such as 'HELP', or a numeric keypad key.
- the character's mouse down action. This is the
action which is called to process left-mouse-
button hits over identified regions of the
graphics window.
- the character's button action. This is the
action which is called to process button hits
done with the mouse. Button-hits are clicks on
scenario-created "mouse-buttons" in the
graphics window.
- the character's idle action. This is the action
which is called when the player leaves the
game when not in wizard mode.
- the character's active action. This is the
action which is called when the player re-
enters the game not in wizard mode. It is
often used to do a 'look around' to establish
the display for the current location.
- the character's status. This is one of:
- normal
- apprentice
- wizard
- the character's usecount. This indicates the
number of references to the character data
structure currently contained in the database.
The character cannot be deleted if this number
is non-zero. There will always be a reference
to the character from the 'Characters' symbol
table, and there will be another one resulting
from executing this command, and those two do
not count towards the total, but do show up.
- the character's current non-wizard-mode prompt
- the character's current password. This will only
be displayed for SysAdmin.
- if the character is currently in wizard mode,
then '(in wizard mode)' is displayed
- if the character is a new character, i.e. has
not yet connected and been initialized, then
'(new player)'.
- the character's "thing" is displayed. This is
where all other changeable properties of the
character are stored. See the section here on
thing output for details.
bool - a boolean value is printed as 'true' or 'false'
int - an integer value is printed in decimal
string - a string value is printed in quotes, with special
characters escaped as in source form
thing - the output for a thing consists of a header
section, showing the fixed values that make up a
thing, followed by some number of property-value pairs
which are the contents of the thing. The fixed header
contains:
- the thing's parent. This is the thing which this
thing starts inheriting properties from.
- the thing's owner. This is the character who
currently owns the thing. When a thing is
created, it's owner is the effective
character at the time.
- the thing's usecount. This is the number of
references to the thing from other entities in
the database. If this count goes to zero, then
the thing can be destroyed.
- the thing's property count. This is the count of
the number of properties attached to the
thing. This does not count any properties that
may be inherited from ancestors.
- the thing's status. This is one of:
ts_private - only the owner of the thing can
examine or change it
ts_readonly - only the owner of the thing can
change it, but anyone can examine it
ts_wizard - any character with wizard status
(or code running with that status) can
change the thing, and anyone can examine
the thing
ts_public - anyone can change or examine the
thing
The contents of the thing, i.e. its properties, are
then displayed, indented two spaces. Each property
consists of the property, a colon and the value of
that property. If the property is defined in any in-
use table, then the name of the property is printed,
otherwise '<PROPERTY>' is printed. The value of the
property is printed much as being described here,
except that things are not shown expanded, but are
shown as a name, if one is found in an in-use table,
or as '<THING>'. Note that properties not known to the
current character are not displayed, unless the
current character is SysAdmin. Thus, adding more
tables to the "in-use" list can cause more properties
to be displayed on things. However, if a property is
not publically exported by the wizard who created it,
only that wizard and SysAdmin can see its value.
action - actions, or functions, or procedures, are printed
with a short header describing the action. This header
contains:
- the owner of the function. This is the character
who defined it.
- the usecount of the function. An action cannot
be deleted unless this count goes to zero.
Following this header is the definition of the
function, as pretty-printed by the system. For builtin
functions, which are pre-implemented, no function body
is shown. Also, if the owner of the function has made
it available in some public symbol table, but has not
marked the function itself as "public", the body is
not shown, unless it is SysAdmin who is looking.
table - only a header is printed for a table. The symbols
in the table can be displayed using the 'ShowTable'
builtin. The header for a table contains:
- the owner of the table
- the usecount of the table
- the number of entries in the table
grammar - a grammar is described much the same as a table.
The words in the grammar can be displayed using the
'ShowWords' builtin. The header for a grammar
contains:
- the owner of the grammar
- the usecount of the grammar
- the number of words in the grammar
lists - AmigaMUD has three kinds of lists: lists of
integers, lists of things, and lists of actions. Each
is displayed as a list of values enclosed in braces.
Integers are shown directly in decimal, and things and
actions are shown as a symbol, if one is found in the
in-use tables, or as '<THING>' or '<ACTION>'.
properties - properties are displayed symbolically if a
symbol for them is found in the set of in-use tables,
or just as '<PROPERTY>'.
thing-status - a thing status (as returned by the builtin
'GetThingStatus') is displayed as one of:
ts_private
ts_readonly
ts_wizard
ts_public
edit/ed/e <function-name> - this command is used to interactively
edit a function. Only the body of a function can be changed -
its header can only be changed by deleting the function and
recreating it. Editing can only be done when using the "MUD"
client program, either locally or remotely, or when using the
"SMUD" client program locally. "SMUD" will always use an
external editor, as indicated by your "EDITOR" environment
variable, and "MUD" will use either an internal one or an
external one, depending on the "Editor" menu setting. See the
"MUD" document for details on how to use the internal editor.
When the editing is done, the AmigaMUD programming language
parser attempts to "compile" the function. This can fail,
because of syntax or other errors, in which case the function
is left unchanged. With the "MUD" internal editor, the errors
are pointed out one at a time and the user can resubmit the
function at any point. When using an external editor, the user
can re-issue the 'edit' command, without giving a function
name, and will be left editing the file as it was when it was
submitted for compiling. This cycle can be repeated until the
function compiles, or the user gives up.
replace <function-definition> - this command can be used, even
when not using the "MUD" or "SMUD" client programs, to change
the body of a function. The entire function must be re-
entered, including its header. E.g.
> private proc doit()void:
: Print("Hello\n");
: corp;
>
> replace doit()void:
: int i;
: for i from 1 upto 10 do
: Print("i = " + IntToString(i) + "\n");
: od;
: corp;
>
This kind of editing is expected to be most useful in
conjunction with a terminal program which can do an ASCII-put
from a file on the remote machine. As with function editing,
the header of the function cannot be changed.
Data Types
There are a number of data types in the AmigaMUD programming language.
Not all are useable in all circumstances. The types are:
void - this is not really a type. It is used as a function return
type to indicate that the function does not return a value
(and hence is actually a procedure and not a function). It is
also the type returned by statements, such as a 'for' loop.
nil - this also is not really a type. It is the type of the
reserved word 'nil', which represents a non-value for things,
actions, tables, grammars and lists. This allows values of
those types to be tested for validity. No other use of this
type can occur.
status - this type is a three-valued type, with values 'succeed',
'fail' and 'continue'. The interpretation of these three
values is at the discretion of the programmer, but a number of
builtin functions, such as 'FindName', return status values
with fixed interpretations on the values. It is suggested that
programmers use similar interpretations to avoid confusion:
'succeed' - the operation has completed successfully
'fail' - the operation has failed
'continue' - the operation can be continued
character - this type represents a reference to a player
character. It is used by a few builtin functions, such as
'Owner', 'Character', etc. Such a reference is not equivalent
to a reference to the character thing, such as is returned by
the 'Me' builtin. Builtins 'CharacterThing' and
'ThingCharacter' can be used to return one from the other.
bool - this type is a two-valued type, with values 'true' and
'false'. It is the result of a comparison, and is the required
type for the condition in an 'if' construct or 'while'
statement. It is also used with the parsing builtins.
int - this type is a signed 32 bit integer. In the programming
language, integers can be entered in decimal, hexadecimal,
octal, or binary. Only decimal conversions are provided as
builtins.
string - this type represents a character string. The current
implementation limits strings to about 4000 characters in
length. Empty strings are allowed. In the programming
language, strings are surrounded by quotation marks (") and
may contain escapes for some non-printable characters.
thing - this type represents a pointer to a thing. Things are the
basic database entity used to represent concepts such as
rooms and objects. There is a thing associated with each
player character or NPC. To the programmer, a thing is just a
set of attribute-value pairs. The attributes are properties
defined in the database by programmers, and their values can
be actions (functions), strings, integers, references to other
things, etc. Each thing also has an owner (the character who
currently owns it), and a parent (the thing, if any, to
inherit other properties from).
action - actions are just functions or procedures. In AmigaMUD
they are first-class objects in that they can be stored in the
database, passed as parameters, and called indirectly. When a
function is called directly by another, the types and number
of the parameters and result are checked during the
compilation of the calling function. When a function is called
indirectly at runtime, this checking must be done dynamically,
after the called function has been identified. Thus, there can
be function-calling errors at runtime. Also, several builtins
take actions as parameters, and they check the parameters and
result of such actions at runtime.
table - a table is a symbol table. It is a mapping from strings
(the symbols) to their values. Such tables are dynamic
entities and can be created, manipulated and destroyed at
runtime. They are stored in the database along with things,
properties, actions, etc. Since tables are values, it is
possible to have a symbol in a table whose value is another
table. This allows the construction of trees of symbol tables,
which is quite useful when organizing a large number of
symbols.
grammar - a grammar is much like a table, in that it contains a
mapping from strings to values. In a grammar, however, the
values are special internal ones which the AmigaMUD system can
use to parse player input. The use of grammars is described in
the section on parsing.
list int
list thing
list action - lists in AmigaMUD are somewhat of a cross between
linked lists and arrays. The size of them is dynamic, and
there are builtins to add and remove elements from both ends.
Their elements can be retrieved and modified by direct
indexing. Such indexing cannot extend the size of the list,
however.
property bool
property int
property string
property thing
property action
property table
property grammar
property list int
property list thing
property list action - properties in AmigaMUD are the identifiers
for attribute-value pairs attached to things. The properties
are themselves first-class objects, however, so they can be
passed to functions as parameters, and returned as results.
Note that only certain types can be attached to things in
attribute-value pairs.
<thing-status> - this type is not a full type in the language. It
has values 'ts_private', 'ts_readonly', 'ts_wizard' and
'ts_public'. It is only used as the result type of the builtin
'GetThingStatus' and the parameter to 'SetThingStatus'.
A few other types exist internally, but they are not generally visible
to the programmer.
Lexical Entities
The bottom-level sequences of characters that are known by a
programming language are called the tokens or lexemes of that
language. In the AmigaMUD programming language, spaces, tabs and
newlines are used to separate tokens that would otherwise appear to be
single tokens, but are otherwise ignored. In other words, the system
does not care, or even notice, what kind of indentation you use.
There are two kinds of comments in the language. One is the C-like
form consisting of characters enclosed within an opening "/*" and a
closing "*/". Unlike C, however, this kind of comment can be nested in
AmigaMUD, so that you can comment out a piece of code without worrying
about whether it has comments inside it. These comments are discarded
very early in the compilation process, so they do not affect runtime
at all. The second kind of comment is the 'note' statement. These are
actually stored in the database and displayed when the function
containing them is printed out. They also slow down execution of
functions containing them by a very small amount.
Newlines are normally ignored when you are in wizard mode. They are
significant, however, when typing wizard mode commands which accept
something other than a period-terminated expression as their
parameter. For example, entering just 'source' as an input line in
wizard mode will yield an error message about a missing file name.
The reserved words in the AmigaMUD programming language (those symbols
that cannot be used as regular identifiers by programmers) are:
and, or, not, if, then, elif, else, fi, while, do, od, for,
from, upto, case, incase, default, esac, ignore, call, note,
proc, utility, wizard, public, corp, void, bool, int, string,
thing, status, grammar, character, table, action, list,
property, true, false, succeed, fail, continue, nil,
ts_private, ts_readonly, ts_wizard, ts_public
Identifiers (user symbols) look like reserved words, but they aren't
give any predefined meaning by the system. They can be of any length,
and are composed of letters, digits and underscores. They must not
start with a digit. The following are legal identifiers:
Fred ThisIsALongIdentifier so_is_this_one fazz_79 x3
Integers (numbers) can be entered in several forms in the AmigaMUD
programming language. The normal form is decimal (base 10). A number
can be prefixed with '0x' for hexadecimal (base 16) interpretation,
'0o' for octal (base 8) interpretation or '0b' for binary (base 2)
interpretation. The following are all valid integer constants:
1234567890
0xcaf4A
0o777
0b1010101010001010111
Integers are signed 32 bit quantities in AmigaMUD. Minus signs are not
part of integer constants - they are unary operators that can be
applied to them. Thus
x := -13;
is perfectly legal - '-13' is interpreted as the unary '-' operator
and the integer constant 13.
String constants in AmigaMUD are similar to those in most programming
languages. They consist of any number of any characters enclosed in
quotation marks ("). Quotation marks and some unprintable characters
can be put inside string constants using an escape mechanism. Inside a
string, a backslash (\) is handled specially, depending on the
character following the backslash, as follows:
\n - a newline character appears in the string
\t - a tab character appears in the string
\X, where X is any other character - a single X appears in the
string. This is how backslashes and quotation marks can be put
in string constants.
An important feature of string constants is the concept of a string
break. Two string constants, separated only by whitespace and /* */
comments, are concatenated together into one string constant. This is
done at "compile" time, and the internal representation used will be
that of a single string constant. When a function containing a long
string constant is printed, the string constant will be broken up
using string breaks in order to fit on the output lines. Such long
string constants are most often used in output messages, as in:
Print("As you open the small wooden door, you detect a strange "
"odour coming from the room beyond. The odour seems "
"familiar, and you are about to identify it when you fall "
"unconscious.");
The following operator and punctuation tokens are also recognized:
. used to end input in wizard mode
= simple equality test for various types
== case ignoring comparison for strings
~ bitwise invert
~= simple inequality test for various types
< <= > >= comparison tests for integers and strings
<< >> bitwise shift operators
>< bitwise exclusive-or operator
: punctuation in function headers, after the result type
:= assignment construct
+ addition and string concatenation operator
-- property deletion operator
- * / % integer arithmetic operators
& | integer bitwise operators
( ) parentheses for subexpressions, function calls, etc.
, ; separators for expressions and statements
@ property lookup operator
[ ] brackets for list indexing
Language Constructs
A number of constructs in AmigaMUD accept a sequence of statements or
an expression as a part of them. As in many programming languages,
statements in a sequence of statements are separated by semicolons.
Such a sequence can have an expression as its final element instead of
a statement, and thus the entire sequence can be used as an
expression. This is most often seen as the body of a function. Note
that this can only happen where specifically indicated - it is not
legal to replace any arbitrary expression with a sequence of
statements and an expression. This is trivial to implement, but I
deliberately did not do so, because of the confusion it can cause.
AmigaMUD does not have any constructs which only accept a single
statement as part of them, and thus it does not have any problem with
"dangling else"'s. All constructs are fully bracketed with reserved
words, hence there are no "begin"/"end" or "{"/"}" brackets needed.
The 'if' Construct
The AmigaMUD programming language has a standard set of language
constructs, including 'if's, 'while's, 'for's and 'case's. 'if's and
'case's can be used as both statements and expressions, i.e. can
return a value or not return a value. An 'if' construct consists of:
- 'if'
- a bool expression (the condition)
- 'then'
- statements/expression to execute if condition is true
- zero or more of:
- 'elif'
- a bool expression (the condition)
- 'then'
- statements/expression to execute if condition is true
- optional:
- 'else'
- statements/expression to execute if all conditions are false
- 'fi'
A simple example of an 'if' is:
if flag then
Print("Flag is true.\n");
fi;
A more complex 'if' statement:
if a <= b and not flag2 then
if a = b then
Print("Found!\n");
else
Print("Not found yet.\n");
fi;
elif a <= b or not flag2 then
Print("Partly found.\n");
else
Print("No result.\n");
fi;
Note that 'if' constructs can be nested. This is true in general of
the programming language - there are no limitations other than memory
available (nesting of constructs is limited only by the available
stack space - the required space is sufficient for a lot of nesting).
'if' expressions can be used like this:
max := if b > a then b else a fi;
An 'if' expression must always have an 'else' part, since there must
always be some value yielded. The various branches of an 'if'
expression must all yield the same type of value. The branches of an
'if' expression can have statements preceeding the final result, all
separated by semicolons. E.g.
result :=
if a < b or b < c then
Print("first case\n");
a := b;
c
elif a < b then
Print("second case\n");
b := a;
c
else
Print("third case\n");
a := b;
c := b;
a
fi;
This kind of construct works fine, but can be a little confusing, so
they should be used with care. Such large 'if' expressions are most
often used as the bodies of functions that return a result which
conditionally depends on something.
The 'while' Construct
A 'while' statement consists of:
- 'while'
- a bool expression (the condition)
- 'do'
- the loop body statement-sequence
- 'od'
A 'while' loop is executed repeatedly until the condition yields
false. A 'while' loop does not return any value, i.e. it yields
'void'. The condition can have statements before the final 'bool'
value, thus yielding a loop with its exit test in the middle. E.g.
i := 10;
while
i := retrieveValue(i);
i ~= 0
do
processValue(i);
od;
Here, the sequence of execution will be:
i := retrieveValue(10); /* lets say this returns 8 */
processValue(8);
i := retrieveValue(8); /* lets say this returns 3 */
processValue(3);
i := retrieveValue(3); /* lets say this returns 0 */
and 'i' will be 0 after the 'while' loop. Programmers should use care
when using 'while' loops, since it may not be obvious when the loop
exits. The AmigaMUD server places an execution time limit on all
execution, so an infinite loop will be aborted, but, depending on what
SysAdmin has set that limit to, bad loops can have serious effects on
the performance of the server for other users. Also, aborting
execution can leave wizard-created data structures in an inconsistent
state.
The 'for' Construct
A 'for' statement consists of:
- 'for'
- local int variable name
- 'from'
- int expression (the start value)
- 'upto'
- int expression (the limit value)
- 'do'
- the loop body statement-sequence
- 'od'
Like a 'while' loop, the 'for' loop does not return any value. The
start and limit expressions are evaluated once at the beginning of the
loop, and then the int variable is stepped by ones from the start
value upto the limit value, with the loop body executed once for each
such value. If the limit value is less than (signed integer
comparison) the start value, then the loop body is never executed.
'for' loops are useful for stepping over fixed ranges, or through the
entries of a list, as in:
GSetPen(nil, C_BLACK);
for i from 1 upto 10 do
GAMove(nil, i * 2, 30);
for j from 1 upto 20 do
GRMove(nil, 0, 1);
GRDraw(nil, 0, 1);
od;
od;
sum := 0;
for j from 0 upto Count(listOfThings) - 1 do
sum := sum + listOfThings[j]@intProperty;
od;
Print("Sum of values = ");
IPrint(sum);
Print(".\n");
When using a 'for' loop to scan down a list, make sure that code
executed in the body of the loop cannot modify the list itself. If it
can, you must use a 'while' loop, since the 'Count' of the elements in
the list will be changing.
The 'case' Construct
The 'case' construct is in some ways a generalization of the 'if'
construct. In other ways it is less general. It consists of:
- 'case'
- int expression (the selector)
- one or more "case alternatives", which are:
- 'default'
- ':'
- the alternative statements/expression
or
- a sequence of "case indexes", which are:
- 'incase'
- integer constant
- ':'
- the alternative statements/expression
- 'esac'
Only one 'default' alternative can occur in any given 'case', and if
the 'case' is a 'case' expression, a 'default' alternative must occur.
Some examples:
case whichButton
incase LEFT_BUTTON:
doMove(MOVE_LEFT);
lastDirection := MOVE_LEFT;
incase RIGHT_BUTTON:
doMove(MOVE_RIGHT);
lastDirection := MOVE_RIGHT;
incase EXIT_BUTTON:
incase LEAVE_BUTTON:
incase OUT_BUTTON:
doMove(MOVE_EXIT);
lastDirection := MOVE_EXIT;
default:
if lastDirection ~= -1 then
Print("You can't go that way.\n");
lastDirection := -1;
else
Print("You still can't go that way.\n");
fi;
esac;
result :=
case retrieveThing()@intProperty
incase 0:
1
incase 1:
20
default:
Print("Illegal value encountered!\n");
-1
esac;
C programmers are cautioned that AmigaMUD case alternatives do not
fall through to the one beneath them. All 'case expressions' must have
a 'default' part, since some value must always result. 'case
statements' need not have one, and if the selector does not match any
of the case indexes, and the 'case' has no 'default' alternative, no
action is taken.
Function Calls
Function calls, whether to a builtin or to a user-defined function,
consist of the name of the function followed by a left parenthesis,
a comma separated list of the function parameters, and a right
parenthesis. The parentheses must be given even if the function has no
parameters. If no parentheses are given after a function name, then
the function itself is the value, with type 'action'. All function
parameters must be given, and must be of the same type as required by
the function header. If the function has a return-type of 'void', then
the function call itself yields 'void', i.e. it is a statement.
Otherwise, the function call yields the type of the function result.
Examples:
Assume:
proc f1(int i, j)int
proc f2(string s1, s2)string
proc f3(int i, string s)void
Then:
f3(f1(6, j + 7), f2("hello", "there") + "world");
If the function to be called is not known until run time, then the
above syntax cannot be used, since the result type of the function is
not known. Instead, the 'call' construct can be used. This form
consists of:
- 'call'
- '('
- action expression (returns the function to call)
- ','
- the expected result type of the action
- ')'
- '('
- the parameters for the function call
- ')'
Since the expected result type is given explicitly, the system can
assume that type at "compile" time, and can check for it at run time.
The parameter count and types of the called function will always be
checked at run time. Examples:
Print(call(Me()@p_pDescAction, string)() + "\n");
i := i + call(if wantMax then max else min fi, int)(j, k);
Miscellaneous Constructs
Sometimes a function or a builtin yields a result that is not always
wanted - the call is being done for its side effects. In these cases,
it can be desireable to make it perfectly clear that the result is
being discarded, so, instead of assigning the result to some dummy
variable, the 'ignore' construct can be used. It consists of the
reserved word 'ignore' followed by any expression whose result is to
be discarded. 'ignore' always returns 'void'. E.g.
ignore FindName(Me()@p_pCarrying, p_oName, "bottle");
theBottle := FindResult();
As mentioned previously, there are two kinds of comments in the
AmigaMUD programming language. The first is the C-like one consisting
of an opening /*, comment text, and a closing */. The second kind of
comment is the 'note', which consists of all of the characters after
the 'note' keyword up to the end of the line. A 'note' comment is
stored in the database and will appear when the function containing it
is printed. For example:
public proc complicated(thing th)void:
note We are doing something complicated here, so be careful!
if th@flag then
...
else
note flag not set, so don't try the tricky stuff.
Print("The easy stuff!\n");
fi;
corp;
It is not necessary to put a semicolon after a note - they delimit
themselves, so the parser can recognize them.
The AmigaMUD system has limited support for direct, unnamed actions.
These are values of type 'action' and can be assigned and called. They
are typically only used in specialized circumstances, such as one-shot
actions produced by the "StringToAction" builtin. They must have
return type 'status', and consist of:
- 'proc'
- optional body statements
- status result expression
- 'corp'
For example, it is legal to do:
myThing@actionProp :=
proc
Print("Hello there world!\n");
succeed
corp;
ignore call(myThing@actionProp, status)();
Such procs have no symbol, so are usually less useful than normal
functions. Also, the forced 'status' result and lack of parameters or
local variables are a limiting factor. This facility may be
generalized in a future release, although there does not seem to be
much need for it.
The most common construct in the AmigaMUD programming language is the
assignment statement. Assignment statements consist of:
- <assignment-left-hand-side>
- ':='
- <expression>
Assignment statements do not return any value, hence the concept of
"nested assignments" does not exist. Several different kinds of
<assignment-left-hand-side>'s are possible:
- local variable or parameter name
or
- <list-expression>
- '['
- <int-expression>
- ']'
or
- <thing-expression>
- '@'
- <property-expression>
The first variant is the obvious one of assigning a new value to a
local variable or parameter. The second is that of assigning a new
value to a specific element of a list. Note that the element indexed
by the <int-expression> must already be present in the list, else a
run-time indexing error will occur. Such indexing starts with 0 as the
first index, and "Count(<list>) - 1" as the last index.
The third form is used to assign a value to a property on a thing. The
property does not need to already exist - this method is the method
used to add new properties also.
Note that there are no global variables in AmigaMUD - values needed
outside of a single function must be stored as properties attached to
some thing.
Example assignment statements (all are of integer values, but the
same rules hold for any type of value):
private th CreateThing(nil).
private intProp CreateIntProp().
private listProp CreateIntListProp().
private proc testProc(int n)void:
int i;
list int li;
i := 10;
n := 100;
li := CreateIntList();
AddTail(li, 10);
Addtail(li, 20);
li[0] := 1;
li[1] := 2;
li(otherFunc()] := 6;
th@intProp := 7;
th@if i < 2 then intProp else otherIntProp fi := 8;
th@listProp := li;
th@listProp[1] := 9;
call(th@thingProp@actionProp, list int)()[n] := i;
corp;
Properties can be removed from things using the '--' construct:
- <thing-expression>
- '--';
- <property-expression>
This construct is a statement - it does not return any value. Note
that it is not an error to try to delete a property from a thing when
that property does not exist on that thing. Examples:
th1--intProp;
thingFunc() -- thingPropFunc();
Expressions
Many examples of expressions have already been seen. This section will
simply list the full set of operators, in order of decreasing
precedence. The precedence of an operator is an indication of how
strongly it binds to its operands. A simple example is the following
expression:
2 * 4 + 6 * 8
The value of this expression is 56, the sum of 2 * 4 and 6 * 8. This
is because the multiplications are done before the addition. The
multiplication operator, '*', has higher precedence than the addition
operator, '+'. The evaluation order of an expression can be changed by
the use of parentheses around a subexpression, as in:
2 * (4 + 6) * 8
which has value 160, the product of 2, 4 + 6, and 8. So, for the
operators in the following descriptions, those described first will be
done before those described later, unless parentheses are introduced
to change the order of evaluation.
All expressions must start with bottom-level items. These are: 'if'-
expression, 'case'-expression, function call result, inline action,
parenthesized sub-expression, list reference, property reference,
identifier, string constant, integer constant, or any of the reserved
words 'false', 'true', 'succeed', 'fail', 'continue', 'nil',
'ts_private', 'ts_readonly', 'ts_wizard', 'ts_public'.
'if'- expressions, 'case'-expressions, function calls, inline actions
and parenthesized sub-expressions have all been covered previously.
Similarly, the bottom-level items were explained in the section on
lexical entities.
A list reference used as an expression looks exactly like the left-
hand-side of an assignment to a list element. The same rule holds -
the indexed element must exist in the list.
A property reference also looks just like the corresponding assignment
left-hand-side. When a property is being searched for on a thing, it
might not be found. If the thing has a parent thing (established when
the thing is created), then the search will continue with that parent
thing. Any value found on the parent will be used. Similarly, if the
value is not found on the parent, then the parent's parent will be
searched, etc. This "inheritance" of properties can be used to save
considerable space in the database, and to save a lot of effort when
creating new things which are all similar. Note that things can only
have a single parent - AmigaMUD does not have "multiple inheritance".
Good examples of the use of inheritance are the monsters in the
Proving Grounds in the standard scenario. Each monster has a model,
defined in the file "monsters.m", and when examples of that monster
are needed, they inherit most of their properties (name, description,
actions, speed, armour class, special actions, etc.) from the model.
Only values which need to be different (such as current hit points)
are stored on the specific instance. The fact that property assignment
only assigns to the descendant thing makes this use automatic.
A tricky use of inheritance can occur with things which inherit from
an ancestor, as in:
private ancestor CreateThing(nil).
private child CreateThing(ancestor).
private intProp CreateIntProp().
ancestor@intProp := 100.
...
child@intProp := child@intProp - 1;
if child@intProp = 0 then
child -- intProp;
fi;
This assignment statement will get the 100 value from the ancestor
thing the first time it is executed, and will add the property with
value 99 to the child. On successive executions, it will modify the
property on the child. When the value reaches 0, the property is
deleted, and will again inherit the 100 from the ancestor.
If a property which is not present on the thing is referenced, the
value yielded depends on the type of the property:
status - fail
bool - false
int - 0
string - "" (an empty string)
thing, action, grammar, list - nil
This defaulting of values is usually useful, but can occasionally be a
bit of a nuisance. The main use is to save storage - the programmer
can count on these values for missing properties, and hence can
arrange to not store them explicitly. This works quite well for flag
values.
Unary Negation: -
This operator appears before an int expression and negates the
value of the expression. E.g.
-6
-(intvar * 7)
-th@func(7, -6)
Bitwise And: &
Bitwise Exclusive-Or: ><
Bitwise Shift Left: <<
Bitwise Shift Right: >>
These operators all operate on int values. They all have the same
precedence, so are evaluated left-to-right when used together in
an expression. The Bitwise And operator combines two int values in
a bit-by-bit fashion - each of the 32 bits of the result is a '1'
bit only if both of the corresponding bits in the operands were
also '1' bits. The Bitwise Exclusive-Or operator yields a '1' bit
in the result only if the corresponding bits in the operands are
different. The Shift-Left operator shifts its left-hand operand
left by the number of bits specified in its right-hand operand.
Similarly, the Shift-Right operator shifts its left-hand operand
right by the number of bits specified in its right-hand operand.
If the right-hand operand to a shift operator is negative, it is
not defined what the result will be. Also, if the right-hand
operand is greater than 32 (the number of bits in the left-hand
operand), the result is not defined. E.g.
0b1100 & 0b0101 => 0b0100
0b1100 >< 0b0101 => 0b1001
0b001100 << 2 => 0b110000
0b001100 << 3 => 0b1100000
0b001100 >> 2 => 0b000011
a & b >< c << d >> e == ((((a & b) >< c) << d) >> e)
Bitwise Inclusive-Or: |
This operator requires two int operands and returns an int result.
Each of the 32 bits in the result is a '1' if the corresponding
bit in either of the operands is a '1'. E.g.
0b1100 | 0b0101 => 0b1101
a & b | c << d | e == (a & b) | (c << d) | e
Integer Multiplication: *
Integer Division: /
Integer Remainder: %
These operators take a pair of int operands and yield an int
result. Division or remainder by zero will be trapped at runtime.
Integer Addition: +
Integer Subtraction: -
String Concatenation: +
Note that both integer addition and string concatenation have the
same operator symbol. The operations are distinguished by the
types of their operands. E.g.
6 + 128 => 134
6 - 128 => -122
"hello" + "world" => "helloworld"
"hello " + "world" => "hello world"
"" + "" => ""
Integer Comparisons: <= < = ~= > >=
String Comparisons: <= < = ~= == > >=
Other Comparisons: = ~=
All comparisons yield a bool value. All comparisons must have
operands which are both of the same type. The integer comparisons
are 32 bit signed integer comparisons. The string comparisons use
comparisons based on the ASCII values of the characters in the
strings. The '==' operator converts all letters to upper case (or
lower case if you prefer!) before doing the comparison. It is
useful when dealing with user input that might be capitalized.
Things, actions, lists, properties, etc. can be compared for
equality or inequality. E.g.
6 < 12 => true
-6 < -12 => false
"hello" = "hello" => true
"hello" <= "hello" => true
"hello" = "Hello" => false
"hello" == "Hello" => true
Logical Not: not
This prefix operator reverses the logical value of its bool
operand. E.g.
not true => false
not false => true
not 6 < 10 => false
Logical And: and
This operator takes two bool operands and returns a bool result
that is true only if both operands are true. Technically, this is
a language construct rather than a true operator, since the
AmigaMUD interpreter will not even try to evaluate the right-hand
operand if the left-hand operand evaluates to false, since it
knows that the right-hand operand will not affect the final
result. This behaviour is part of the language definition and will
not change; thus the programmer is correct to write things like:
th ~= nil and th@field = 2
Logical Or: or
This operator takes two bool operands and returns a bool result
that is true if either of its operands is true. Similar to the
'and' operator, the 'or' operator will not evaluate its right-hand
operand if its left-hand operand is "true".
Further Reading
This document has informally defined the AmigaMUD programming
language. This information allows wizards and apprentices to write and
run valid AmigaMUD programs, but it has not given enough information
to allow them to write meaningful programs, or programs that fit in
with the supplied standard scenario. Further documents relevant to
programming are:
ProgConcepts.txt - discusses some classes of builtin functions and
how to use them. This includes parsing, effects handling,
graphics, character manipulation, etc.
Builtins.txt - this is a reference manual for the builtin
functions. It lists and describes all of the builtins, in
alphabetical order.
Scenario.txt - this document is a rambling discussion of how the
standard scenario is set up, the utility functions it
provides, how to build from it, and how to change how it
works.