User Tools

Site Tools


script:walkthrough:records

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:

  1. Look up self.
  2. The member operator . remembers self as the current context.
  3. Look up t.
  4. Within t, look up MyClass.
  5. Within MyClass, look up __init__.
  6. 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.
script/walkthrough/records.txt · Last modified: 2019-11-23 19:38 by skyjake