User Tools

Site Tools


script:walkthrough:records

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
Next revision
Previous revision
script:walkthrough:records [2019-11-23 10:35] – [Inheritance] skyjakescript:walkthrough:records [2019-11-23 19:38] (current) skyjake
Line 1: Line 1:
 +<- 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 ''<nowiki>__init__</nowiki>'' 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 ''<nowiki>-></nowiki>'' notation, as seen in the example below. The initializer method of Subclass calls the initializer of the parent class. Without ''<nowiki>MyClass-></nowiki>'', 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 //<nowiki>__init__</nowiki>//.
 +  - Make a function call on //<nowiki>__init__</nowiki>// 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, //<nowiki>__init__</nowiki>// would use //MyClass// as the context for the function call.