Records are the foundation of Doomsday Script. Each namespace and module is a record, each object is a record, and classes are records, too.
The ownership model of records is that there is always a single owner for a record, and if that owner gets destroyed, all records owned by it will get destroyed, too. Many records are owned by native code (e.g., built-in modules like Core
), but may still be referenced by variables in scripts.
A script variable may own a record, or simply reference one owned by someone else. When a record gets destroyed, all variables that reference it will be invalidated. In other words, records are not reference-counted (like Python objects would be).
In practice, a record is a set of named variables. Some of these variables may point to other records, while others have plain data values.
There are a few alternative ways to create a record:
record
statement.Record()
function.record
expression.
With the record
statement, one can create one or more empty records in the local namespace. The following would initialize variables a and b, giving each a new empty owned record.
record a, b
Note that the record
statement will replace any existing variables with the same names.
The Record()
function makes a copy of an another record and returns an owned reference to it. Without arguments, it returns an empty owned record. This can then be assigned to a variable.
a = Record() b = Record(a)
Record()
also accepts a dictionary as argument. In this case, the keys of the dictionary become variables in the newly created record.
Record({'a': 1, 'b': 2})
record
may also be used as an expression, to create a subrecord in the current scope. In this example, myrec is created in the local namespace. On the second line subexp is created inside myrec because it appears after the member operator.
(record myrec).expressionCreated = True myrec.(record subexp).alsoExpCreated = True
The member operator .
can be used together with assignment to create new variables inside a record. This works with both owned and non-owned record variables.
$ record myrec $ myrec.newMember = 100 $ print len(myrec) 1 $ print myrec.newMember 100 $ print myrec newMember:100
Subrecords can be created with the record
expression as seen above, or more simply using the record
statement.
$ record myrec $ record myrec.subrec $ myrec.subrec.a = 1 $ print myrec subrec.a:1
The in
operator is used to check if members exist in a record.
$ print 'subrec' in myrec, \ 'newMember' in myrec, \ 'not-there' in myrec.subrec True True False
The built-in functions members()
and subrecords()
return dictionaries containing the corresponding members of a record.
record myrec myrec.var = 'hello' myrec.(record subrec).a = 123 print ' members:', members(myrec) print 'subrecords:', subrecords(myrec)
Output:
members: { subrec: a:123, var: hello } subrecords: { subrec: a:123 }
Members of a record can also be accessed with the []
operator, and via the dictionary returned by members()
or subrecords()
.
print myrec['newMember'] print myrec['subrec'] print myrec['subrec']['something'] print members(myrec)['newMember']
Members can also be created or changed using []
. However, the key value still has to be a text string that contains no periods. (Otherwise, the member would not be accessible as usual via the member operator.)
myrec['assignedElement'] = 3000
When assigning a record to another variable, the variable references the same record but doesn't share ownership of it. The record can nevertheless be accessed via the reference without restrictions.
record myrec myrec.firstMember = 123 reference = myrec reference.otherMember = 5 print "\nHere's myrec:" print myrec
Output:
Here's myrec: firstMember:123 otherMember:5
The del
keyword can be used to delete individual variables.
del a del myrec.subrec.something
If the variable owns a record, the record will be destroyed as well.
record soonDeleted reference = soonDeleted del soonDeleted try: print reference catch Error, er: print er
Output:
[NullError] (in RecordValue::verify) Value no longer references a record
Records can be serialized with serialize()
.
record myrec myrec.a = 1 print 'serialize(myrec) =', serialize(myrec) print 'deserialize(serialize(myrec)) =' print deserialize(serialize(myrec))
Output:
serialize(myrec) = (Block of 29 bytes) deserialize(serialize(myrec)) = a:1
A class is a record that acts as the namespace containing the members of the class. Classes can define an initializer method that gets called during instantiation.
record MyClass() a = 'class member' def __init__() self.b = 'local member' end end obj = MyClass() print obj.a print obj.b
Output:
class member local member
Note that there is nothing special about a class — it is simply a record that contains variables, just like any other record. In fact, the only special thing about class instantiation is that behavior of the member operator (.
) is augmented to additionally search for identifiers in the superclasses.
This means that, for instance, an imported module could be used as a class.
Setting up the superclass relationship and calling the initializer method __init__
happens when one uses the call operator ()
on a record.
The special variable self
is set when a function is called via the member operator, referencing the left-hand operand. This is how the initializer and other methods of the class can access the instance.
Arguments can be passed to the initializer method.
record MyClass() def __init__(someVar) self.v = someVar end end obj = MyClass('arg') print obj.v
Output:
arg
A subclass is a class that inherits from an another class.
Normally member lookup proceeds to check through each superclass in order, however it is also possible to manually specify the namespace for identifier lookup. This is done with the ->
notation, as seen in the example below. The initializer method of Subclass calls the initializer of the parent class. Without MyClass->
, the call would recursively be made to Subclass's own initializer.
record MyClass() def __init__(someVar) self.v = someVar end end record Subclass(MyClass) def __init__() self.MyClass->__init__('from sub') end end obj = Subclass() print obj.v
Output:
from sub
The scope notation can be used sequentially. For example, if the parent class is part of another record:
record t.MyClass()
Now in Subclass, the initializer needs to looked up with a sequence of scopes:
self.t->MyClass->__init__('from sub')
The behavior of this statement is:
.
remembers self as the current context.Note that the scope notation does not restrict the lookup to superclasses; one could look up any function this way, while retaining self as the context.
Compare this with:
# This is wrong: self.t.MyClass.__init__('from sub')
This doesn't work because: