浏览代码

Initialize records fields with custom constructors.

Vladislav Folts 10 年之前
父节点
当前提交
652ea01a84
共有 7 个文件被更改,包括 121 次插入10 次删除
  1. 二进制
      bin/compiled.zip
  2. 17 0
      src/eberon/EberonRecord.ob
  3. 55 7
      src/eberon/eberon_context.js
  4. 6 2
      src/eberon/eberon_grammar.js
  5. 1 1
      src/grammar.js
  6. 13 0
      src/ob/JsMap.ob
  7. 29 0
      test/test_unit_eberon.js

二进制
bin/compiled.zip


+ 17 - 0
src/eberon/EberonRecord.ob

@@ -8,9 +8,11 @@ TYPE
         PROCEDURE defineMethod(methodId: Context.PIdentdefInfo; type: EberonTypes.PMethodType);
         PROCEDURE defineMethod(methodId: Context.PIdentdefInfo; type: EberonTypes.PMethodType);
         PROCEDURE requireNewOnly();
         PROCEDURE requireNewOnly();
         PROCEDURE setBaseConstructorCallCode(code: STRING);
         PROCEDURE setBaseConstructorCallCode(code: STRING);
+        PROCEDURE setFieldInitializationCode(field: STRING; code: STRING);
         PROCEDURE setRecordInitializationCode(baseConstructorCallCode, fieldsInitializationCode, inheritanceCode: STRING);
         PROCEDURE setRecordInitializationCode(baseConstructorCallCode, fieldsInitializationCode, inheritanceCode: STRING);
 
 
         customConstructor-: Types.PDefinedProcedure;
         customConstructor-: Types.PDefinedProcedure;
+        customInitedfields-: ARRAY * OF STRING;
         finalized: BOOLEAN;
         finalized: BOOLEAN;
         declaredMethods: JsMap.Type;
         declaredMethods: JsMap.Type;
         definedMethods: ARRAY * OF STRING;
         definedMethods: ARRAY * OF STRING;
@@ -21,6 +23,7 @@ TYPE
         lazyDefinitions: JsMap.Type;
         lazyDefinitions: JsMap.Type;
         nonExportedMethods: ARRAY * OF STRING;
         nonExportedMethods: ARRAY * OF STRING;
         inheritanceCode, baseConstructorCallCode-, fieldsInitializationCode-: STRING;
         inheritanceCode, baseConstructorCallCode-, fieldsInitializationCode-: STRING;
+        fieldsInit: JsMap.Strings;
     END;
     END;
     PRecord* = POINTER TO Record;
     PRecord* = POINTER TO Record;
 
 
@@ -237,6 +240,14 @@ BEGIN
         Errors.raise(
         Errors.raise(
             "cannot declare field, record already has method '" + id +"'");
             "cannot declare field, record already has method '" + id +"'");
     END;
     END;
+
+    type <- f.type();
+    IF (type IS PRecord) 
+        & (type.customConstructor # NIL) 
+        & (LEN(type.customConstructor.args()) # 0) THEN
+        SELF.customInitedfields.add(id);
+    END;
+
     SUPER(f);
     SUPER(f);
 END;
 END;
 
 
@@ -303,6 +314,11 @@ BEGIN
     SELF.baseConstructorCallCode := code;
     SELF.baseConstructorCallCode := code;
 END;
 END;
 
 
+PROCEDURE Record.setFieldInitializationCode(field: STRING; code: STRING);
+BEGIN
+    JsMap.putString(SELF.fieldsInit, field, code);
+END;
+
 PROCEDURE Record.setRecordInitializationCode(baseConstructorCallCode, fieldsInitializationCode, inheritanceCode: STRING);
 PROCEDURE Record.setRecordInitializationCode(baseConstructorCallCode, fieldsInitializationCode, inheritanceCode: STRING);
 BEGIN
 BEGIN
     SELF.baseConstructorCallCode := baseConstructorCallCode;
     SELF.baseConstructorCallCode := baseConstructorCallCode;
@@ -382,6 +398,7 @@ BEGIN
     Types.initRecord(result, name, cons, scope);
     Types.initRecord(result, name, cons, scope);
     result.declaredMethods := JsMap.make();
     result.declaredMethods := JsMap.make();
     result.lazyDefinitions := JsMap.make();
     result.lazyDefinitions := JsMap.make();
+    result.fieldsInit := JsMap.makeStrings();
     RETURN result;
     RETURN result;
 END;
 END;
 
 

+ 55 - 7
src/eberon/eberon_context.js

@@ -472,32 +472,49 @@ function handleTypePromotionMadeInSeparateStatement(msg){
 }
 }
 
 
 function getConstructorSuperMsg(){}
 function getConstructorSuperMsg(){}
+function getConstructorBoundType(){}
+
+function InitFieldMsg(id){
+    this.id = id;
+}
 
 
 var BaseInit = Context.Chained.extend({
 var BaseInit = Context.Chained.extend({
     init: function EberonContext$BaseInit(parent){
     init: function EberonContext$BaseInit(parent){
         Context.Chained.prototype.init.call(this, parent);
         Context.Chained.prototype.init.call(this, parent);
         this.__type = undefined;
         this.__type = undefined;
-        this.__baseConstructorCall = undefined;
+        this.__initCall = undefined;
+        this.__initField = undefined;
+    },
+    type: function(){
+        if (!this.__type)
+            this.__type = this.handleMessage(getConstructorBoundType);
+        return this.__type;
     },
     },
     codeGenerator: function(){return CodeGenerator.nullGenerator();},
     codeGenerator: function(){return CodeGenerator.nullGenerator();},
     handleMessage: function(msg){
     handleMessage: function(msg){
         if (msg == Context.beginCallMsg)
         if (msg == Context.beginCallMsg)
             return;
             return;
         if (msg == Context.endCallMsg){
         if (msg == Context.endCallMsg){
-            var e = this.__baseConstructorCall.end();
-            this.__type.setBaseConstructorCallCode(e.code());
+            var e = this.__initCall.end();
+            if (this.__initField)
+                this.type().setFieldInitializationCode(this.__initField, e.code());
+            else
+                this.type().setBaseConstructorCallCode(e.code());
             return;
             return;
         }
         }
         return Context.Chained.prototype.handleMessage.call(this, msg);
         return Context.Chained.prototype.handleMessage.call(this, msg);
     },
     },
+    handleIdent: function(id){
+        this.__initField = id;
+        this.__initCall = this.handleMessage(new InitFieldMsg(id));
+    },
     handleExpression: function(e){
     handleExpression: function(e){
-        this.__baseConstructorCall.handleArgument(e);
+        this.__initCall.handleArgument(e);
     },
     },
     handleLiteral: function(s){
     handleLiteral: function(s){
         if (s == "SUPER"){
         if (s == "SUPER"){
             var ms = this.handleMessage(getConstructorSuperMsg);
             var ms = this.handleMessage(getConstructorSuperMsg);
-            this.__type = ms.type;
-            this.__baseConstructorCall = makeConstructorCall(this, Type.recordBase(this.__type), EberonConstructor.makeBaseConstructorCall);
+            this.__initCall = makeConstructorCall(this, Type.recordBase(this.type()), EberonConstructor.makeBaseConstructorCall);
         }
         }
     }
     }
 });
 });
@@ -511,6 +528,7 @@ var ProcOrMethodDecl = Context.ProcDecl.extend({
         this.__endingId = undefined;
         this.__endingId = undefined;
         this.__isConstructor = false;
         this.__isConstructor = false;
         this.__baseConstructorWasCalled = false;
         this.__baseConstructorWasCalled = false;
+        this.__initedFields = [];
     },
     },
     handleMessage: function(msg){
     handleMessage: function(msg){
         if (msg == getMethodSelf){
         if (msg == getMethodSelf){
@@ -523,6 +541,9 @@ var ProcOrMethodDecl = Context.ProcDecl.extend({
             return this.__boundType;
             return this.__boundType;
         }
         }
 
 
+        if (msg == getConstructorBoundType)
+            return this.__boundType;
+
         if (msg == getConstructorSuperMsg){
         if (msg == getConstructorSuperMsg){
             this.__baseConstructorWasCalled = true;
             this.__baseConstructorWasCalled = true;
             return this.__handleSuperCall();
             return this.__handleSuperCall();
@@ -534,6 +555,9 @@ var ProcOrMethodDecl = Context.ProcDecl.extend({
             return this.__handleSuperCall();
             return this.__handleSuperCall();
         }
         }
 
 
+        if (msg instanceof InitFieldMsg)
+            return this.__handleFieldInit(msg.id);
+
         if (msg instanceof MethodOrProcMsg){
         if (msg instanceof MethodOrProcMsg){
             var id = msg.id;
             var id = msg.id;
             var type = msg.type;
             var type = msg.type;
@@ -614,6 +638,16 @@ var ProcOrMethodDecl = Context.ProcDecl.extend({
                     throw new Errors.Error("base record constructor has parameters but was not called (use '| SUPER' to pass parameters to base constructor)");
                     throw new Errors.Error("base record constructor has parameters but was not called (use '| SUPER' to pass parameters to base constructor)");
                 if (this.__baseConstructorWasCalled && (!base.customConstructor || !base.customConstructor.args().length))
                 if (this.__baseConstructorWasCalled && (!base.customConstructor || !base.customConstructor.args().length))
                     throw new Errors.Error("base record constructor has no parameters and will be called automatically (do not use '| SUPER' to call base constructor)");
                     throw new Errors.Error("base record constructor has no parameters and will be called automatically (do not use '| SUPER' to call base constructor)");
+                
+                var fieldsWereNotInited = [];
+                for(var i = 0; i < this.__boundType.customInitedfields.length; ++i){
+                    var f = this.__boundType.customInitedfields[i];
+                    if (this.__initedFields.indexOf(f) == -1)
+                        fieldsWereNotInited.push(f);
+                }
+                if (fieldsWereNotInited.length)
+                    throw new Errors.Error("constructor '" + Type.typeName(this.__boundType) + "' must initialize fields: " + fieldsWereNotInited);
+
                 this.codeGenerator().write(
                 this.codeGenerator().write(
                     this.__boundType.defineConstructor(this.__methodType.procType()));
                     this.__boundType.defineConstructor(this.__methodType.procType()));
             }
             }
@@ -636,12 +670,26 @@ var ProcOrMethodDecl = Context.ProcDecl.extend({
             EberonRecord.requireMethodDefinition(baseType, id, "cannot use abstract method(s) in SUPER calls");
             EberonRecord.requireMethodDefinition(baseType, id, "cannot use abstract method(s) in SUPER calls");
         
         
         return {
         return {
-            type: this.__boundType,
             info: this.__isConstructor ? undefined
             info: this.__isConstructor ? undefined
                                        : Type.makeProcedure(EberonTypes.makeMethodType(id, this.__methodType.procType(), superMethodCallGenerator)),
                                        : Type.makeProcedure(EberonTypes.makeMethodType(id, this.__methodType.procType(), superMethodCallGenerator)),
             code: this.qualifyScope(Type.recordScope(baseType))
             code: this.qualifyScope(Type.recordScope(baseType))
                 + Type.typeName(baseType) + ".prototype." + id + ".call"
                 + Type.typeName(baseType) + ".prototype." + id + ".call"
         };
         };
+    },
+    __handleFieldInit: function(id){
+        var fields = Type.recordOwnFields(this.__boundType);
+        if (!fields.hasOwnProperty(id))
+            throw new Errors.Error("'" + id + "' is not record '" + Type.typeName(this.__boundType) + "' own field");
+        
+        if (this.__initedFields.indexOf(id) != -1)
+            throw new Errors.Error("field '" + id + "' is already initialized");
+
+        this.__initedFields.push(id);        
+        var type = fields[id].type();
+        if (!(type instanceof Type.Record))
+            throw new Errors.Error("cannot initialize field '" + id + "', only fields of record types are supported");
+
+        return makeConstructorCall(this, type, EberonConstructor.makeConstructorCall);
     }
     }
 });
 });
 
 

+ 6 - 2
src/eberon/eberon_grammar.js

@@ -91,9 +91,13 @@ function makeFormalArray(){
     return and("ARRAY", optional("*"), "OF");
     return and("ARRAY", optional("*"), "OF");
 }
 }
 
 
-function makeFormalResult(base, actualParameters){
+function makeFormalResult(base, ident, actualParameters){
+    var initField = and(ident, actualParameters);
+    var followingFields = repeat(and(",", initField));
     return or(base, 
     return or(base, 
-              context(and("|", "SUPER", actualParameters), EbContext.BaseInit));
+              context(and("|", or(and("SUPER", actualParameters, followingFields),
+                                  and(initField, followingFields))), 
+                      EbContext.BaseInit));
 }
 }
 
 
 function makeReturn(base){
 function makeReturn(base){

+ 1 - 1
src/grammar.js

@@ -178,7 +178,7 @@ var formalParameters = and(
           "("
           "("
         , optional(context(and(fpSection, repeat(and(";", fpSection))), Context.ProcParams))
         , optional(context(and(fpSection, repeat(and(";", fpSection))), Context.ProcParams))
         , required( ")" )
         , required( ")" )
-        , optional(makeFormalResult(and(":", qualident), actualParameters)));
+        , optional(makeFormalResult(and(":", qualident), ident, actualParameters)));
 
 
 var procedureType = and("PROCEDURE"
 var procedureType = and("PROCEDURE"
                       , context(optional(formalParameters), contexts.FormalParameters)
                       , context(optional(formalParameters), contexts.FormalParameters)

+ 13 - 0
src/ob/JsMap.ob

@@ -16,6 +16,14 @@ BEGIN
     RETURN result    
     RETURN result    
 END make;
 END make;
 
 
+PROCEDURE makeStrings*(): Strings;
+VAR
+    result: Strings;
+BEGIN
+    JS.do("result = {}");
+    RETURN result    
+END;
+
 PROCEDURE has*(m: Type; s: STRING): BOOLEAN;
 PROCEDURE has*(m: Type; s: STRING): BOOLEAN;
 VAR
 VAR
     result: BOOLEAN;
     result: BOOLEAN;
@@ -37,6 +45,11 @@ BEGIN
     JS.do("m[s] = o");
     JS.do("m[s] = o");
 END put;
 END put;
 
 
+PROCEDURE putString*(m: Strings; s: STRING; v: STRING);
+BEGIN
+    JS.do("m[s] = v");
+END;
+
 PROCEDURE erase*(m: Type; s: STRING);
 PROCEDURE erase*(m: Type; s: STRING);
 BEGIN
 BEGIN
     JS.do("delete m[s]");
     JS.do("delete m[s]");

+ 29 - 0
test/test_unit_eberon.js

@@ -1138,6 +1138,35 @@ exports.suite = {
              ["PROCEDURE DerivedWthoutConstructor() | SUPER(); END;", "base record constructor has no parameters and will be called automatically (do not use '| SUPER' to call base constructor)"],
              ["PROCEDURE DerivedWthoutConstructor() | SUPER(); END;", "base record constructor has no parameters and will be called automatically (do not use '| SUPER' to call base constructor)"],
              ["PROCEDURE DerivedWthConstructorNoParameters() | SUPER(); END;", "base record constructor has no parameters and will be called automatically (do not use '| SUPER' to call base constructor)"]
              ["PROCEDURE DerivedWthConstructorNoParameters() | SUPER(); END;", "base record constructor has no parameters and will be called automatically (do not use '| SUPER' to call base constructor)"]
             )
             )
+        ),
+    "initialize fields": testWithModule(
+            "MODULE m;"
+          + "TYPE Field* = RECORD END;"
+          + "PROCEDURE Field(a: INTEGER); END;"
+          + "END m.",
+        pass("MODULE m2; IMPORT m; TYPE T = RECORD f: m.Field; END; PROCEDURE T() | f(123); END; END m2."),
+        fail(["MODULE m2; IMPORT m; TYPE T = RECORD f: m.Field; END; PROCEDURE T(); END; END m2.", 
+              "constructor 'T' must initialize fields: f"],
+             ["MODULE m2; TYPE T = RECORD END; PROCEDURE T() | unknownField(123); END; END m2.", 
+              "'unknownField' is not record 'T' own field"],
+             ["MODULE m2; IMPORT m; TYPE T = RECORD f: m.Field; END; Derived = RECORD(T) END; PROCEDURE Derived() | f(123); END; END m2.", 
+              "'f' is not record 'Derived' own field"],
+             ["MODULE m2; IMPORT m; TYPE T = RECORD f: m.Field; END; PROCEDURE T() | f(123), f(123); END; END m2.", 
+              "field 'f' is already initialized"],
+             ["MODULE m2; TYPE T = RECORD i: INTEGER; END; PROCEDURE T() | i(); END; END m2.", 
+              "cannot initialize field 'i', only fields of record types are supported"],
+             ["MODULE m2; TYPE T = RECORD i: INTEGER; END; PROCEDURE T() | i(123); END; END m2.", 
+              "cannot initialize field 'i', only fields of record types are supported"]
+             )
+        ),
+    "call base and initialize fields": testWithContext(
+        context(grammar.declarationSequence,
+                "TYPE Field = RECORD END; T = RECORD END; Derived = RECORD(T) f: Field; END;"
+              + "PROCEDURE Field(a: INTEGER); END;"
+              + "PROCEDURE T(a: INTEGER); END;"
+              ),
+        pass("PROCEDURE Derived() | SUPER(123), f(456); END;"),
+        fail(["PROCEDURE Derived() | f(456), SUPER(123); END;", "not parsed"])
         )
         )
     }
     }
 };
 };