-
Notifications
You must be signed in to change notification settings - Fork 22
Eberon Record Constructor
Record Constructor is a procedure associated with a record and invoked every time when an instance of record is instantiated. Being invoked automatically constructor is a unified way to initialize record and maintain record's invariant.
Constructor is declared as a record method with the same name as the record type name:
T = RECORD
PROCEDURE T(); (* this is constructor declaration for record 'T' *)
END;
PROCEDURE T.T();
(* this is constructor definition *)
END;
Constructor cannot have result type specified.
Constructor can have parameters:
T = RECORD
PROCEDURE T(param1: INTEGER, param2: BOOLEAN);
END;
Records having constructor with parameters cannot be declared in VAR section or pointers to such records cannot be passed to procedure NEW. In place variables or operator NEW must be used instead. Also such records cannot be used as elements of static arrays - use dynamic arrays instead.
Constructor can be called as an ordinary procedure by its name. Call result type is the type of constructed record:
record <- T(123, TRUE); (* initialize in place variable 'record' with instance of 'T' *)
NEW T(123, TRUE); (* construct pointer to 'T' using operator NEW *)
do(T()); (* pass instance of 'T' to procedure 'do' *)
T().field; (* construct instance of record 'T' and designate its field 'field' *)
Constructor parameters are inherited from base type to derived:
Base = RECORD
PROCEDURE Base(param: INTEGER);
END;
Derived = RECORD(Base)
END;
Here constructor was not declared for 'Derived' but it was inherited from 'Base' so 'Derived' now can be constructed only using the same parameters as for constructing 'Base':
Derived(123);
Special syntax is introduced to call base constructor and to initialize fields having their own parameterized constructors:
InitList = "|" ("SUPER" ActualParameters fields) | (InitField fields))
fields = {"," InitField}
InitField = ident ActualParameters
Consider following example:
Base = RECORD
PROCEDURE Base(param: INTEGER);
END;
Derived = RECORD(Base)
PROCEDURE Derived();
END;
Constructor 'Derived' has no parameters and 'Base' does have. To pass actual parameters to base constructor SUPER is used in initialization list:
PROCEDURE Derived.Derived() | SUPER(123); (* call base constructor with parameter '123' *)
END;
Similar thing is for fields having parameterized constructors:
Field = RECORD
PROCEDURE Field(a: INTEGER);
END;
T = RECORD
PROCEDURE T();
f: Field;
END;
PROCEDURE T.T() | f(123); (* pass '123' to constructor of record's field 'f' *)
END;
Fields of non-record types also can be referenced in initialization list. In this case field will be assigned to single actual parameter:
T = RECORD
PROCEDURE T();
f1: INTEGER;
f2: BOOLEAN;
END;
PROCEDURE T.T() | f1(123), f2(TRUE);
END;
Fields must be referenced in the initialization list in the same order as they declared in record. For example above "f2(TRUE), f1(123)" will cause compiler error. Only record's own fields can be referenced (not fields from base record). Fields of non-record types may be missed in the list - in this case they will be initialized using default values. Record fields having constructor without parameters will be initialized automatically and must be not referenced in initialization list. All fields are initialized strictly in the same order as they declared in record - no matter if they referenced in the initialization list or initialized automatically. It is possible to use SELF in initialization list but be aware of the initialization order - do not use SELF to access not initialized field (the compiler does not diagnose that).
The order of execution in constructor:
- call base constructor (if present), using SUPER (with parameters) or automatically (no parameters)
- initialize fields strictly in order from record declaration (mixing default values or supplied parameters in the initialization list)
- execute constructor body (BEGIN/END section)
Example:
Base = RECORD
PROCEDURE Base(param: INTEGER);
END;
Field1 = RECORD
PROCEDURE Field1(a: INTEGER);
END;
Field2 = RECORD
PROCEDURE Field2();
END;
Field3 = RECORD
END;
T = RECORD
int: INTEGER;
field1: Field1;
field2: Field2;
field3: Field3;
bool: BOOLEAN;
set: SET;
END;
PROCEDURE T.T() | SUPER(123), field1(SELF.int), bool(TRUE);
BEGIN
ASSERT(SELF.bool);
END;
Execution order for example above will be:
- call Base(123)
- initialize field 'int' with default value '0' - automatically
- initialize field 'field1' by calling Field1(SELF.int)
- initialize field 'field2' by calling Field2() - automatically
- initialize field 'field3' (without constructor) - automatically
- initialize field 'bool' with TRUE
- initialize field 'set' with default value '{}' - automatically
Constructor can be exported using '*' notation
T* = RECORD
PROCEDURE T*();
END;
If constructor is exported then the record itself must be exported too. If constructor is not exported then record cannot be instantiated or extended in other modules.