Doomsday Script (DS) is the Doomsday 2 scripting language, built right into the core of the engine. Its syntax is heavily Python-inspired, however with a somewhat smaller set of language features. Recently I’ve been improving DS with future needs in mind.
Having a scripting language fully integrated with the rest of the engine is very powerful: subsystems can use the native DS classes (like de::Record
) to store and manipulate data, making it instantly accessible from scripts as well.
While DS is available in Doomsday today, it is not yet taken advantage of everywhere internally. The most heavily used part is currently engine bootstrapping: persistent/default configuration and possible startup scripts (e.g., when upgrading to a new version that requires some changes).
After hearing about Swift, I was inspired to do some enhancements to Doomsday Script. The DS language has always lacked any object-oriented features, which is a huge omission considering that Doomsday 2 is fully object-oriented itself. I’ve spent the last few days adding the following new language features:
- Any record can be considered a class from which other records can be instantiated. In practice this is done using the call operator: calling a record will produce a new instance. This means that any object can act as the class for another object.
- If a record has a member called
__super__
, it is expected to contain an array with all the superclasses — multiple inheritance is possible. Symbol lookup automatically now extends to cover the members of superclasses after the record itself has been checked. - The member operator (
.
) can be used with any value: if the left-side operand points to a record, the right-side operand is looked up from that record; otherwise the value can specify a record from which members can be found. For instance, aDictionaryValue
uses the built-inCore.Dictionary
class that has native functions for operations likekeys()
andvalues()
. This makes C-style functions likedictkeys(var)
obsolete in favor ofvar.keys()
kind of OO syntax. - A
self
variable is automatically set when calling functions in relation to an object. This means that, unlike Python, the methods of a class do not need to haveself
spelled out as one of the arguments. One still has to useself
inside member functions, though, to refer to the object being acted on. - Symbol lookup was augmented with a special syntax for specifying where to look up the symbol. Since
self
is not an argument, this is needed for accessing shadowed members from superclasses.
The syntax for defining a new class is simple:
record MyClass()
def __init__()
print 'Initializing MyClass'
end
end
As in Python, the __init__
method is automatically called when the class is instantiated:
obj = MyClass()
A subclass needs the special ->
notation to access shadowed members of the superclass:
record SubClass(MyClass)
def __init__()
self.MyClass->__init__()
end
end
The part with MyClass->
tells the DS interpreter to look up __init__
from MyClass
instead of self
. However, when the function is called, self
is still passed as the invocation scope, since it’s on the left side of the member operator.
I decided to go with ->
instead of the C++ style ::
because the latter already occurs in the slice operator, and the lexical analyzer does not know if ::
here refers to the scope keyword or slice operator:
rev = array[::-1] # Reversed array.
These new object-oriented features will be crucial in the future. For instance, every File
object in DS can now use the same superclass that contains the functions for manipulating a file object.
The plan is to have DS form the foundation of many of the engine’s subsystems: the “Quake-style” interactive console, InFine and game menus, XG (including new map scripting extensions), runtime help database, and possibly even game logic extensions like defining new types of objects via scripts. In most of these cases, object-orientation will prove very valuable.