Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

MOO Language Statements

Statements are MOO constructs that, in contrast to expressions, perform some useful, non-value-producing operation. For example, there are several kinds of statements, called looping constructs, that repeatedly perform some set of operations. Fortunately, there are many fewer kinds of statements in MOO than there are kinds of expressions.

Errors While Executing Statements

Statements do not return values, but some kinds of statements can, under certain circumstances described below, generate errors. If such an error is generated in a verb whose d (debug) bit is not set, then the error is ignored and the statement that generated it is simply skipped; execution proceeds with the next statement.

Note: This error-ignoring behavior is very error prone, since it affects all errors, including ones the programmer may not have anticipated. The d bit exists only for historical reasons; it was once the only way for MOO programmers to catch and handle errors. The error-catching expression and the try -except statement are far better ways of accomplishing the same thing.

If the d bit is set, as it usually is, then the error is raised and can be caught and handled either by code surrounding the expression in question or by verbs higher up on the chain of calls leading to the current verb. If the error is not caught, then the server aborts the entire task and, by default, prints a message to the current player. See the descriptions of the error-catching expression and the try-except statement for the details of how errors can be caught, and the chapter on server assumptions about the database for details on the handling of uncaught errors.

Simple Statements

The simplest kind of statement is the null statement, consisting of just a semicolon:

;

It doesn't do anything at all, but it does it very quickly.

The next simplest statement is also one of the most common, the expression statement, consisting of any expression followed by a semicolon:

expression;

The given expression is evaluated and the resulting value is ignored. Commonly-used kinds of expressions for such statements include assignments and verb calls. Of course, there's no use for such a statement unless the evaluation of expression has some side-effect, such as changing the value of some variable or property, printing some text on someone's screen, etc.

#42.weight = 40;
#42.weight;
2 + 5;
obj:verbname();
1 > 2;
2 < 1;

Statements for Testing Conditions

The if statement allows you to decide whether or not to perform some statements based on the value of an arbitrary expression:

if (expression)
  statements
endif

Expression is evaluated and, if it returns a true value, the statements are executed in order; otherwise, nothing more is done.

One frequently wants to perform one set of statements if some condition is true and some other set of statements otherwise. The optional else phrase in an if statement allows you to do this:

if (expression)
  statements-1
else
  statements-2
endif

This statement is executed just like the previous one, except that statements-1 are executed if expression returns a true value and statements-2 are executed otherwise.

Sometimes, one needs to test several conditions in a kind of nested fashion:

if (expression-1)
  statements-1
else
  if (expression-2)
    statements-2
  else
    if (expression-3)
      statements-3
    else
      statements-4
    endif
  endif
endif

Such code can easily become tedious to write and difficult to read. MOO provides a somewhat simpler notation for such cases:

if (expression-1)
  statements-1
elseif (expression-2)
  statements-2
elseif (expression-3)
  statements-3
else
  statements-4
endif

Note that elseif is written as a single word, without any spaces. This simpler version has the very same meaning as the original: evaluate expression-i for i equal to 1, 2, and 3, in turn, until one of them returns a true value; then execute the statements-i associated with that expression. If none of the expression-i return a true value, then execute statements-4.

Any number of elseif phrases can appear, each having this form:

elseif (expression)
    statements

The complete syntax of the if statement, therefore, is as follows:

if (expression)
  statements
zero-or-more-elseif-phrases
an-optional-else-phrase
endif

Statements for Looping

MOO provides three different kinds of looping statements, allowing you to have a set of statements executed (1) once for each element of a given sequence (list, map or string); (2) once for each integer or object number in a given range; and (3) over and over until a given condition stops being true.

To perform some statements once for each element of a given sequence, use this syntax:

for value, key-or-index in (expression)
  statements
endfor

The expression is evaluated and should return a list, map or string; if it does not, E_TYPE is raised. The statements are then executed once for each element of that sequence in turn; each time, the given value is assigned the value of the element in question, and key-or-index is assigned the index of value in the list or string, or its key if the sequence is a map. key-or-index is optional. For example, consider the following statements:

odds = {1, 3, 5, 7, 9};
evens = {};
for n in (odds)
  evens = {@evens, n + 1};
endfor

The value of the variable evens after executing these statements is the list

{2, 4, 6, 8, 10}

If the example were modified:

odds = {1, 3, 5, 7, 9};
pairs = [];
for n, i in (odds)
  pairs[i] = n + 1;
endfor

The value of the variable pairs after executing these statements is the map

[1 -> 2, 2 -> 4, 3 -> 6, 4 -> 8, 5 -> 10]

To perform a set of statements once for each integer or object number in a given range, use this syntax:

for variable in [expression-1..expression-2]
  statements
endfor

The two expressions are evaluated in turn and should either both return integers or both return object numbers; E_TYPE is raised otherwise. The statements are then executed once for each integer (or object number, as appropriate) greater than or equal to the value of expression-1 and less than or equal to the result of expression-2, in increasing order. Each time, the given variable is assigned the integer or object number in question. For example, consider the following statements:

evens = {};
for n in [1..5]
  evens = {@evens, 2 * n};
endfor

The value of the variable evens after executing these statements is just as in the previous example: the list

{2, 4, 6, 8, 10}

The following loop over object numbers prints out the number and name of every valid object in the database:

for o in [#0..max_object()]
  if (valid(o))
    notify(player, tostr(o, ": ", o.name));
  endif
endfor

The final kind of loop in MOO executes a set of statements repeatedly as long as a given condition remains true:

while (expression)
  statements
endwhile

The expression is evaluated and, if it returns a true value, the statements are executed; then, execution of the while statement begins all over again with the evaluation of the expression. That is, execution alternates between evaluating the expression and executing the statements until the expression returns a false value. The following example code has precisely the same effect as the loop just shown above:

evens = {};
n = 1;
while (n <= 5)
  evens = {@evens, 2 * n};
  n = n + 1;
endwhile

Fine point: It is also possible to give a name to a while loop.

while name (expression)
  statements
endwhile

which has precisely the same effect as

while (name = expression)
  statements
endwhile

This naming facility is only really useful in conjunction with the break and continue statements, described in the next section.

With each kind of loop, it is possible that the statements in the body of the loop will never be executed at all. For iteration over lists, this happens when the list returned by the expression is empty. For iteration on integers, it happens when expression-1 returns a larger integer than expression-2. Finally, for the while loop, it happens if the expression returns a false value the very first time it is evaluated.

Warning: With while loops it is especially important to make sure you do not create an infinite loop. That is, a loop that will never terminate because it's expression will never become false. Be especially careful if you suspend(), yin(), or $command_utils:suspend_if_needed() within a loop, as the task may never run out of ticks.

Terminating One or All Iterations of a Loop

Sometimes, it is useful to exit a loop before it finishes all of its iterations. For example, if the loop is used to search for a particular kind of element of a list, then it might make sense to stop looping as soon as the right kind of element is found, even if there are more elements yet to see. The break statement is used for this purpose; it has the form

break;

or

break name;

Each break statement indicates a specific surrounding loop; if name is not given, the statement refers to the innermost one. If it is given, name must be the name appearing right after the for or while keyword of the desired enclosing loop. When the break statement is executed, the indicated loop is immediately terminated and executing continues just as if the loop had completed its iterations normally.

MOO also allows you to terminate just the current iteration of a loop, making it immediately go on to the next one, if any. The continue statement does this; it has precisely the same forms as the break statement:

continue;

or

continue name;

An example that sums up a list of integers, excluding any integer equal to four:

my_list = {1, 2, 3, 4, 5, 6, 7};
sum = 0;
for element in (my_list)
    if (element == 4)
        continue;
    endif
    sum = sum + element;
endfor

An example that breaks out of hte loop when a specific object in a list is found

my_list = {#13633, #98, #15840, #18657, #22664};
i = 0;
found = 0;
for obj in (my_list)
    i = i + 1;
    if (obj == #18657)
        found = 1;
        break;
    endif
endfor
if (found)
    notify(player, tostr("found #18657 at ", i, " index"));
else
    notify(player, "couldn't find #18657 in the list!");
endif

Returning a Value from a Verb

The MOO program in a verb is just a sequence of statements. Normally, when the verb is called, those statements are simply executed in order and then the integer 0 is returned as the value of the verb-call expression. Using the return statement, one can change this behavior. The return statement has one of the following two forms:

return;

or

return expression;

When it is executed, execution of the current verb is terminated immediately after evaluating the given expression, if any. The verb-call expression that started the execution of this verb then returns either the value of expression or the integer 0, if no expression was provided.

We could modify the example given above. Imagine a verb called has_object which takes an object (that we want to find) as it's first argument and a list of objects (to search) as it's second argument:

{seek_obj, list_of_objects} = args;
for obj in (list_of_objects)
    if (obj == seek_obj)
        return 1;
    endif
endfor

The verb above could be called with obj_with_verb:has_object(#18657, {#1, #3, #4, #3000}) and it would return false (0) if the object was not found in the list. It would return true (1) if the object was found in the list.

Of course we could write this much more simply (and get the index of the object in the list at the same time):

{seek_obj, list_of_objects} = args;
return seek_obj in list_of_objects;

Handling Errors in Statements

A traceback is raised when there is an error in the execution of code (this differs from a compilation error you might see when programming a verb).

Examples to cause tracebacks:

;notify(5)

#-1:Input to EVAL (this == #-1), line 3:  Incorrect number of arguments (expected 2-4; got 1)
... called from built-in function eval()
... called from #58:eval_cmd_string, line 19
... called from #58:eval*-d, line 13
(End of traceback)

And another example:

;notify(me, 5)

#-1:Input to EVAL (this == #-1), line 3:  Type mismatch (args[1] of notify() expected object; got integer)
... called from built-in function eval()
... called from #58:eval_cmd_string, line 19
... called from #58:eval*-d, line 13
(End of traceback)

As you can see in the above examples, mooR will tell you the line number of the error, as well as some additional information about the error, including the expected number of arguments and the type. This will also work when you are catching errors in a try/except statement (detailed below).

Additional, you will also be shown {object, verb / property name} when you try to access a verb or property that was not found.

Normally, whenever a piece of MOO code raises an error, the entire task is aborted and a message printed to the user. Often, such errors can be anticipated in advance by the programmer and code written to deal with them in a more graceful manner. The try-except statement allows you to do this; the syntax is as follows:

try
  statements-0
except variable-1 (codes-1)
  statements-1
except variable-2 (codes-2)
  statements-2
...
endtry

where the variables may be omitted and each codes part is either the keyword ANY or else a comma-separated list of expressions, just like an argument list. As in an argument list, the splicing operator (@) can be used here. There can be anywhere from 1 to 255 except clauses.

First, each codes part is evaluated, yielding a list of error codes that should be caught if they're raised; if a codes is ANY, then it is equivalent to the list of all possible MOO values.

Next, statements-0 is executed; if it doesn't raise an error, then that's all that happens for the entire try-except statement. Otherwise, let E be the error it raises. From top to bottom, E is searched for in the lists resulting from the various codes parts; if it isn't found in any of them, then it continues to be raised, possibly to be caught by some piece of code either surrounding this try-except statement or higher up on the verb-call stack.

If E is found first in codes-i, then variable-i (if provided) is assigned a value containing information about the error being raised and statements-i is executed. The value assigned to variable-i is a list of four elements:

{code, message, value, traceback}

where code is E, the error being raised, message and value are as provided by the code that raised the error, and traceback is a list like that returned by the callers() function, including line numbers. The traceback list contains entries for every verb from the one that raised the error through the one containing this try-except statement.

Unless otherwise mentioned, all of the built-in errors raised by expressions, statements, and functions provide tostr(code) as message and zero as value.

Here's an example of the use of this kind of statement:

try
  result = object:(command)(@arguments);
  player:tell("=> ", toliteral(result));
except v (ANY)
  tb = v[4];
  if (length(tb) == 1)
    player:tell("** Illegal command: ", v[2]);
  else
    top = tb[1];
    tb[1..1] = {};
    player:tell(top[1], ":", top[2], ", line ", top[6], ":", v[2]);
    for fr in (tb)
      player:tell("... called from ", fr[1], ":", fr[2], ", line ", fr[6]);
    endfor
    player:tell("(End of traceback)");
  endif
endtry

Cleaning Up After Errors

Whenever an error is raised, it is usually the case that at least some MOO code gets skipped over and never executed. Sometimes, it's important that a piece of code always be executed, whether or not an error is raised. Use the try- finally statement for these cases; it has the following syntax:

try
  statements-1
finally
  statements-2
endtry

First, statements-1 is executed; if it completes without raising an error, returning from this verb, or terminating the current iteration of a surrounding loop (we call these possibilities transferring control), then statements-2 is executed and that's all that happens for the entire try-finally statement.

Otherwise, the process of transferring control is interrupted and statements-2 is executed. If statements-2 itself completes without transferring control, then the interrupted control transfer is resumed just where it left off. If statements-2 does transfer control, then the interrupted transfer is simply forgotten in favor of the new one.

In short, this statement ensures that statements-2 is executed after control leaves statements-1 for whatever reason; it can thus be used to make sure that some piece of cleanup code is run even if statements-1 doesn't simply run normally to completion.

Here's an example:

try
  start = time();
  object:(command)(@arguments);
finally
  end = time();
  this:charge_user_for_seconds(player, end - start);
endtry

Warning: If a task runs out of ticks, it's possible for your finally code to not run.

Executing Statements at a Later Time

It is sometimes useful to have some sequence of statements execute at a later time, without human intervention. For example, one might implement an object that, when thrown into the air, eventually falls back to the ground; the throw verb on that object should arrange to print a message about the object landing on the ground, but the message shouldn't be printed until some number of seconds have passed.

The fork statement is intended for just such situations and has the following syntax:

fork (expression)
  statements
endfork

The fork statement first executes the expression, which must return an integer or float; call that value n. It then creates a new MOO task that will, after at least n seconds (or sub seconds in the case of a float like 0.1), execute the statements. When the new task begins, all variables will have the values they had at the time the fork statement was executed. The task executing the fork statement immediately continues execution. The concept of tasks is discussed in detail in the next section.

By default, there is no limit to the number of tasks any player may fork, but such a limit can be imposed from within the database. See the chapter on server assumptions about the database for details.

Occasionally, one would like to be able to kill a forked task before it even starts; for example, some player might have caught the object that was thrown into the air, so no message should be printed about it hitting the ground. If a variable name is given after the fork keyword, like this:

fork name (expression)
  statements
endfork

then that variable is assigned the task ID of the newly-created task. The value of this variable is visible both to the task executing the fork statement and to the statements in the newly-created task. This ID can be passed to the kill_task() function to keep the task from running and will be the value of task_id() once the task begins execution.

An example of this:

{ball} = args;
player:tell("You throw the ball!");
ball:calculate_trajectory();
player:tell("You get out another ball!");

In the above example, player:tell("You get out another ball!"); will not be executed until after ball:calculate_trajectory(); is completed.

{ball} = args;
player:tell("You throw the ball!");
fork (1)
    ball:calculate_trajectory();
endfor
player:tell("You get out another ball!");

In this forked example, the ball will be thrown, the task forked for 1 second later and the the final line telling the player they got out another ball will be followed up right after, without having to wait for the trajectory verb to finish running.

This type of fork cannot be used if the trajectory is required by the code that runs after it. For instance:

{ball} = args;
player:tell("You throw the ball!");
direction = ball:calculate_trajectory();
player:tell("You get out another ball!");
player:tell("Your ball arcs to the " + direction);

If the above task was forked as it is below:

{ball} = args;
player:tell("You throw the ball!");
fork (1)
    direction = ball:calculate_trajectory();
endfork
player:tell("You get out another ball!");
player:tell("Your ball arcs to the " + direction);

The verb would raise E_VARNF due to direction not being defined.