Extensions
mooR adds the following notable extensions to the LambdaMOO 1.8.x language and runtime. Note that this list
isn't fully complete as some features are still in development, and some are not so easily categorized
as extensions and more "architectural differences" which will be described elsewhere.
Lexical variable scoping
Adds block-level lexical scoping to the LambdaMOO language.
Enabled by default, can be disabled with command line option --lexical-scopes=false
In LambdaMOO all variables are global to the verb scope, and are bound at the first assignment.
mooR adds optional lexical scoping, where variables are bound to the scope in which they are declared,
and leave the scope when it is exited.
This is done by using the let keyword to declare variables that will exist only in the current scope.
while (y < 10)
let x = 1;
...
if (x == 1000)
return x;
endif
endwhile
The block scopes for lexical variables apply to for, while, if/elseif, try/except
... and a new ad-hoc block type declared with begin / end:
begin
let x = 1;
...
end
Where a variable is declared in a block scope, it is not visible outside of that block.
Global variables are still available, and can be accessed from within a block scope.
In the future a strict mode may be added to require all variables to be declared lexically before use,
but this would break compatibility with existing code.
Primitive type dispatching
Adds the ability to call verbs on primitive types, such as numbers and strings.
Enabled by default, can be disabled with command line option --type-dispatch=false
In LambdaMOO, verbs can only be called on objects. mooR adds the ability to indirectly dispatch verbs for primitive
types by interpreting the verb name as a verb on a special object that represents the primitive type and passing
the value itself as the first argument to the verb. Where other arguments are passed, they are appended to the argument
list.
"hello":length() => 5
is equivalent to
$string:length("hello") => 5
The names of the type objects to the system objects $string, $float, $integer, $list, $map, and $error
Map type
mooR adds a dictionary or map type mostly equivalent to the one present in stunt/toaststunt.
This is a set of immutable sorted key-value pairs with convenient syntax for creating and accessing them and
can be used as an alternative to traditional MOO alists / "associative lists".
let my_map = [ "a" -> 1, "b" -> 2, "c" -> 3 ];
my_map["a"] => 1
Wherever possible, the Map type semantics are made to be compatible with those from stunt, so
most existing code from those servers should work with mooR with no changes.
Values are stored sorted, and lookup uses a binary search. The map is immutable, and modification is done through copy and update like other MOO primitive types.
Performance wise, construction and lookup are O(log n) operations, and iteration is O(n).
Custom errors and errors with attached messages
LambdaMOO had a set of hardcoded builtin-in errors with numeric values. It also uses the same errors for exceptions, and allows optional messages and an optional value to be passed when raising them, but these are not part of the error value itself.
mooR adds support for custom errors, where any value that comes after E_ is treated as an error identifier, though
it does not have a numeric value (and will raise error if you try to call tonum() on it).
Additionally, mooR extends errors with an optional message component which can be used to provide more context:
E_PROPNF("$foo.bar does not exist")
This is useful for debugging and error handling, as it allows you to provide more information about the error.
Most of the builtin functions and builtin types have been updated to produce more descriptive error messages when they fail. This can help greatly with debugging and understanding what went wrong in your code.
Symbol type
mooR adds a symbol type which represents interned strings similar to those found in Lisp, Scheme and other languages.
Enabled by default, can be disabled with command line option --symbol-type=false
Symbols are immutable string values that are guaranteed to be unique and can be compared by reference rather than value. This makes them useful for keys in maps and other data structures where fast comparison is important.
The syntax for creating a symbol is the same as for strings, but with a leading apostrophe:
'hello is a symbol, while "hello" is a string.
Symbols are useful for representing identifiers, keywords, and other values that are not meant to be modified, and they can be used in place of strings as "keys" in maps and other data structures.
For comprehensions
mooR adds a syntax similar to Python or Julia for list comprehensions, allowing you to create lists from existing
lists
or ranges in a more concise way:
{ x * 2 for x in ({1, 2, 3, 4}) };
=> {2, 4, 6, 8}
or
{ x * 2 for x in [1..10] };
=> {2, 4, 6, 8, 10, 12, 14, 16, 18, 20}
Return as expression rather than a statement
mooR allows the return statement to be used as an expression, allowing for "short circuit" returns within chains
of expression, in a manner similar to Julia.
This can be useful for forcing immediate returns from a verb without having to use if statements.
this.colour != "red" && return true;
this.colour || return false;
Flyweight objects
mooR adds a new type of object called a "flyweight" which is a lightweight object that can be used to represent
data structures without the overhead of a full object. Flyweights are immutable and can be used to represent
complex data structures like trees or graphs without the overhead of creating a full object for each node.
let request = < $http_request, .method = "GET" >;
request:send(); // Calls $http_request:send with 'this' as the flyweight
See the Flyweights chapter for full documentation.
"Rich" output via notify
The notify builtin in MOO is used to output a line of text over the telnet connection to the player.
In mooR, the connection may not be telnet, and the output may be more than just text.
Enabled by default, can be disabled with command line option --rich-notify=false
mooR ships with a web-host that can serve HTTP and websocket connections, and the notify verb can be used to
output JSON values over the websocket connection.
When this feature is enabled, the second (value) argument to notify can be any MOO value, and it will be
serialized to JSON and sent to the client.
If a third argument is present, it is expected to be a "content-type" string, and will be places in the websocket JSON message as such.
notify(player, [ "type" -> "message", "text" -> "Hello, world!" ], "application/json");
becomes, on the websocket:
{
"content-type": "application/json",
"message": {
"map_pairs": [
[
"type",
"message"
],
[
"text",
"Hello, world!"
]
]
},
"system-time": 1234567890
}
The telnet host will still output the text value of the second argument as before, and ignore anything which
is not a string.
Document Processing (HTML, XML, JSON)
mooR provides built-in functions for working with structured documents:
html_query()- Extract data from HTML using tag/attribute queriesxml_parse()/to_xml()- Parse and generate XMLparse_json()/generate_json()- Work with JSON data
See Document Processing for full documentation.
Lambda functions
mooR adds support for creating small functions within your verbs, similar to functions in other programming languages.
Enabled by default, this feature lets you create both named functions (for organizing code) and anonymous functions (called "lambdas") that can remember variables from where they were created.
Arrow syntax for simple expressions:
let add = {x, y} => x + y;
let greet = {name} => "Hello, " + name;
Function syntax for complex logic:
let max = fn(x, y)
if (x > y)
return x;
else
return y;
endif
endfn;
Named recursive functions:
fn factorial(n)
if (n <= 1)
return 1;
else
return n * factorial(n - 1);
endif
endfn
Closures with variable capture:
let multiplier = 5;
let multiply_by_five = {x} => x * multiplier; // Captures 'multiplier'
return multiply_by_five(10); // Returns 50
Functions support all MOO parameter patterns including optional parameters (?param) and rest parameters (@args). They can be called like regular functions and are particularly useful for organizing code, event handling, and data processing.
Historical Note: Despite its name suggesting otherwise, the original LambdaMOO never actually had lambda functions! mooR brings this useful programming tool to MOO as part of our mission of dragging the future into the past.
Type constant literals
mooR changes how type constants are represented in the language.
The Problem: In LambdaMOO and ToastStunt, type constants like INT, OBJ, STR, LIST, etc. were pre-populated variables in every verb's stack frame. This caused two issues:
- Variable name conflicts: Legacy code that used these names as variables (e.g.,
NUM = 123orSTR = "hello") would fail because they couldn't be assigned to. - Decompilation inconsistency: The decompiler would output lowercase
int,obj, etc. which didn't match the uppercase convention.
The Solution: In mooR, type constants are now literals (like true and false), not variables. They use a new TYPE_ prefix:
| Old Form | New Form |
|---|---|
INT / NUM | TYPE_INT |
OBJ | TYPE_OBJ |
STR | TYPE_STR |
LIST | TYPE_LIST |
MAP | TYPE_MAP |
ERR | TYPE_ERR |
FLOAT | TYPE_FLOAT |
BOOL | TYPE_BOOL |
FLYWEIGHT | TYPE_FLYWEIGHT |
SYM | TYPE_SYM |
Usage:
if (typeof(x) == TYPE_STR)
player:tell("x is a string");
endif
if (typeof(obj) == TYPE_OBJ && valid(obj))
obj:tell("Hello!");
endif
Migrating Legacy Code
Textdump imports: When importing LambdaMOO or ToastStunt textdumps, the legacy forms (INT, OBJ, etc.) are automatically recognized and converted to the new TYPE_* format. No action is required.
Objdef format sources: If you have existing objdef-format MOO sources that use the old type constant names, use the moorc migration command:
# In your core directory (e.g., cores/lambda-moor or cores/cowbell)
make migrate
This will:
- Parse your sources with legacy type constant support
- Output them with the new
TYPE_*format - Copy the migrated files back to your
src/directory
After migration, use make rebuild for normal development.
You can also invoke moorc directly with the --legacy-type-constants true flag:
moorc --legacy-type-constants true --src-objdef-dir src --out-objdef-dir gen.objdir
Note: This migration only needs to be done once per codebase. After migration, all code will use the new
TYPE_*format and the legacy flag is no longer needed.