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

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.

Enabled by default, can be disabled with command line option --map-type=false

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.

Flyweights have three components only one of this is mandatory:

  • A delegate (like a parent)
  • A set of slots (like properties)
  • Contents (a list)

Which is expressed in the following literal syntax:

< delegate, [ slot -> value, ... ], { contents } >

Examples:

< $key, [ password -> "secret" ], { 1, 2, 3 } >
< $exit, [ name -> "door", locked -> 1, description -> "..." ] >
< $password, [ crypted -> "fsadfdsa", salt -> "sdfasfd" ]>

When accessing a property (or slot) on a flyweight using property accessing syntax, the system will first check the flyweight itself, and then check the delegate object. If the property is not found on either, it will return E_PROPNF:

let x = < $key, [ password => "secret" ] >;
return x.password;

=> "secret"

Verbs cannot be defined on a flyweight, but calling a verb on one will attempt to call it on the delegate:

let x = < $key, [ password -> "secret" >;
x:unlock("magic_key");

Will call $key:unlock with this being the flyweight object, and the first argument being the string "magic_key".

"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.

XML Document Processing

mooR can generate XML and HTML documents from MOO data structures, and parse XML or well-formed HTML document strings into structured data.

The xml_parse builtin can produce XML data in three different formats:

Flyweight format (original): xml_parse(xml_string, FLYWEIGHT, [tag_map])

// Returns flyweight objects representing XML structure
let result = xml_parse("<div class='test'>Hello</div>", 15);

List format: xml_parse(xml_string, 4)

// Returns nested lists: {"tag", {"attr", "value"}, ...contents...}
let result = xml_parse("<div class='test'>Hello</div>", LIST);
// result = {{"div", {"class", "test"}, "Hello"}}

Attributes-as-Map format: xml_parse(xml_string, 10)

// Returns list of maps with structured data
let result = xml_parse("<div class='test'>Hello</div>", MAP);
// result = {["tag" -> "div", "attributes" -> ["class" -> "test"], "content" -> {"Hello"}]}

The to_xml builtin can generate XML from both flyweights and list formats:

// Generate XML from list format
let html_structure = {"div", {"class", "container"}, "Hello World"};
let xml_string = to_xml(html_structure);
// Returns: "<div class='container'>Hello World</div>"

This makes it easy to work with XML data in web applications and API integrations without requiring flyweight objects.