- Exceptions ^ ^ Functions ->
====== Records ======
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.
===== Creation =====
There are a few alternative ways to create a record:
* ''record'' statement.
* Built-in ''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
===== Members =====
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
===== Aliasing =====
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
===== Deletion =====
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
===== Serialization =====
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
===== Classes =====
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
==== Inheritance ====
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:
- Look up //self//.
- The member operator ''.'' remembers //self// as the current context.
- Look up //t//.
- Within //t//, look up //MyClass//.
- Within //MyClass//, look up //__init__//.
- Make a function call on //__init__// using //self// as the context (//self// will be available in the function's localspace for the duration of the call).
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:
* //t// is in the global namespace, so it's not found within //self//.
* Even if that somehow worked, //__init__// would use //MyClass// as the context for the function call.