Просмотр исходного кода

Parameterized constructors.
Inlcude unit tests to generated html page.
Rewrite JS code (partially) in eberon.

Vladislav Folts 10 лет назад
Родитель
Сommit
445cfded49

BIN
bin/compiled.zip


+ 31 - 16
browser/oberonjs.html

@@ -58,12 +58,11 @@ END test.
 <textarea id="result" rows="10" cols="80">
 </textarea>
 </div>
-
-    <p>
-        <button onclick="run()">Run</button>
-    </p>
+    <p><button onclick="run()">Run</button></p>
+    <p><button onclick="run_tests()">Run Tests</button></p>
 <p id="runErrors" style="color:red"></p>
 <p id="runTime"></p>
+<p id="testsResult"></p>
 <p id="version"></p>
 
 <script src="oc.js"></script>
@@ -107,20 +106,36 @@ END test.
         document.getElementById("compileTime").textContent = "compile time (seconds): " + compileTime;
             }
 
-            function run(){
-        var errElement = document.getElementById("runErrors");
-        errElement.textContent = "";
-        var start = new Date();
-        try{
-            eval(javascriptEditor.getValue());
+        function timed_run(action){
+            var start = new Date();
+            var result = action();
+            var runTime = (new Date() - start) / 1000;
+            document.getElementById("runTime").textContent = "run time (seconds): " + runTime;
+            return result;
         }
-        catch (e){
-            var errors = "" + e;
-            errElement.textContent = errors;
+
+        function run(){
+            var errElement = document.getElementById("runErrors");
+            errElement.textContent = "";
+            timed_run(function(){
+                try{
+                    eval(javascriptEditor.getValue());
+                }
+                catch (e){
+                    var errors = "" + e;
+                    errElement.textContent = errors;
+                }
+            });
+        }
+
+        function run_tests(){
+            var resultsElement = document.getElementById("testsResult");
+            resultsElement.textContent = "running...";
+            var result = timed_run(function(){
+                return require("test_unit.js").run();
+            });
+            resultsElement.textContent = result ? "Tests OK" : "Tests failed";
         }
-        var runTime = (new Date() - start) / 1000;
-        document.getElementById("runTime").textContent = "run time (seconds): " + runTime;
-    }
 </script>
 
 </body>

+ 7 - 5
build.py

@@ -110,7 +110,7 @@ def run_tests(bin, unit_test=None, code_test=None):
         code_test = '*'
 
     if unit_test:
-        unit_tests = os.path.join(root, 'test', 'test_unit.js')
+        unit_tests = os.path.join(root, 'test', 'test_unit_run.js')
         args = [unit_tests]
         if unit_test != '*':
             args += [unit_test]
@@ -176,15 +176,16 @@ def build_html(options):
         print("current html is up to date, do nothing")
         return
 
-    print('unpacking compiled js to %s...' % package.root)
-    package.unpack()
+    if not options.do_not_unpack_compiled:
+        print('unpacking compiled js to %s...' % package.root)
+        package.unpack()
 
     if not os.path.exists(out):
         os.mkdir(out)
 
-    link(['oc.js', 'oberon/oberon_grammar.js', 'eberon/eberon_grammar.js'],
+    link(['oc.js', 'oberon/oberon_grammar.js', 'eberon/eberon_grammar.js', 'test_unit.js'],
          os.path.join(out, 'oc.js'),
-         ['src', 'bin'],
+         ['src', 'bin', 'test'],
          version)
     copy('browser/oberonjs.html', out)
     for d in ['codemirror', 'jslibs']:
@@ -245,6 +246,7 @@ class html_target(object):
     def setup_options(parser):
         parser.add_option('--out', help='output directory, default: "_out"', default='_out')
         parser.add_option('--set-version', action="store_true", help='include version in built html')
+        parser.add_option('--do-not-unpack-compiled', action="store_true", help='do unpack already compiled "binaries", use current')
 
     def __init__(self, options):
         build_html(options)

+ 7 - 32
src/context.js

@@ -6,6 +6,7 @@ var CodeGenerator = require("js/CodeGenerator.js");
 var Errors = require("js/Errors.js");
 var Module = require("js/Module.js");
 var op = require("js/Operator.js");
+var ObContext = require("js/Context.js");
 var Parser = require("parser.js");
 var Procedure = require("js/Procedure.js");
 var Class = require("rtl.js").Class;
@@ -269,15 +270,6 @@ exports.QualifiedIdentificator = ChainedContext.extend({
     }
 });
 
-var IdentdefInfo = Class.extend({
-    init: function Context$Identdef(id, exported){
-        this.__id = id;
-        this.__exported = exported;
-    },
-    id: function(){return this.__id;},
-    exported: function(){return this.__exported;}
-});
-
 exports.Identdef = ChainedContext.extend({
     init: function IdentdefContext(context){
         ChainedContext.prototype.init.call(this, context);
@@ -290,7 +282,7 @@ exports.Identdef = ChainedContext.extend({
         this.parent().handleIdentdef(this._makeIdendef());
     },
     _makeIdendef: function(){
-        return new IdentdefInfo(this._id, this._export);
+        return ObContext.makeIdentdefInfo(this._id, this._export);
     }
 });
 
@@ -771,8 +763,8 @@ exports.ArrayDecl = HandleSymbolAsType.extend({
             return rtl.makeCharArray(dimensions);
 
         var initializer = type instanceof Type.Array || type instanceof Type.Record
-            ? "function(){return " + type.initializer(this) + ";}"
-            : type.initializer(this);
+            ? "function(){return " + type.initializer(this, false, "") + ";}"
+            : type.initializer(this, false, "");
         return rtl.makeArray(dimensions + ", " + initializer);
     },
     _makeType: function(elementsType, init, length){
@@ -1651,7 +1643,7 @@ exports.VariableDeclaration = HandleSymbolAsType.extend({
                 this.checkExport(varName);
             this.currentScope().addSymbol(Symbol.makeSymbol(varName, v), id.exported());
             var t = v.type();
-            gen.write("var " + varName + " = " + t.initializer(this) + ";");
+            gen.write("var " + varName + " = " + t.initializer(this, false, "") + ";");
         }
 
         gen.write("\n");
@@ -1726,20 +1718,6 @@ function isTypeRecursive(type, base){
     return false;
 }
 
-var RecordField = Class.extend.call(Type.Field, {
-    init: function Context$RecordField(identdef, type){
-        this.__identdef = identdef;
-        this.__type = type;
-    },
-    id: function(){return this.__identdef.id();},
-    exported: function(){return this.__identdef.exported();},
-    identdef: function(){return this.__identdef;},
-    type: function(){return this.__type;},
-    asVar: function(isReadOnly){ 
-        return Type.makeVariable(this.__type, isReadOnly); 
-    }
-});
-
 exports.RecordDecl = ChainedContext.extend({
     init: function RecordDeclContext(context, makeRecord){
         ChainedContext.prototype.init.call(this, context);
@@ -1795,7 +1773,7 @@ exports.RecordDecl = ChainedContext.extend({
         var ownFields = Type.recordOwnFields(type);
         for(var f in ownFields){
             var fieldType = ownFields[f].type();
-            result += "this." + mangleField(f, fieldType) + " = " + fieldType.initializer(this) + ";\n";
+            result += "this." + mangleField(f, fieldType) + " = " + fieldType.initializer(this, false, "") + ";\n";
         }
         return result;
     },
@@ -1807,7 +1785,7 @@ exports.RecordDecl = ChainedContext.extend({
         return this.language().rtl.extend(this.__cons, qualifiedBase) + ";\n";
     },
     _makeField: function(field, type){
-        return new RecordField(field, type);
+        return Type.makeRecordField(field, type);
     }
 });
 
@@ -2059,10 +2037,7 @@ exports.endCallMsg = endCallMsg;
 exports.Chained = ChainedContext;
 exports.endParametersMsg = endParametersMsg;
 exports.getSymbolAndScope = getSymbolAndScope;
-exports.IdentdefInfo = IdentdefInfo;
 exports.makeProcCall = makeProcCall;
 exports.unwrapType = unwrapType;
-exports.IdentdefInfo = IdentdefInfo;
-exports.RecordField = RecordField;
 exports.RelationOps = RelationOps;
 exports.HandleSymbolAsType = HandleSymbolAsType;

+ 2 - 2
src/eberon/EberonArray.ob

@@ -1,5 +1,5 @@
 MODULE EberonArray;
-IMPORT Code, EberonTypes, Errors, LanguageContext, Procedure, Types;
+IMPORT Code, Context, EberonTypes, Errors, LanguageContext, Procedure, Types;
 CONST
     methodNameIndexOf = "indexOf";
 TYPE
@@ -40,7 +40,7 @@ PROCEDURE MethodField.type(): Types.PType;
     RETURN SELF.method
 END MethodField.type;
 
-PROCEDURE MethodField.asVar(): Types.PId;
+PROCEDURE MethodField.asVar(isReadOnly: BOOLEAN; cx: Context.Type): Types.PId;
     RETURN EberonTypes.makeMethod(SELF.method)
 END MethodField.asVar;
 

+ 12 - 5
src/eberon/EberonConstructor.ob

@@ -1,23 +1,30 @@
 MODULE EberonConstructor;
-IMPORT Code, LanguageContext, Procedure, Types;
+IMPORT Code, EberonRecord, LanguageContext, Procedure, Types;
 TYPE
     ConstructorCall = RECORD(Procedure.StdCall)
-        recordType: Types.PRecord;
+        recordType: EberonRecord.PRecord;
     END;
 
 PROCEDURE ConstructorCall.make(args: ARRAY OF Code.PExpression; cx: LanguageContext.PType): Code.PExpression;
 BEGIN
-    Procedure.processArguments(args, SELF.args, NIL, NIL);
-    RETURN Code.makeSimpleExpression(SELF.recordType.initializer(cx^, FALSE), SELF.recordType);
+    argCode <- Procedure.makeArgumentsCode(cx);
+    Procedure.processArguments(args, SELF.args, argCode, cx.types);
+    RETURN Code.makeSimpleExpression(SELF.recordType.initializer(cx^, FALSE, argCode.result()), SELF.recordType);
 END;
 
-PROCEDURE makeConstructorCall*(type: Types.PRecord; cx: LanguageContext.PType): Procedure.PCallGenerator;
+PROCEDURE makeConstructorCall*(
+    type: EberonRecord.PRecord; 
+    cx: LanguageContext.PType
+    ): Procedure.PCallGenerator;
 VAR
     call: POINTER TO ConstructorCall;
 BEGIN
     NEW(call);
     Procedure.initStdCall(call);
     call.recordType := type; 
+    IF type.customConstructor # NIL THEN
+        call.args := type.customConstructor.args();
+    END;
     RETURN Procedure.makeCallGenerator(call, cx)
 END;
 

+ 25 - 0
src/eberon/EberonContext.ob

@@ -0,0 +1,25 @@
+MODULE EberonContext;
+IMPORT Context;
+TYPE
+    IdentdefInfo* = RECORD(Context.IdentdefInfo)
+        PROCEDURE isReadOnly*(): BOOLEAN;
+        
+        ro: BOOLEAN;
+    END;
+    PIdentdefInfo* = POINTER TO IdentdefInfo;
+
+PROCEDURE IdentdefInfo.isReadOnly(): BOOLEAN;
+    RETURN SELF.ro;
+END;
+
+PROCEDURE makeIdentdefInfo*(id: STRING; exported: BOOLEAN; ro: BOOLEAN): PIdentdefInfo;
+VAR
+    result: PIdentdefInfo;
+BEGIN
+    NEW(result);
+    Context.initIdentdefInfo(id, exported, result^);
+    result.ro := ro;
+    RETURN result;
+END;
+
+END EberonContext.

+ 1 - 1
src/eberon/EberonDynamicArray.ob

@@ -46,7 +46,7 @@ BEGIN
     RETURN result
 END arrayDimensionDescription;
 
-PROCEDURE DynamicArray.initializer(cx: Context.Type; forNew: BOOLEAN): STRING;
+PROCEDURE DynamicArray.initializer(cx: Context.Type; forNew: BOOLEAN; code: STRING): STRING;
     RETURN "[]"
 END DynamicArray.initializer;
 

+ 391 - 0
src/eberon/EberonRecord.ob

@@ -0,0 +1,391 @@
+MODULE EberonRecord;
+IMPORT 
+    Cast, Context, EberonContext, EberonTypes, Errors, JS, JsMap, ScopeBase, Object, String, Types;
+TYPE
+    Record* = RECORD(Types.Record)
+        PROCEDURE defineConstructor(type: Types.PDefinedProcedure): STRING;
+        PROCEDURE addMethod(methodId: Context.PIdentdefInfo; type: Types.PProcedure);
+        PROCEDURE defineMethod(methodId: Context.PIdentdefInfo; type: EberonTypes.PMethodType);
+        PROCEDURE requireNewOnly();
+        PROCEDURE setRecordInitializationCode(fieldsInitializationCode, inheritanceCode: STRING);
+
+        customConstructor-: Types.PDefinedProcedure;
+        finalized: BOOLEAN;
+        declaredMethods: JsMap.Type;
+        definedMethods: ARRAY * OF STRING;
+        abstractMethods: ARRAY * OF STRING;
+        instantiated: BOOLEAN;
+        createByNewOnly: BOOLEAN;
+        declaredAsVariable: BOOLEAN;
+        lazyDefinitions: JsMap.Type;
+        nonExportedMethods: ARRAY * OF STRING;
+        inheritanceCode, fieldsInitializationCode-: STRING;
+    END;
+    PRecord* = POINTER TO Record;
+
+    RecordField = RECORD(Types.RecordField)
+        record: PRecord;
+    END;
+
+    RecordFieldAsMethod = RECORD(Types.RecordField)
+    END;
+    PRecordFieldAsMethod = POINTER TO RecordFieldAsMethod;
+
+    MethodIds = POINTER TO RECORD(Object.Type)
+        ids: ARRAY * OF STRING;
+    END;
+
+    EnsureMethodDefinitionsClosure = RECORD(Object.Type)
+        record: PRecord;
+        result: ARRAY * OF STRING;
+    END;
+
+    RequireMethodDefinitionClosure = RECORD(Object.Type)
+        record: PRecord;
+        base: PRecord;
+    END;
+
+PROCEDURE cannotInstantiateErrMsg(r: Types.Record): STRING;
+    RETURN "cannot instantiate '" 
+         + r.name 
+         + "' because it has abstract method(s)";
+END;
+
+PROCEDURE hasMethodDefinition(r: PRecord; id: STRING): BOOLEAN;
+BEGIN
+    type <- r;
+    WHILE (type # NIL) & (type.definedMethods.indexOf(id) = -1) DO
+        type := type.base(PRecord);
+    END;
+    RETURN type # NIL;
+END;
+
+PROCEDURE findMethodDeclaration(r: PRecord; id: STRING): Types.PField;
+VAR
+    result: Object.PType;
+BEGIN
+    type <- r;
+    WHILE (type # NIL) & ~JsMap.find(type.declaredMethods, id, result) DO
+        type := type.base(PRecord);
+    END;
+    RETURN result(Types.PField);
+END;
+
+PROCEDURE ensureMethodDefinitionsForEach(key: STRING; value: Object.PType; VAR closure: Object.Type);
+    PROCEDURE do(ids: ARRAY OF STRING; closure: EnsureMethodDefinitionsClosure);
+    VAR
+        report: ARRAY * OF STRING;
+    BEGIN
+        FOR i <- 0 TO LEN(ids) - 1 DO
+            m <- ids[i];
+            IF ~hasMethodDefinition(closure.record, m) THEN
+                report.add(m);
+            END;
+        END;
+
+        IF LEN(report) # 0 THEN
+            closure.result.add(key + ": " + String.join(report, ", "));
+        END;
+    END;
+BEGIN
+    do(value(MethodIds).ids, closure(EnsureMethodDefinitionsClosure));
+END;
+
+PROCEDURE ensureMethodDefinitions(r: PRecord; reasons: JsMap.Type);
+VAR
+    closure: EnsureMethodDefinitionsClosure;
+BEGIN
+    closure.record := r;
+    JsMap.forEach(reasons, ensureMethodDefinitionsForEach, closure);
+    IF LEN(closure.result) # 0 THEN
+        Errors.raise(String.join(closure.result, "; "));
+    END;
+END;
+
+PROCEDURE makeMethodIds(): MethodIds;
+VAR
+    result: MethodIds;
+BEGIN
+    NEW(result);
+    RETURN result;
+END;
+
+PROCEDURE requireMethodDefinition*(r: PRecord; id: STRING; reason: STRING);
+VAR
+    existingIds: Object.PType;
+
+    PROCEDURE makeIds(): MethodIds;
+    BEGIN
+        result <- makeMethodIds();
+        result.ids.add(id);
+        RETURN result;
+    END;
+
+    PROCEDURE addIfNotThere(VAR ids: ARRAY * OF STRING);
+    BEGIN
+        IF ids.indexOf(id) = -1 THEN
+            ids.add(id);
+        END;
+    END;
+
+BEGIN
+    IF findMethodDeclaration(r, id) = NIL THEN
+        Errors.raise("there is no method '" + id + "' in base type(s)");
+    END;
+
+    IF r.finalized THEN
+        reasons <- JsMap.make();
+        JsMap.put(reasons, reason, makeIds());
+        ensureMethodDefinitions(r, reasons);
+    ELSE
+        IF ~JsMap.find(r.lazyDefinitions, reason, existingIds) THEN
+            JsMap.put(r.lazyDefinitions, reason, makeIds());
+        ELSE
+            addIfNotThere(existingIds(MethodIds).ids);
+        END;
+    END;
+END;
+
+PROCEDURE requireMethodDefinitionForEach(key: STRING; value: Object.PType; VAR closure: Object.Type);
+    PROCEDURE do(closure: RequireMethodDefinitionClosure);
+    BEGIN
+        IF ~hasMethodDefinition(closure.record, key) THEN
+            requireMethodDefinition(closure.base, key, cannotInstantiateErrMsg(closure.record^));
+        END;
+    END;
+BEGIN
+    do(closure(RequireMethodDefinitionClosure));
+END;
+
+PROCEDURE ensureNonAbstract(r: PRecord);
+VAR
+    closure: RequireMethodDefinitionClosure;
+BEGIN
+    IF LEN(r.abstractMethods) # 0 THEN
+        Errors.raise(cannotInstantiateErrMsg(r^) + ": " + String.join(r.abstractMethods, ", "));
+    END;
+
+    baseType <- r.base(PRecord);
+    closure.record := r;
+    WHILE baseType # NIL DO
+        IF ~baseType.finalized THEN
+            closure.base := baseType;
+            JsMap.forEach(baseType.declaredMethods, requireMethodDefinitionForEach, closure);
+        END;
+        baseType := baseType.base(PRecord);
+    END;
+END;
+
+PROCEDURE ensureVariableCanBeDeclared(r: PRecord);
+BEGIN
+    type <- r;
+    WHILE type # NIL DO
+        IF type.createByNewOnly THEN
+            Errors.raise(
+                    "cannot declare a variable of type '" 
+                  + type.name + "' (and derived types) "
+                  + "because SELF(POINTER) was used in its method(s)");
+        END;
+        type := type.base(PRecord);
+    END;
+END;
+
+PROCEDURE RecordFieldAsMethod.asVar(isReadOnly: BOOLEAN; cx: Context.Type): Types.PId;
+    RETURN EberonTypes.makeMethod(SELF.type()); 
+END;
+
+PROCEDURE makeRecordFieldAsMethod(identdef: Context.PIdentdefInfo; type: Types.PType): PRecordFieldAsMethod;
+VAR
+    result: PRecordFieldAsMethod;
+BEGIN
+    NEW(result);
+    Types.initRecordField(identdef, type, result^);
+    RETURN result; 
+END;
+
+PROCEDURE Record.initializer(cx: Context.Type; forNew: BOOLEAN; code: STRING): STRING;
+BEGIN
+    IF SELF.finalized THEN
+        ensureNonAbstract(SELF(POINTER));
+        IF ~forNew THEN
+            ensureVariableCanBeDeclared(SELF(POINTER));
+        END;
+    ELSE
+        SELF.instantiated := TRUE;
+        IF ~forNew THEN
+            SELF.declaredAsVariable := TRUE;
+        END;
+    END;
+
+    RETURN SUPER(cx, forNew, code);
+END;
+
+PROCEDURE Record.findSymbol(id: STRING): Types.PField;
+BEGIN
+    result <- findMethodDeclaration(SELF(POINTER), id);
+    IF result = NIL THEN
+        result := SUPER(id);
+    END;
+    RETURN result;
+END;
+
+PROCEDURE Record.addField(f: Types.PField);
+BEGIN
+    id <- f.id();
+    IF findMethodDeclaration(SELF(POINTER), id) # NIL THEN
+        Errors.raise(
+            "cannot declare field, record already has method '" + id +"'");
+    END;
+    SUPER(f);
+END;
+
+PROCEDURE Record.addMethod(methodId: Context.PIdentdefInfo; type: Types.PProcedure);
+VAR
+    msg: STRING;
+BEGIN
+    id <- methodId.id();
+    existingField <- SELF.findSymbol(id);
+    IF existingField # NIL THEN
+        IF existingField.type()^ IS EberonTypes.MethodType THEN
+            msg := "cannot declare a new method '" 
+                 + id 
+                 + "': method already was declared";
+        ELSE
+            msg := "cannot declare method, record already has field '" + id + "'";
+        END;
+        Errors.raise(msg);
+    END;
+
+    JsMap.put(SELF.declaredMethods, id, makeRecordFieldAsMethod(methodId, type));
+    IF ~methodId.exported() THEN
+        SELF.nonExportedMethods.add(id);
+    END;
+END;
+
+PROCEDURE Record.defineMethod(methodId: Context.PIdentdefInfo; type: EberonTypes.PMethodType);
+VAR 
+    existingType: Types.PDefinedProcedure;
+BEGIN
+    id <- methodId.id();
+    IF SELF.definedMethods.indexOf(id) # -1 THEN
+        Errors.raise("method '" + SELF.name + "." + id + "' already defined");
+    END;
+
+    existingField <- SELF.findSymbol(id);
+    IF existingField # NIL THEN
+        t <- existingField.type();
+        IF t^ IS EberonTypes.MethodType THEN
+            existingType := t.procType();
+        END;
+    END;
+    IF existingType = NIL THEN
+        Errors.raise("'" + SELF.name + "' has no declaration for method '" + id + "'");
+    END;
+
+    addType <- type.procType();
+    IF ~Cast.areProceduresMatch(existingType, addType) THEN
+        Errors.raise("overridden method '" + id + "' signature mismatch: should be '"
+                   + existingType.description() + "', got '" 
+                   + addType.description() + "'");
+    END;
+    
+    SELF.definedMethods.add(id);
+END;
+
+PROCEDURE Record.requireNewOnly();
+BEGIN
+    SELF.createByNewOnly := TRUE;
+END;
+
+PROCEDURE Record.setRecordInitializationCode(fieldsInitializationCode, inheritanceCode: STRING);
+BEGIN
+    SELF.fieldsInitializationCode := fieldsInitializationCode;
+    SELF.inheritanceCode := inheritanceCode;
+END;
+
+PROCEDURE Record.defineConstructor(type: Types.PDefinedProcedure): STRING;
+BEGIN
+    IF SELF.customConstructor # NIL THEN
+        Errors.raise("constructor '" + SELF.name + "' already defined");
+    END;
+    IF type.result() # NIL THEN
+        Errors.raise("constructor '" + SELF.name + "' cannot have result type specified");
+    END;
+
+    SELF.customConstructor := type;
+    RETURN SELF.inheritanceCode;
+END;
+
+PROCEDURE collectAbstractMethods(r: Record);
+VAR
+    methods: ARRAY * OF STRING;
+BEGIN
+    selfMethods <- JsMap.keys(r.declaredMethods);
+    baseType <- r.base(PRecord);
+    IF baseType # NIL THEN
+        JS.do("methods = baseType.abstractMethods.concat(selfMethods);");
+    ELSE
+        methods := selfMethods;
+    END;
+
+    FOR i <- 0 TO LEN(methods) - 1 DO
+        m <- methods[i];
+        IF r.definedMethods.indexOf(m) = -1 THEN
+            r.abstractMethods.add(m);
+        END;
+    END;
+END;
+
+PROCEDURE Record.finalize();
+BEGIN
+    SELF.finalized := TRUE;
+    collectAbstractMethods(SELF);
+    IF SELF.instantiated THEN
+        ensureNonAbstract(SELF(POINTER));
+    END;
+    
+    IF SELF.declaredAsVariable THEN
+        ensureVariableCanBeDeclared(SELF(POINTER));
+    END;
+    
+    ensureMethodDefinitions(SELF(POINTER), SELF.lazyDefinitions);
+
+    FOR i <- 0 TO LEN(SELF.nonExportedMethods) - 1 DO
+        JsMap.erase(SELF.declaredMethods, SELF.nonExportedMethods[i]);
+    END;
+    SELF.nonExportedMethods.clear();
+
+    SUPER();
+END;
+
+PROCEDURE RecordField.asVar(isReadOnly: BOOLEAN; cx: Context.Type): Types.PId;
+BEGIN
+    actualReadOnly <- isReadOnly;
+    IF ~actualReadOnly & (LEN(cx.qualifyScope(Types.recordScope(SELF.record^))) # 0) THEN
+        actualReadOnly := SELF.identdef()(EberonContext.PIdentdefInfo).isReadOnly();
+    END;
+    RETURN SUPER(actualReadOnly, cx); 
+END;
+
+PROCEDURE makeRecord*(name: STRING; cons: STRING; scope: ScopeBase.PType): Types.PRecord;
+VAR
+    result: PRecord;
+BEGIN
+    NEW(result);
+    Types.initRecord(result, name, cons, scope);
+    result.declaredMethods := JsMap.make();
+    result.lazyDefinitions := JsMap.make();
+    RETURN result;
+END;
+
+PROCEDURE makeRecordField*(identdef: Context.PIdentdefInfo; type: Types.PType; record: PRecord): Types.PRecordField;
+VAR
+    result: POINTER TO RecordField;
+BEGIN
+    NEW(result);
+    Types.initRecordField(identdef, type, result^);
+    result.record := record;
+    RETURN result;
+END;
+
+END EberonRecord.

+ 3 - 18
src/eberon/EberonTypes.ob

@@ -5,15 +5,12 @@ TYPE
     CallGenerator = PROCEDURE(cx: LanguageContext.PType; type: Types.DefinedProcedure): Procedure.PCallGenerator;
 
     MethodType* = RECORD(Procedure.Std)
-        PROCEDURE procType(): Types.PProcedure;
-        PROCEDURE args(): Types.ProcedureArguments;
-        PROCEDURE result(): Types.PType;
-        PROCEDURE procDescription(): STRING;
+        PROCEDURE procType*(): Types.PDefinedProcedure;
 
         type: Types.PDefinedProcedure;
         call: CallGenerator
     END;
-    PMethodType = POINTER TO MethodType;
+    PMethodType* = POINTER TO MethodType;
 
     MethodVariable* = RECORD(Types.ProcedureId)
     END;
@@ -22,26 +19,14 @@ PROCEDURE MethodType.designatorCode(id: STRING): STRING;
     RETURN id
 END MethodType.designatorCode;
 
-PROCEDURE MethodType.procType(): Types.PProcedure;
+PROCEDURE MethodType.procType(): Types.PDefinedProcedure;
     RETURN SELF.type
 END MethodType.procType;
 
-PROCEDURE MethodType.args(): Types.ProcedureArguments;
-    RETURN SELF.type.args()
-END MethodType.args;
-
-PROCEDURE MethodType.result(): Types.PType;
-    RETURN SELF.type.result()
-END MethodType.result;
-
 PROCEDURE MethodType.description(): STRING;
     RETURN "method '" + SELF.name + "'"
 END MethodType.description;
 
-PROCEDURE MethodType.procDescription(): STRING;
-    RETURN SELF.type.description()
-END MethodType.procDescription;
-
 PROCEDURE MethodType.callGenerator(cx: LanguageContext.PType): Procedure.PCallGenerator;
     RETURN SELF.call(cx, SELF.type^)
 END MethodType.callGenerator;

+ 9 - 238
src/eberon/eberon_context.js

@@ -6,7 +6,9 @@ var Code = require("js/Code.js");
 var CodeGenerator = require("js/CodeGenerator.js");
 var Context = require("context.js");
 var EberonConstructor= require("js/EberonConstructor.js");
+var EberonContext= require("js/EberonContext.js");
 var EberonDynamicArray= require("js/EberonDynamicArray.js");
+var EberonRecord = require("js/EberonRecord.js");
 var EberonScope = require("js/EberonScope.js");
 var EberonString = require("js/EberonString.js");
 var EberonTypes = require("js/EberonTypes.js");
@@ -166,14 +168,6 @@ var InPlaceStringLiteral = TypeNarrowVariable.extend({
     idType: function(){return "string literal";}
 });
 
-var IdentdefInfo = Context.IdentdefInfo.extend({
-    init: function(id, exported, ro){
-        Context.IdentdefInfo.prototype.init.call(this, id, exported);
-        this.__ro = ro;
-    },
-    isReadOnly: function(){return this.__ro;}
-});
-
 var Identdef = Context.Identdef.extend({
     init: function(parent){
         Context.Identdef.prototype.init.call(this, parent);
@@ -185,7 +179,7 @@ var Identdef = Context.Identdef.extend({
         Context.Identdef.prototype.handleLiteral.call(this, l);
     },
     _makeIdendef: function(){
-        return new IdentdefInfo(this._id, this._export, this.__ro);
+        return EberonContext.makeIdentdefInfo(this._id, this._export, this.__ro);
     }
 });
 
@@ -389,228 +383,6 @@ var AssignmentOrProcedureCall = Context.Chained.extend({
     }
 });
 
-var RecordField = Context.RecordField.extend({
-    init: function EberonContext$RecordField(field, type, recordType){
-        Context.RecordField.prototype.init.call(this, field, type);
-        this.__recordType = recordType;
-    },
-    asVar: function(isReadOnly, context){ 
-        if (!isReadOnly && context.qualifyScope(Type.recordScope(this.__recordType)))
-            isReadOnly = this.identdef().isReadOnly();
-        return Context.RecordField.prototype.asVar.call(this, isReadOnly); 
-    }
-});
-
-var RecordFieldAsMethod = Context.RecordField.extend({
-    init: function EberonContext$RecordFieldAsMethod(field, type){
-        Context.RecordField.prototype.init.call(this, field, type);
-    },
-    asVar: function(){ 
-        return EberonTypes.makeMethod(this.type()); 
-    }
-});
-var RecordType = Class.extend.call(Type.Record, {
-    init: function EberonContext$RecordType(name, cons, scope){
-        Type.Record.call(this);
-        Type.initRecord(this, name, cons, scope);
-        this.__customConstructor = undefined;
-        this.__finalized = false;
-        this.__declaredMethods = {};
-        this.__definedMethods = [];
-        this.__abstractMethods = [];
-        this.__instantiated = false;
-        this.__createByNewOnly = false;
-        this.__declaredAsVariable = false;
-        this.__lazyDefinitions = {};
-        this.__nonExportedMethods = [];
-        this.__inheritanceCode = undefined;
-    },
-    initializer: function(context, forNew){
-        if (this.__finalized){
-            this.__ensureNonAbstract();
-            if (!forNew)
-                this.__ensureVariableCanBeDeclared();
-        }
-        else {
-            this.__instantiated = true;
-            if (!forNew)
-                this.__declaredAsVariable = true;
-        }
-
-        return Type.Record.prototype.initializer.call(this, context);
-    },
-    customConstructor: function(){return this.__customConstructor;},
-    findSymbol: function(id){
-        var result = this.__hasMethodDeclaration(id);
-        if (!result)
-            result = Type.Record.prototype.findSymbol.call(this, id);
-        return result;
-    },
-    addField: function(field, type){
-        var id = field.id();
-        if (this.__hasMethodDeclaration(id))
-            throw new Errors.Error(
-                "cannot declare field, record already has method '" + id +"'");
-        return Type.Record.prototype.addField.call(this, field, type);
-    },
-    addMethod: function(methodId, type){
-        var id = methodId.id();
-        var existingField = this.findSymbol(id);
-        if (existingField)
-            throw new Errors.Error(
-                  existingField.type() instanceof EberonTypes.MethodType
-                ?   "cannot declare a new method '" + id 
-                  + "': method already was declared"
-                : "cannot declare method, record already has field '" + id + "'");
-
-        this.__declaredMethods[id] = new RecordFieldAsMethod(methodId, type);
-
-        if (!methodId.exported())
-            this.__nonExportedMethods.push(id);
-    },
-    setRecordInitializationCode: function(fieldsInitializationCode, inheritanceCode){
-        this.__fieldsInitializationCode = fieldsInitializationCode;
-        this.__inheritanceCode = inheritanceCode;
-    },
-    fieldsInitializationCode: function(){return this.__fieldsInitializationCode;},
-    defineConstructor: function(type){
-        if (this.__customConstructor)
-            throw new Errors.Error("constructor '" + Type.typeName(this) + "' already defined");
-        if (type.result())
-            throw new Errors.Error("constructor '" + Type.typeName(this) + "' cannot have result type specified");
-        this.__customConstructor = type;
-        return this.__inheritanceCode;
-    },
-    defineMethod: function(methodId, type){
-        var base = Type.recordBase(this);
-        var id = methodId.id();
-
-        if (this.__definedMethods.indexOf(id) != -1)
-            throw new Errors.Error(
-                  "method '" + Type.typeName(this) + "." + id + "' already defined");
-
-        var existingField = this.findSymbol(id);
-        if (!existingField || !(existingField.type() instanceof EberonTypes.MethodType)){
-            throw new Errors.Error(
-                  "'" + Type.typeName(this) + "' has no declaration for method '" + id 
-                + "'");
-        }
-        var existing = existingField.type();
-        if (!Cast.areProceduresMatch(existing, type))
-            throw new Errors.Error(
-                  "overridden method '" + id + "' signature mismatch: should be '"
-                + existing.procDescription() + "', got '" 
-                + type.procDescription() + "'");
-        
-        this.__definedMethods.push(id);
-    },
-    requireMethodDefinition: function(id, reason){
-        if (!this.__hasMethodDeclaration(id))
-            throw new Errors.Error(
-                "there is no method '" + id + "' in base type(s)");
-        if (this.__finalized)
-            this.__ensureMethodDefinitions({reason: [id]});
-        else {
-            var ids = this.__lazyDefinitions[reason];
-            if (!ids){
-                ids = [id];
-                this.__lazyDefinitions[reason] = ids;
-            }
-            else if (ids.indexOf(id) == -1)
-                ids.push(id);
-            }
-    },
-    requireNewOnly: function(){this.__createByNewOnly = true;},
-    abstractMethods: function(){return this.__abstractMethods;},
-    __collectAbstractMethods: function(){
-        var selfMethods = Object.keys(this.__declaredMethods);
-        var baseType = Type.recordBase(this);
-        var methods = baseType ? baseType.abstractMethods().concat(selfMethods)
-                               : selfMethods;
-        for(var i = 0; i < methods.length; ++i){
-            var m = methods[i];
-            if (this.__definedMethods.indexOf(m) == -1)
-                this.__abstractMethods.push(m);
-        }
-    },
-    finalize: function(){
-        this.__finalized = true;
-        this.__collectAbstractMethods();
-        if (this.__instantiated)
-            this.__ensureNonAbstract();
-        if (this.__declaredAsVariable)
-            this.__ensureVariableCanBeDeclared();
-        this.__ensureMethodDefinitions(this.__lazyDefinitions);
-
-        for(var i = 0; i < this.__nonExportedMethods.length; ++i)
-            delete this.__declaredMethods[this.__nonExportedMethods[i]];
-        delete this.__nonExportedMethods;
-
-        Type.Record.prototype.finalize.call(this);
-    },
-    __ensureMethodDefinitions: function(reasons){
-        var result = [];
-        for(var reason in reasons){
-            var ids = reasons[reason];
-            var report = [];
-            for(var i = 0; i < ids.length; ++i){
-                var m = ids[i];
-                if (!this.__hasMethodDefinition(m))
-                    report.push(m);
-            }
-            if (report.length)
-                result.push(reason + ": " + report.join(", "));
-        }
-        if (result.length)
-            throw new Errors.Error(result.join("; "));
-    },
-    __ensureNonAbstract: function(){
-        function errMsg(self){
-            return "cannot instantiate '" 
-                 + Type.typeName(self) 
-                 + "' because it has abstract method(s)";
-        }
-
-        var am = this.abstractMethods();
-        if (am.length)
-            throw new Errors.Error(errMsg(this) + ": " + am.join(", ")
-                );
-
-        var baseType = Type.recordBase(this);
-        while (baseType){
-            if (!baseType.__finalized)
-                for(var id in baseType.__declaredMethods){
-                    if (!this.__hasMethodDefinition(id))
-                        baseType.requireMethodDefinition(id, errMsg(this));
-                }
-            baseType = Type.recordBase(baseType);
-        }
-    },
-    __ensureVariableCanBeDeclared: function(){
-        var type = this;
-        while (type){
-            if (type.__createByNewOnly)
-                throw new Errors.Error(
-                    "cannot declare a variable of type '" 
-                  + Type.typeName(type) + "' (and derived types) "
-                  + "because SELF(POINTER) was used in its method(s)");
-            type = Type.recordBase(type);
-        }
-    },
-    __hasMethodDeclaration: function(id){
-        var type = this;
-        while (type && !type.__declaredMethods.hasOwnProperty(id))
-            type = Type.recordBase(type);
-        return type && type.__declaredMethods[id];
-    },
-    __hasMethodDefinition: function(id){
-        var type = this;
-        while (type && type.__definedMethods.indexOf(id) == -1)
-            type = Type.recordBase(type);
-        return type;
-    }
-});
-
 function checkOrdinaryExport(id, hint){
     if (id.isReadOnly())
         throw new Errors.Error(hint + " cannot be exported as read-only using '-' mark (did you mean '*'?)");
@@ -648,8 +420,7 @@ var TypeDeclaration = Context.TypeDeclaration.extend({
 
 var RecordDecl = Context.RecordDecl.extend({
     init: function EberonContext$RecordDecl(context){
-        var makeRecord = function(name, cons, scope){return new RecordType(name, cons, scope);};
-        Context.RecordDecl.prototype.init.call(this, context, makeRecord);
+        Context.RecordDecl.prototype.init.call(this, context, EberonRecord.makeRecord);
     },
     handleMessage: function(msg){
         if (msg instanceof MethodOrProcMsg)
@@ -663,7 +434,7 @@ var RecordDecl = Context.RecordDecl.extend({
         return Context.RecordDecl.prototype.handleMessage.call(this, msg);
     },
     _makeField: function(field, type){
-        return new RecordField(field, type, this.__type);
+        return EberonRecord.makeRecordField(field, type, this.__type);
     },
     endParse: function(){
         var gen = this.codeGenerator();
@@ -674,7 +445,7 @@ var RecordDecl = Context.RecordDecl.extend({
             this._generateFieldsInitializationCode(), 
             inheritanceCode);
         this.currentScope().addFinalizer(function(){
-            if (!this.__type.customConstructor())
+            if (!this.__type.customConstructor)
                 gen.insert(pos, this._generateConstructor() + inheritanceCode);
         }.bind(this));
     }
@@ -749,7 +520,7 @@ var ProcOrMethodDecl = Context.ProcDecl.extend({
     _beginBody: function(){
         Context.ProcDecl.prototype._beginBody.call(this);
         if (this.__isConstructor)
-            this.codeGenerator().write(this.__boundType.fieldsInitializationCode());
+            this.codeGenerator().write(this.__boundType.fieldsInitializationCode);
     },
     _makeArgumentVariable: function(arg){
         if (!arg.isVar)
@@ -796,7 +567,7 @@ var ProcOrMethodDecl = Context.ProcDecl.extend({
 
             if (this.__isConstructor)
                 this.codeGenerator().write(
-                    this.__boundType.defineConstructor(this.__methodType));
+                    this.__boundType.defineConstructor(this.__methodType.procType()));
             else
                 this.__boundType.defineMethod(this.__methodId, this.__methodType);
         }
@@ -812,7 +583,7 @@ var ProcOrMethodDecl = Context.ProcDecl.extend({
                 + "' has no base type - SUPER cannot be used");
 
         var id = this.__methodId.id();
-        baseType.requireMethodDefinition(id, "cannot use abstract method(s) in SUPER calls");
+        EberonRecord.requireMethodDefinition(baseType, id, "cannot use abstract method(s) in SUPER calls");
         return {
             info: Type.makeProcedure(EberonTypes.makeMethodType(id, this.__methodType.procType(), superMethodCallGenerator)),
             code: this.qualifyScope(Type.recordScope(baseType))

+ 33 - 0
src/ob/Context.ob

@@ -13,4 +13,37 @@ TYPE
     END;
     PType* = POINTER TO Type;
 
+    IdentdefInfo* = RECORD
+        PROCEDURE id*(): STRING;
+        PROCEDURE exported*(): BOOLEAN;
+
+        mId: STRING;
+        mExported: BOOLEAN;
+    END;
+
+    PIdentdefInfo* = POINTER TO IdentdefInfo;
+
+PROCEDURE IdentdefInfo.id(): STRING;
+    RETURN SELF.mId;
+END;
+
+PROCEDURE IdentdefInfo.exported(): BOOLEAN;
+    RETURN SELF.mExported;
+END;
+
+PROCEDURE initIdentdefInfo*(id: STRING; exported: BOOLEAN; VAR result: IdentdefInfo);
+BEGIN
+    result.mId := id;
+    result.mExported := exported;
+END;
+
+PROCEDURE makeIdentdefInfo*(id: STRING; exported: BOOLEAN): PIdentdefInfo;
+VAR
+    result: PIdentdefInfo;
+BEGIN
+    NEW(result);
+    initIdentdefInfo(id, exported, result^);
+    RETURN result;
+END;
+
 END Context.

+ 9 - 0
src/ob/JsMap.ob

@@ -2,6 +2,7 @@ MODULE JsMap;
 IMPORT JS, Object;
 TYPE
     Type* = POINTER TO RECORD END;
+    Keys = ARRAY * OF STRING;
     ForEachProc = PROCEDURE(key: STRING; value: Object.PType; VAR closure: Object.Type);
     
     Strings* = POINTER TO RECORD END;
@@ -41,6 +42,14 @@ BEGIN
     JS.do("delete m[s]");
 END erase;
 
+PROCEDURE keys*(m: Type): Keys;
+VAR
+    result: ARRAY * OF STRING;
+BEGIN
+    JS.do("result = JS.Object.keys(m);");
+    RETURN result;
+END;
+
 PROCEDURE forEach*(m: Type; p: ForEachProc; VAR closure: Object.Type);
 BEGIN
     JS.do("for(var key in m){p(key, m[key], closure)}");

+ 2 - 2
src/ob/Module.ob

@@ -35,7 +35,7 @@ PROCEDURE AnyType.description(): STRING;
     RETURN "JS.var"
 END AnyType.description;
 
-PROCEDURE AnyType.initializer(cx: Context.Type; forNew: BOOLEAN): STRING;
+PROCEDURE AnyType.initializer(cx: Context.Type; forNew: BOOLEAN; code: STRING): STRING;
     RETURN "undefined"
 END AnyType.initializer;
 
@@ -71,7 +71,7 @@ PROCEDURE AnyField.type(): Types.PType;
     RETURN any
 END AnyField.type;
 
-PROCEDURE AnyField.asVar(): Types.PId;
+PROCEDURE AnyField.asVar(isReadOnly: BOOLEAN; cx: Context.Type): Types.PId;
     RETURN any.asVar
 END AnyField.asVar;
 

+ 1 - 1
src/ob/Procedure.ob

@@ -356,7 +356,7 @@ PROCEDURE makeNew(): Symbols.PSymbol;
             Errors.raise("non-exported RECORD type cannot be used in NEW");
         END;
         RETURN Code.makeSimpleExpression(
-                arg.code() + " = " + baseType.initializer(cx^, TRUE),
+                arg.code() + " = " + baseType.initializer(cx^, TRUE, ""),
                 NIL)
     END CallImpl.make;
 BEGIN

+ 8 - 0
src/ob/String.ob

@@ -41,4 +41,12 @@ BEGIN
     RETURN result
 END substr;
 
+PROCEDURE join*(a: ARRAY OF STRING; separator: STRING): STRING;
+VAR 
+    result: STRING;
+BEGIN
+    JS.do("result = a.join(separator)")
+    RETURN result
+END;
+
 END String.

+ 56 - 13
src/ob/Types.ob

@@ -15,7 +15,7 @@ TYPE
     PType* = POINTER TO Type;
 
     StorageType* = RECORD(Type)    
-        PROCEDURE initializer*(cx: Context.Type; forNew: BOOLEAN): STRING
+        PROCEDURE initializer*(cx: Context.Type; forNew: BOOLEAN; code: STRING): STRING
     END;
 
     TypeId* = RECORD(Id)
@@ -85,10 +85,18 @@ TYPE
         PROCEDURE id*(): STRING;
         PROCEDURE exported*(): BOOLEAN;
         PROCEDURE type*(): PType;
-        PROCEDURE asVar*(): PId 
+        PROCEDURE asVar*(isReadOnly: BOOLEAN; cx: Context.Type): PId;
     END;
     PField* = POINTER TO Field;
 
+    RecordField* = RECORD(Field)
+        PROCEDURE identdef*(): Context.PIdentdefInfo;
+
+        mIdentdef: Context.PIdentdefInfo;
+        mType: PType;
+    END;
+    PRecordField* = POINTER TO RecordField;
+
     NamedType* = RECORD(StorageType)
         PROCEDURE denote*(id: STRING): PField;
 
@@ -148,12 +156,12 @@ TYPE
     PBasicType* = POINTER TO BasicType;
 
     Record* = RECORD(NamedType)
-        PROCEDURE addField(f: PField);
-        PROCEDURE findSymbol(id: STRING): PField;
-        PROCEDURE finalize();
+        PROCEDURE addField*(f: PField);
+        PROCEDURE findSymbol*(id: STRING): PField;
+        PROCEDURE finalize*();
 
         fields: JsMap.Type;
-        base:   PRecord;
+        base-:   PRecord;
         cons:   STRING;
         scope:  ScopeBase.PType;
         notExported: ARRAY * OF STRING
@@ -351,7 +359,7 @@ PROCEDURE BasicType.description(): STRING;
     RETURN SELF.name
 END BasicType.description;
 
-PROCEDURE BasicType.initializer(cx: Context.Type; forNew: BOOLEAN): STRING;
+PROCEDURE BasicType.initializer(cx: Context.Type; forNew: BOOLEAN; code: STRING): STRING;
     RETURN SELF.mInitializer
 END BasicType.initializer;
 
@@ -398,8 +406,8 @@ BEGIN
     RETURN result
 END Record.description;
 
-PROCEDURE Record.initializer(cx: Context.Type; forNew: BOOLEAN): STRING;
-    RETURN "new " + cx.qualifyScope(SELF.scope) + SELF.cons + "()"
+PROCEDURE Record.initializer(cx: Context.Type; forNew: BOOLEAN; code: STRING): STRING;
+    RETURN "new " + cx.qualifyScope(SELF.scope) + SELF.cons + "(" + code + ")"
 END Record.initializer;
 
 PROCEDURE Record.addField(f: PField);
@@ -483,7 +491,7 @@ BEGIN
     RETURN result
 END Pointer.description;
 
-PROCEDURE Pointer.initializer(cx: Context.Type; forNew: BOOLEAN): STRING;
+PROCEDURE Pointer.initializer(cx: Context.Type; forNew: BOOLEAN; code: STRING): STRING;
     RETURN "null"
 END Pointer.initializer;
 
@@ -550,11 +558,11 @@ BEGIN
     RETURN NIL
 END NamedType.denote;
 
-PROCEDURE OpenArray.initializer(cx: Context.Type; forNew: BOOLEAN): STRING;
+PROCEDURE OpenArray.initializer(cx: Context.Type; forNew: BOOLEAN; code: STRING): STRING;
     RETURN ""
 END OpenArray.initializer;
 
-PROCEDURE StaticArray.initializer(cx: Context.Type; forNew: BOOLEAN): STRING;
+PROCEDURE StaticArray.initializer(cx: Context.Type; forNew: BOOLEAN; code: STRING): STRING;
     RETURN SELF.mInitializer
 END StaticArray.initializer;
 
@@ -581,7 +589,7 @@ PROCEDURE StaticArray.length(): INTEGER;
     RETURN SELF.len
 END StaticArray.length;
 
-PROCEDURE Procedure.initializer(cx: Context.Type; forNew: BOOLEAN): STRING;
+PROCEDURE Procedure.initializer(cx: Context.Type; forNew: BOOLEAN; code: STRING): STRING;
     RETURN "null"
 END Procedure.initializer;
 
@@ -778,6 +786,41 @@ BEGIN
     m.name := name;
 END initModule;
 
+PROCEDURE RecordField.id(): STRING;
+    RETURN SELF.mIdentdef.id();
+END;
+
+PROCEDURE RecordField.exported(): BOOLEAN;
+    RETURN SELF.mIdentdef.exported();
+END;
+
+PROCEDURE RecordField.identdef(): Context.PIdentdefInfo;
+    RETURN SELF.mIdentdef;
+END;
+
+PROCEDURE RecordField.type(): PType;
+    RETURN SELF.mType;
+END;
+
+PROCEDURE RecordField.asVar(isReadOnly: BOOLEAN; cx: Context.Type): PId;
+    RETURN makeVariable(SELF.mType, isReadOnly);
+END;
+
+PROCEDURE initRecordField*(identdef: Context.PIdentdefInfo; type: PType; VAR result: RecordField);
+BEGIN
+    result.mIdentdef := identdef;
+    result.mType := type;
+END;
+
+PROCEDURE makeRecordField*(identdef: Context.PIdentdefInfo; type: PType): PRecordField;
+VAR
+    result: PRecordField;
+BEGIN
+    NEW(result);
+    initRecordField(identdef, type, result^);
+    RETURN result;
+END;
+
 BEGIN
     basic.bool := makeBasic("BOOLEAN", "false");
     basic.ch := makeBasic("CHAR", "0");

+ 3 - 0
test/expected/eberon/constructor.js

@@ -23,7 +23,10 @@ RTL$.extend(RecordWithFieldDerived, T);
 
 function passAsArgument(o/*T*/){
 }
+function RecordWithParamConstructor(a/*INTEGER*/){
+}
 passAsArgument(new T());
 var r = new T();
 var i = new RecordWithField().i;
+var rParam = new RecordWithParamConstructor(123);
 }();

+ 8 - 0
test/input/eberon/constructor.ob

@@ -13,6 +13,9 @@ TYPE
     RecordWithFieldDerived = RECORD(T)
     END;
 
+    RecordWithParamConstructor = RECORD
+    END;
+
 PROCEDURE T();
 END;
 
@@ -28,8 +31,13 @@ END;
 PROCEDURE passAsArgument(o: T);
 END;
 
+PROCEDURE RecordWithParamConstructor(a: INTEGER);
+END;
+
 BEGIN
     passAsArgument(T());
     r <- T();
     i <- RecordWithField().i;
+
+    rParam <- RecordWithParamConstructor(123);
 END m.

+ 12 - 10
test/test_unit.js

@@ -1434,13 +1434,15 @@ return {
     };
 }
 
-var result = Test.run({
-    "common": {
-        "oberon": makeSuiteForGrammar(oberon),
-        "eberon": makeSuiteForGrammar(eberon)
-    },
-    "eberon": TestUnitEberon.suite,
-    "oberon": TestUnitOberon.suite
-});
-if (typeof process != "undefined")
-    process.exit(result ? 0 : -1);
+function run(){
+    return Test.run({
+        "common": {
+            "oberon": makeSuiteForGrammar(oberon),
+            "eberon": makeSuiteForGrammar(eberon)
+        },
+        "eberon": TestUnitEberon.suite,
+        "oberon": TestUnitOberon.suite
+    });
+}
+
+exports.run = run;

+ 11 - 4
test/test_unit_eberon.js

@@ -1080,8 +1080,9 @@ exports.suite = {
         pass("TYPE T = RECORD END; PROCEDURE T(); END;",
              "TYPE T = RECORD i: INTEGER; END; PROCEDURE T(); BEGIN SELF.i := 0; END;",
              "TYPE T = RECORD END; PROCEDURE T(); END T;",
-             "TYPE T = RECORD END; PROCEDURE p(); PROCEDURE T(); END; BEGIN T(); END;" /* local procedure name may match type name from outer scope*/
-             ),
+             "TYPE T = RECORD END; PROCEDURE p(); PROCEDURE T(); END; BEGIN T(); END;", /* local procedure name may match type name from outer scope*/
+             "TYPE T = RECORD END; PROCEDURE T(a: INTEGER); END T;"
+        ),
         fail(["TYPE T = RECORD END; PROCEDURE T(); END; PROCEDURE T(); END;", "constructor 'T' already defined"],
              ["TYPE T = RECORD END; PROCEDURE T(): INTEGER; RETURN 0; END;", "constructor 'T' cannot have result type specified"],
              ["TYPE T = ARRAY 3 OF INTEGER; PROCEDURE T(); END;", "'T' already declared"],
@@ -1094,15 +1095,21 @@ exports.suite = {
     "as expression": testWithContext(
         context(grammar.expression,
                 "TYPE T = RECORD i: INTEGER; END; PT = POINTER TO T;"
+                + "ConsWithArguments = RECORD END;"
+                + "PROCEDURE ConsWithArguments(a: INTEGER); END;"
                 + "PROCEDURE byVar(VAR a: T): INTEGER; RETURN 0; END;"
                 + "PROCEDURE byNonVar(a: T): INTEGER; RETURN 0; END;"
                 ),
         pass("T()",
              "byNonVar(T())",
-             "T().i"
+             "T().i",
+             "ConsWithArguments(123)"
              ),
         fail(["PT()", "PROCEDURE expected, got 'type PT'"],
-             ["byVar(T())", "expression cannot be used as VAR parameter"]
+             ["byVar(T())", "expression cannot be used as VAR parameter"],
+             ["T(0)", "0 argument(s) expected, got 1"],
+             ["ConsWithArguments()", "1 argument(s) expected, got 0"],
+             ["ConsWithArguments(FALSE)", "type mismatch for argument 1: 'BOOLEAN' cannot be converted to 'INTEGER'"]
             )
         ),
     "initialize in place variable": testWithContext(

+ 4 - 0
test/test_unit_run.js

@@ -0,0 +1,4 @@
+var test = require("test_unit.js");
+var result = test.run();
+if (typeof process != "undefined")
+    process.exit(result ? 0 : -1);