-
Notifications
You must be signed in to change notification settings - Fork 0
MUDScript
KMUD is Transpiled to JavaScript (ES6+) script and adds some syntactic sugar to make it more like other OOP languages. This document assumes the reader is familiar with how to write classes in ES6. MUDScript is a "superset" of JavaScript with some notable differences. In many cases, you may opt to change the behaviors through the use of MUDScript compiler options. The default settings are meant to promote strong coding practices and to prevent common mistakes.
-
No late binding
- MUDScript classes represent well-defined interfaces. All properties and methods must be explicitly declared at compile time.
- MUDScript class prototypes are sealed when compiling is complete.
- There is an optional compiler flag called
allowLazyBinding
that will backfill classes with symbols not explicitly declared in the class body, but the flag is off by default. - If you ABSOLUTELY want late binding and you understand the security risks, you may turn off the
sealTypesAfterCompile
option, but this is HIGHLY discouraged as it allows developers to potentially bypass many of the server security features. Even if the option is turned off, the MUDlib will seal certain types such as the SimulEfun or Master objects and other sensitive types.
-
Multiple inheritance
- MUDScript classes may be extended by multiple types. Example:
class Room extends Container, SmellSupport, LookSupport { ... }
- MUDScript classes may be extended by multiple types. Example:
-
Injected superclass
- All MUDScript classes must extend MUDObject, either directly or through inheritance.
- If a class does not explicitly extend a type, then the compiler will use the
injectedSuperClass
option (defaults to 'MUDObject')
-
Custom constructor name
- MUDScript does not allow the use of the traditional ES6
constructor
method as this would prevent multiple inheritance. The compiler may allow the use of the word 'constructor', but it will be substituted for the configured, default constructor name. - MUDScript uses a constructor named
create
unless overridden in the compiler config. - MUDScript classes must always call
super.create(...)
as their first statement - Parameters may be passed to
create
when an instance is created usingnew (Room, ...args)
orawait createAsync(Room, ...args
- MUDScript does not allow the use of the traditional ES6
-
Async Constructors
- MUDScript allows for asynchronous constructors
- Types with async constructors can only be instantiated in an async context.
-
Custom class modifiers
-
abstract
Abstract classes may not be instantiated. They are strictly to be inherited. Any abstract members must be explicitly declared by the first non-abstract child class. -
final
Final classes may not be extended. This may be important for special, privileged types. -
singleton
Singleton classes are limited to a single instance during runtime. The compiler will automatically create an instance when compiling is complete. -
final
andsingleton
may be combined, butabstract
may not.
-
-
Custom member modifiers:
-
private
- Private data is only accessible by the type that defines said method/property -
protected
- Protected data is accessible by the defining type and related types that inherit it. -
public
- Public data is accessible by any other type in the game. -
package
- Package methods and properties may be accessed by types defined in the same module. -
protected package
- Members of such a type are accessible both by children and the module they were defined in (although NOT by other types in the child's module). -
abstract
- Abstract members should have empty bodies and must be implemented by child classes.abstract
members can only be defined inabstract
classes. -
final
- Final members cannot be redefined in child classes. -
override
- Any time a class defines a member that would mask an inherited member, theoverride
keyword must be used.
-
-
Custom call operators:
-
->
- Dereferencing call operator. When the LHS (left hand side) of a call may be a wrapper, an object instance, a string variable, or a string literal, you may use the->
call operator (instead of the traditional.
). This will ensure that the LHS ALWAYS resolves into an object reference. -
::
- Scope resolution operator. This allows calling of a specific implementation of a particular member. Example: In the Room example above, the class extends three types (Container, SmellSupport, LookSupport). In order to call the methodsetItems
in SmellSupport, you would callthis->SmellSupport::setItems(...)
- Override of
super
behavior. Since MUDScript types may inherit multiple parents, calling parent members usingsuper
will call ALL the parent implementations. The return value for such a call is an object in the form of (using Room example)super.getItems()
could return{ "SmellSupport":{ ... }, "LookSupport":{...} }
-
-
set/get pseudo operators
- Classes are not designed to directly hold/store their own data. The game creates an underlying storage object for each object reference.
- Classes are responsible for performing in-game logic and data validation before passing data to the storage layer using the set/get operators.
- Setters must call
set([value])
to store the value - Getters must call
get([defaultValue]
to retrieve the data. The optionaldefaultValue
is only used if the storage value is undefined. - The get() and set() operators may only be used in property definitions.
Example:
class Sword {
set weight(v) {
if (typeof v === 'number' and v > 0) set(v);
}
get weight() { return get(0); }
-
All async methods MUST be awaited
- The execution context checks to make sure all async calls are awaited. Attempting to call an async method without awaiting triggers a runtime exception.
-
wrappers (vs objects) - Objects in the game may be represented by direct reference or by using wrappers. A wrapper is a function that maintains a weak reference to an in-game object. It will return the current instance when evaluated. If a piece of code is going to store a reference to a particular object, then it should store a wrapper since the wrapper will update automatically if the underlying object is re-compiled whereas a direct reference will go stale (and prevent proper garbage cleanup). You can think of a wrapper as a pointer.
let player = thisPlayer(),
playerWrapper = wrapper(player);
/* some update changes name from Bob to Frank */
writeLine(player.name); <-- Prints Bob and causes memory leak
writeLine(playerWrapper().name); <-- Prints Frank
- Disallowed ECMA patterns - ECMA script is very flexible, but in order to safeguard against malicious code, certain aspects are considered errors if used in "MUDScript". Use of these patterns would potentially allow code to circumvent the driver's custom call stack maintenance (and thus the security measures). These patterns include (compiler exceptions):
- func.apply(thisArg, [])
- func.call(thisArg, ...)
- func.bind(thisArg)
Attempts to bypass this policy are caught at runtime:
- func['call'](master(), 'shutdown')
- func['ca' + 'll'](master(), 'shutdown')
- var totallySafe = 'call'; func[totallySafe](master(), 'shutdown')