Browse Source

support read-onle export for excord fields using '-' mark

Vladislav Folts 11 năm trước cách đây
mục cha
commit
56b16a615e

BIN
bin/compiled.zip


+ 32 - 0
doc/wiki/eberon-record-fields-read-only-export.md

@@ -0,0 +1,32 @@
+Read-only export is introduced for record fields using '-' mark - in the same way as it is done in Component Pascal.
+The major reason for this extension is to maintain data integrity without obligation to write down corresponfing procedures-accessors. I.e. instead of hiding (not exporting) record field and giving procedure-accessor (exported) for reading field value it is possble to just mark a field as exported for reading only.
+
+### Syntax
+    identdef = ident ["*" | "-"].
+
+Example:
+
+    MODULE m1;
+    TYPE 
+        T* = RECORD
+            i-: INTEGER
+        END;
+        TP* = POINTER TO T;
+
+        PROCEDURE make*(): TP;
+        VAR result: TP;
+        BEGIN
+            NEW(result);
+            result.i := 123; (* field 'T.i' can be modified in this module *)
+            RETURN result
+        END make;
+    END m1.    
+
+    MODULE m2;
+    IMPORT m1;
+    VAR p: m1.TP; 
+    BEGIN
+        p := m1.make();
+        ASSERT(p.i = 123); (* field 'T.i' can be accessed in this module *) 
+        p.i := 321; (* compiller error, 'T.i' cannot be modified in this module *)
+    END m2.        

+ 1 - 0
doc/wiki/eberon.md

@@ -5,5 +5,6 @@ Eberon extends original Oberon so any valid oberon program is also a valid ebero
 ### Extensions
 ### Extensions
 * [Methods](/vladfolts/oberonjs/wiki/eberon-methods)
 * [Methods](/vladfolts/oberonjs/wiki/eberon-methods)
 * [Strings](/vladfolts/oberonjs/wiki/eberon-strings)
 * [Strings](/vladfolts/oberonjs/wiki/eberon-strings)
+* [Record fields read-only export](/vladfolts/oberonjs/wiki/eberon-record-fields-read-only-export)
 * [Procedure call result can be denoted](/vladfolts/oberonjs/wiki/eberon-procedure-call-result)
 * [Procedure call result can be denoted](/vladfolts/oberonjs/wiki/eberon-procedure-call-result)
 * Non-scalar variables (arrays and records) can be exported (forbidden in oberon for some unknown reason).
 * Non-scalar variables (arrays and records) can be exported (forbidden in oberon for some unknown reason).

+ 34 - 16
src/context.js

@@ -246,13 +246,16 @@ var IdentdefInfo = Class.extend({
 exports.Identdef = ChainedContext.extend({
 exports.Identdef = ChainedContext.extend({
     init: function IdentdefContext(context){
     init: function IdentdefContext(context){
         ChainedContext.prototype.init.call(this, context);
         ChainedContext.prototype.init.call(this, context);
-        this.__id = undefined;
-        this.__export = false;
+        this._id = undefined;
+        this._export = false;
     },
     },
-    handleIdent: function(id){this.__id = id;},
-    handleLiteral: function(){this.__export = true;},
+    handleIdent: function(id){this._id = id;},
+    handleLiteral: function(){this._export = true;},
     endParse: function(){
     endParse: function(){
-        this.parent().handleIdentdef(new IdentdefInfo(this.__id, this.__export));
+        this.parent().handleIdentdef(this._makeIdendef());
+    },
+    _makeIdendef: function(){
+        return new IdentdefInfo(this._id, this._export);
     }
     }
 });
 });
 
 
@@ -299,16 +302,16 @@ exports.Designator = ChainedContext.extend({
             isReadOnly = false;
             isReadOnly = false;
         }
         }
         else if (!(t instanceof Type.Record
         else if (!(t instanceof Type.Record
-                || t instanceof Type.Module
                 || t instanceof Module.AnyType))
                 || t instanceof Module.AnyType))
             throw new Errors.Error("cannot designate '" + t.description() + "'");
             throw new Errors.Error("cannot designate '" + t.description() + "'");
 
 
-        this.__denote(id, pointerType);
-        this.__info = this._makeDenoteVar(this.__currentType, isReadOnly);
+        var field = this.__denote(id, pointerType);
+        this.__info = this._makeDenoteVar(field, isReadOnly);
+        this.__currentType = field.type();
         this.__scope = undefined;
         this.__scope = undefined;
     },
     },
-    _makeDenoteVar: function(type, isReadOnly){
-        return Type.makeVariable(type, isReadOnly);
+    _makeDenoteVar: function(field, isReadOnly){
+        return Type.makeVariable(field.type(), isReadOnly);
     },
     },
     handleExpression: function(e){this.__indexExpression = e;},
     handleExpression: function(e){this.__indexExpression = e;},
     __handleIndexExpression: function(){
     __handleIndexExpression: function(){
@@ -405,8 +408,8 @@ exports.Designator = ChainedContext.extend({
     },
     },
     __denote: function(id, pointerType){
     __denote: function(id, pointerType){
         var t = this.__currentType;
         var t = this.__currentType;
-        var fieldType = t.findSymbol(id);
-        if (!fieldType){
+        var field = t.findSymbol(id);
+        if (!field){
             var typeDesc = !Type.typeName(t) && pointerType && Type.typeName(pointerType)
             var typeDesc = !Type.typeName(t) && pointerType && Type.typeName(pointerType)
                 ? Type.typeName(pointerType)
                 ? Type.typeName(pointerType)
                 : t.description();
                 : t.description();
@@ -415,7 +418,7 @@ exports.Designator = ChainedContext.extend({
         this.__derefCode = this.__code;
         this.__derefCode = this.__code;
         this.__propCode = "\"" + id + "\"";
         this.__propCode = "\"" + id + "\"";
         this.__code += "." + id;
         this.__code += "." + id;
-        this.__currentType = fieldType;
+        return field;
     },
     },
     endParse: function(){
     endParse: function(){
         var code = this.__code;
         var code = this.__code;
@@ -1669,7 +1672,7 @@ function isTypeRecursive(type, base){
             return true;
             return true;
         var fields = Type.recordOwnFields(type);
         var fields = Type.recordOwnFields(type);
         for(var fieldName in fields){
         for(var fieldName in fields){
-            if (isTypeRecursive(fields[fieldName], base))
+            if (isTypeRecursive(fields[fieldName].type(), base))
                 return true;
                 return true;
         }
         }
     }
     }
@@ -1678,6 +1681,19 @@ function isTypeRecursive(type, base){
     return false;
     return false;
 }
 }
 
 
+var RecordField = Class.extend({
+    init: function Context$RecordField(identdef, type, recordType){
+        this.__identdef = identdef;
+        this.__type = type;
+        this.__refcordType = recordType;
+    },
+    id: function(){return this.__identdef.id();},
+    exported: function(){return this.__identdef.exported();},
+    identdef: function(){return this.__identdef;},
+    type: function(){return this.__type;},
+    recordType: function(){return this.__refcordType;}
+});
+
 exports.RecordDecl = ChainedContext.extend({
 exports.RecordDecl = ChainedContext.extend({
     init: function RecordDeclContext(context, makeRecord){
     init: function RecordDeclContext(context, makeRecord){
         ChainedContext.prototype.init.call(this, context);
         ChainedContext.prototype.init.call(this, context);
@@ -1693,7 +1709,7 @@ exports.RecordDecl = ChainedContext.extend({
         if (isTypeRecursive(type, this.__type))
         if (isTypeRecursive(type, this.__type))
             throw new Errors.Error("recursive field definition: '"
             throw new Errors.Error("recursive field definition: '"
                 + field.id() + "'");
                 + field.id() + "'");
-        this.__type.addField(field, type);
+        this.__type.addField(new RecordField(field, type, this.__type));
         if (field.exported())
         if (field.exported())
             this.parent().exportField(field.id());
             this.parent().exportField(field.id());
     },
     },
@@ -1723,7 +1739,7 @@ exports.RecordDecl = ChainedContext.extend({
             gen.write(qualifiedBase + ".prototype.init.call(this);\n");
             gen.write(qualifiedBase + ".prototype.init.call(this);\n");
         var ownFields = Type.recordOwnFields(type);
         var ownFields = Type.recordOwnFields(type);
         for(var f in ownFields)
         for(var f in ownFields)
-            gen.write("this." + f + " = " + ownFields[f].initializer(this) + ";\n");
+            gen.write("this." + f + " = " + ownFields[f].type().initializer(this) + ";\n");
 
 
         gen.closeScope("");
         gen.closeScope("");
         gen.closeScope(");\n");
         gen.closeScope(");\n");
@@ -1962,7 +1978,9 @@ exports.endCallMsg = endCallMsg;
 exports.Chained = ChainedContext;
 exports.Chained = ChainedContext;
 exports.endParametersMsg = endParametersMsg;
 exports.endParametersMsg = endParametersMsg;
 exports.getSymbolAndScope = getSymbolAndScope;
 exports.getSymbolAndScope = getSymbolAndScope;
+exports.IdentdefInfo = IdentdefInfo;
 exports.makeProcCall = makeProcCall;
 exports.makeProcCall = makeProcCall;
 exports.unwrapType = unwrapType;
 exports.unwrapType = unwrapType;
 exports.IdentdefInfo = IdentdefInfo;
 exports.IdentdefInfo = IdentdefInfo;
+exports.RecordField = RecordField;
 exports.RelationOps = RelationOps;
 exports.RelationOps = RelationOps;

+ 75 - 10
src/eberon/eberon_context.js

@@ -65,6 +65,7 @@ var ProcOrMethodId = Context.Chained.extend({
     handleIdentdef: function(id){
     handleIdentdef: function(id){
         if (this.__type && id.exported())
         if (this.__type && id.exported())
             throw new Errors.Error("method implementation cannot be exported: " + id.id());
             throw new Errors.Error("method implementation cannot be exported: " + id.id());
+        checkOrdinaryExport(id, "procedure");
         this.handleMessage(new MethodOrProcMsg(id, this.__type));
         this.handleMessage(new MethodOrProcMsg(id, this.__type));
     }
     }
 });
 });
@@ -105,6 +106,29 @@ var ResultVariable = Type.Variable.extend({
     idType: function(){return "procedure call " + (this.type() ? "result" : "statement");}
     idType: function(){return "procedure call " + (this.type() ? "result" : "statement");}
 });
 });
 
 
+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);
+        this.__ro = false;
+    },
+    handleLiteral: function(l){
+        if (l == "-")
+            this.__ro = true;  
+        Context.Identdef.prototype.handleLiteral.call(this, l);
+    },
+    _makeIdendef: function(){
+        return new IdentdefInfo(this._id, this._export, this.__ro);
+    }
+});
+
 var Designator = Context.Designator.extend({
 var Designator = Context.Designator.extend({
     init: function EberonContext$Designator(parent){
     init: function EberonContext$Designator(parent){
         Context.Designator.prototype.init.call(this, parent);
         Context.Designator.prototype.init.call(this, parent);
@@ -120,10 +144,13 @@ var Designator = Context.Designator.extend({
         }
         }
         return Context.Designator.prototype._indexSequence.call(this, type, info);
         return Context.Designator.prototype._indexSequence.call(this, type, info);
     },
     },
-    _makeDenoteVar: function(type, isReadOnly){
-        return (type instanceof MethodType)
-            ? new MethodVariable(type)
-            : Context.Designator.prototype._makeDenoteVar(type, isReadOnly);
+    _makeDenoteVar: function(field, isReadOnly){
+        var type = field.type();
+        if (type instanceof MethodType)
+            return new MethodVariable(type);
+        if (!isReadOnly && this.qualifyScope(Type.recordScope(field.recordType())))
+            isReadOnly = field.identdef().isReadOnly();
+        return Context.Designator.prototype._makeDenoteVar(field, isReadOnly);
     },
     },
     handleMessage: function(msg){
     handleMessage: function(msg){
         if (msg == Context.beginCallMsg)
         if (msg == Context.beginCallMsg)
@@ -244,12 +271,12 @@ var RecordType = Type.Record.extend({
         var existingField = this.findSymbol(id);
         var existingField = this.findSymbol(id);
         if (existingField)
         if (existingField)
             throw new Errors.Error(
             throw new Errors.Error(
-                  existingField instanceof MethodType
+                  existingField.type() instanceof MethodType
                 ?   "cannot declare a new method '" + id 
                 ?   "cannot declare a new method '" + id 
                   + "': method already was declared"
                   + "': method already was declared"
                 : "cannot declare method, record already has field '" + id + "'");
                 : "cannot declare method, record already has field '" + id + "'");
 
 
-        this.__declaredMethods[id] = type;
+        this.__declaredMethods[id] = new Context.RecordField(methodId, type);
 
 
         if (!methodId.exported())
         if (!methodId.exported())
             this.__nonExportedMethods.push(id);
             this.__nonExportedMethods.push(id);
@@ -257,14 +284,13 @@ var RecordType = Type.Record.extend({
     defineMethod: function(methodId, type){
     defineMethod: function(methodId, type){
         var base = Type.recordBase(this);
         var base = Type.recordBase(this);
         var id = methodId.id();
         var id = methodId.id();
-        var existing = this.findSymbol(id);
-        if (!(existing instanceof MethodType)){
+        var existingField = this.findSymbol(id);
+        if (!existingField || !(existingField.type() instanceof MethodType)){
             throw new Errors.Error(
             throw new Errors.Error(
                   "'" + Type.typeName(this) + "' has no declaration for method '" + id 
                   "'" + Type.typeName(this) + "' has no declaration for method '" + id 
                 + "'");
                 + "'");
         }
         }
-        //if (this.__definedMethods.indexOf(id) != -1)
-        //    throw new Error.Error("method definition duplicate");
+        var existing = existingField.type();
         if (!Cast.areProceduresMatch(existing, type))
         if (!Cast.areProceduresMatch(existing, type))
             throw new Errors.Error(
             throw new Errors.Error(
                   "overridden method '" + id + "' signature mismatch: should be '"
                   "overridden method '" + id + "' signature mismatch: should be '"
@@ -367,6 +393,41 @@ var RecordType = Type.Record.extend({
     }
     }
 });
 });
 
 
+function checkOrdinaryExport(id, hint){
+    if (id.isReadOnly())
+        throw new Errors.Error(hint + " cannot be exported as read-only using '-' mark (did you mean '*'?)");
+}
+
+var ConstDecl = Context.ConstDecl.extend({
+    init: function EberonContext$ConstDecl(context){
+        Context.ConstDecl.prototype.init.call(this, context);
+    },
+    handleIdentdef: function(id){
+        checkOrdinaryExport(id, "constant");
+        Context.ConstDecl.prototype.handleIdentdef.call(this, id);
+    }
+});
+
+var VariableDeclaration = Context.VariableDeclaration.extend({
+    init: function EberonContext$VariableDeclaration(context){
+        Context.VariableDeclaration.prototype.init.call(this, context);
+    },
+    handleIdentdef: function(id){
+        checkOrdinaryExport(id, "variable");
+        Context.VariableDeclaration.prototype.handleIdentdef.call(this, id);
+    }
+});
+
+var TypeDeclaration = Context.TypeDeclaration.extend({
+    init: function EberonContext$TypeDeclaration(context){
+        Context.TypeDeclaration.prototype.init.call(this, context);
+    },
+    handleIdentdef: function(id){
+        checkOrdinaryExport(id, "type");
+        Context.TypeDeclaration.prototype.handleIdentdef.call(this, id);
+    }
+});
+
 var RecordDecl = Context.RecordDecl.extend({
 var RecordDecl = Context.RecordDecl.extend({
     init: function EberonContext$RecordDecl(context){
     init: function EberonContext$RecordDecl(context){
         var makeRecord = function(name, cons, scope){return new RecordType(name, cons, scope);};
         var makeRecord = function(name, cons, scope){return new RecordType(name, cons, scope);};
@@ -532,11 +593,15 @@ var Expression = Context.Expression.extend({
 });
 });
 
 
 exports.AddOperator = AddOperator;
 exports.AddOperator = AddOperator;
+exports.ConstDecl = ConstDecl;
 exports.Designator = Designator;
 exports.Designator = Designator;
 exports.Expression = Expression;
 exports.Expression = Expression;
 exports.ExpressionProcedureCall = ExpressionProcedureCall;
 exports.ExpressionProcedureCall = ExpressionProcedureCall;
+exports.Identdef = Identdef;
 exports.MethodHeading = MethodHeading;
 exports.MethodHeading = MethodHeading;
 exports.AssignmentOrProcedureCall = AssignmentOrProcedureCall;
 exports.AssignmentOrProcedureCall = AssignmentOrProcedureCall;
 exports.ProcOrMethodId = ProcOrMethodId;
 exports.ProcOrMethodId = ProcOrMethodId;
 exports.ProcOrMethodDecl = ProcOrMethodDecl;
 exports.ProcOrMethodDecl = ProcOrMethodDecl;
 exports.RecordDecl = RecordDecl;
 exports.RecordDecl = RecordDecl;
+exports.TypeDeclaration = TypeDeclaration;
+exports.VariableDeclaration = VariableDeclaration;

+ 20 - 11
src/eberon/eberon_grammar.js

@@ -25,6 +25,10 @@ function makeAssignmentOrProcedureCall(designator, assignment){
                    EbContext.AssignmentOrProcedureCall);
                    EbContext.AssignmentOrProcedureCall);
 }
 }
 
 
+function makeIdentdef(ident){
+    return context(and(ident, optional(or("*", "-"))), EbContext.Identdef);
+}
+
 function makeDesignator(qualident, selector, actualParameters){
 function makeDesignator(qualident, selector, actualParameters){
     var designator = context(
     var designator = context(
         and(or("SELF", "SUPER", qualident), repeat(or(selector, actualParameters))), EbContext.Designator);
         and(or("SELF", "SUPER", qualident), repeat(or(selector, actualParameters))), EbContext.Designator);
@@ -59,17 +63,22 @@ function makeFieldList(identdef, identList, type, formalParameters){
 }
 }
 
 
 exports.language = {
 exports.language = {
-  grammar: Grammar.make(
-      makeDesignator,
-      makeProcedureHeading,
-      makeProcedureDeclaration,
-      makeFieldList,
-      EbContext.RecordDecl,
-      Context.VariableDeclaration,
-      EbContext.AddOperator,
-      EbContext.Expression,
-      Grammar.reservedWords + " SELF SUPER"
-      ),
+    grammar: Grammar.make(
+        makeIdentdef,
+        makeDesignator,
+        makeProcedureHeading,
+        makeProcedureDeclaration,
+        makeFieldList, 
+        { 
+            constDeclaration:   EbContext.ConstDecl, 
+            typeDeclaration:    EbContext.TypeDeclaration,
+            recordDecl:         EbContext.RecordDecl,
+            variableDeclaration: EbContext.VariableDeclaration,
+            addOperator:        EbContext.AddOperator,
+            expression:         EbContext.Expression
+        },
+        Grammar.reservedWords + " SELF SUPER"
+        ),
     stdSymbols: Symbols.makeStd(),
     stdSymbols: Symbols.makeStd(),
     types: {
     types: {
         implicitCast: Cast.implicit
         implicitCast: Cast.implicit

+ 10 - 13
src/grammar.js

@@ -22,14 +22,12 @@ var required = Parser.required;
 
 
 var reservedWords = "ARRAY IMPORT THEN BEGIN IN TO BY IS TRUE CASE MOD TYPE CONST MODULE UNTIL DIV NIL VAR DO OF WHILE ELSE OR ELSIF POINTER END PROCEDURE FALSE RECORD FOR REPEAT IF RETURN";
 var reservedWords = "ARRAY IMPORT THEN BEGIN IN TO BY IS TRUE CASE MOD TYPE CONST MODULE UNTIL DIV NIL VAR DO OF WHILE ELSE OR ELSIF POINTER END PROCEDURE FALSE RECORD FOR REPEAT IF RETURN";
 
 
-function make(makeDesignator,
+function make(makeIdentdef,
+              makeDesignator,
               makeProcedureHeading, 
               makeProcedureHeading, 
               makeProcedureDeclaration,
               makeProcedureDeclaration,
               makeFieldList,
               makeFieldList,
-              recordDeclContext,
-              varDeclContext,
-              addOperatorContext,
-              expressionContext,
+              contexts,
               reservedWords
               reservedWords
               ){
               ){
 var result = {};
 var result = {};
@@ -46,8 +44,7 @@ var ModuleDeclContext = Context.ModuleDeclaration.extend({
 
 
 var qualident = context(and(optional(and(ident, ".")), ident),
 var qualident = context(and(optional(and(ident, ".")), ident),
                         Context.QualifiedIdentificator);
                         Context.QualifiedIdentificator);
-var identdef = context(and(ident, optional("*")),
-                       Context.Identdef);
+var identdef = makeIdentdef(ident);
 
 
 var selector = or(and(point, ident)
 var selector = or(and(point, ident)
                 // break recursive declaration of expList
                 // break recursive declaration of expList
@@ -65,7 +62,7 @@ var type = or(context(qualident, Context.Type),
               function(stream, context){return strucType(stream, context);} // break recursive declaration of strucType
               function(stream, context){return strucType(stream, context);} // break recursive declaration of strucType
              );
              );
 var identList = and(identdef, repeat(and(",", identdef)));
 var identList = and(identdef, repeat(and(",", identdef)));
-var variableDeclaration = context(and(identList, ":", type), varDeclContext);
+var variableDeclaration = context(and(identList, ":", type), contexts.variableDeclaration);
 
 
 var integer = or(context(and(digit, repeat(hexDigit), "H", separator), Context.HexInteger)
 var integer = or(context(and(digit, repeat(hexDigit), "H", separator), Context.HexInteger)
                , context(and(digit, repeat(digit), separator), Context.Integer));
                , context(and(digit, repeat(digit), separator), Context.Integer));
@@ -90,7 +87,7 @@ var factor = context(
      )
      )
     , Context.Factor);
     , Context.Factor);
 
 
-var addOperator = context(or("+", "-", "OR"), addOperatorContext);
+var addOperator = context(or("+", "-", "OR"), contexts.addOperator);
 var mulOperator = context(or("*", "/", "DIV", "MOD", "&"), Context.MulOperator);
 var mulOperator = context(or("*", "/", "DIV", "MOD", "&"), Context.MulOperator);
 var term = context(and(factor, repeat(and(mulOperator, factor))), Context.Term);
 var term = context(and(factor, repeat(and(mulOperator, factor))), Context.Term);
 var simpleExpression = context(
 var simpleExpression = context(
@@ -100,7 +97,7 @@ var simpleExpression = context(
       , Context.SimpleExpression);
       , Context.SimpleExpression);
 var relation = or("=", "#", "<=", "<", ">=", ">", "IN", "IS");
 var relation = or("=", "#", "<=", "<", ">=", ">", "IN", "IS");
 var expression = context(and(simpleExpression, optional(and(relation, simpleExpression)))
 var expression = context(and(simpleExpression, optional(and(relation, simpleExpression)))
-                       , expressionContext);
+                       , contexts.expression);
 var constExpression = expression;
 var constExpression = expression;
 
 
 var element = context(and(expression, optional(and("..", expression))), Context.SetElement);
 var element = context(and(expression, optional(and("..", expression))), Context.SetElement);
@@ -165,7 +162,7 @@ var arrayType = and("ARRAY", context(and(
 
 
 var baseType = context(qualident, Context.BaseType);
 var baseType = context(qualident, Context.BaseType);
 var recordType = and("RECORD", context(and(optional(and("(", baseType, ")")), optional(fieldListSequence)
 var recordType = and("RECORD", context(and(optional(and("(", baseType, ")")), optional(fieldListSequence)
-                                     , "END"), recordDeclContext));
+                                     , "END"), contexts.recordDecl));
 
 
 var pointerType = and("POINTER", "TO", context(type, Context.PointerDecl));
 var pointerType = and("POINTER", "TO", context(type, Context.PointerDecl));
 
 
@@ -181,9 +178,9 @@ var procedureType = and("PROCEDURE"
                       , context(optional(formalParameters), Context.FormalParameters)
                       , context(optional(formalParameters), Context.FormalParameters)
                         );
                         );
 var strucType = or(arrayType, recordType, pointerType, procedureType);
 var strucType = or(arrayType, recordType, pointerType, procedureType);
-var typeDeclaration = context(and(identdef, "=", strucType), Context.TypeDeclaration);
+var typeDeclaration = context(and(identdef, "=", strucType), contexts.typeDeclaration);
 
 
-var constantDeclaration = context(and(identdef, "=", constExpression), Context.ConstDecl);
+var constantDeclaration = context(and(identdef, "=", constExpression), contexts.constDeclaration);
 
 
 var imprt = and(ident, optional(and(":=", ident)));
 var imprt = and(ident, optional(and(":=", ident)));
 var importList = and("IMPORT", imprt, repeat(and(",", imprt)));
 var importList = and("IMPORT", imprt, repeat(and(",", imprt)));

+ 26 - 3
src/ob/Module.ob

@@ -8,7 +8,11 @@ TYPE
 
 
     AnyType* = RECORD(Types.StorageType)
     AnyType* = RECORD(Types.StorageType)
         PROCEDURE callGenerator(cx: LanguageContext.PType; id: STRING): Procedure.PCallGenerator;
         PROCEDURE callGenerator(cx: LanguageContext.PType; id: STRING): Procedure.PCallGenerator;
-        PROCEDURE findSymbol(id: STRING): Types.PType
+        PROCEDURE findSymbol(id: STRING): Types.PField;
+        asField: POINTER TO AnyField
+    END;
+
+    AnyField = RECORD(Types.Field)
     END;
     END;
 
 
     AnyTypeProc* = RECORD(Types.DefinedProcedure)
     AnyTypeProc* = RECORD(Types.DefinedProcedure)
@@ -34,10 +38,26 @@ PROCEDURE AnyType.callGenerator(cx: LanguageContext.PType; id: STRING): Procedur
     RETURN Procedure.makeProcCallGenerator(cx, id, anyProc)
     RETURN Procedure.makeProcCallGenerator(cx, id, anyProc)
 END AnyType.callGenerator;
 END AnyType.callGenerator;
 
 
-PROCEDURE AnyType.findSymbol(id: STRING): Types.PType;
-    RETURN any
+PROCEDURE AnyType.findSymbol(id: STRING): Types.PField;
+    RETURN any.asField
 END AnyType.findSymbol;
 END AnyType.findSymbol;
 
 
+PROCEDURE AnyField.id(): STRING;
+    RETURN "any field"
+END AnyField.id;
+
+PROCEDURE AnyField.exported(): BOOLEAN;
+    RETURN FALSE
+END AnyField.exported;
+
+PROCEDURE AnyField.type(): Types.PType;
+    RETURN any
+END AnyField.type;
+
+PROCEDURE AnyField.recordType(): Types.PType;
+    RETURN any
+END AnyField.recordType;
+
 PROCEDURE AnyTypeProc.args(): JsArray.Type;
 PROCEDURE AnyTypeProc.args(): JsArray.Type;
     RETURN NIL
     RETURN NIL
 END AnyTypeProc.args;
 END AnyTypeProc.args;
@@ -115,7 +135,10 @@ END makeJS;
 BEGIN
 BEGIN
     doProcId := "do$";
     doProcId := "do$";
     varTypeId := "var$";
     varTypeId := "var$";
+    
     NEW(any);
     NEW(any);
+    NEW(any.asField); 
+
     doProcSymbol := makeDoProcSymbol();
     doProcSymbol := makeDoProcSymbol();
     varTypeSymbol := makeVarTypeSymbol();
     varTypeSymbol := makeVarTypeSymbol();
 END Module.
 END Module.

+ 12 - 9
src/ob/Types.ob

@@ -131,14 +131,17 @@ TYPE
 
 
     PBasicType* = POINTER TO BasicType;
     PBasicType* = POINTER TO BasicType;
 
 
-    Field = RECORD
-        id: PROCEDURE(): STRING;
-        exported: PROCEDURE(): BOOLEAN
+    Field* = RECORD(Object.Type)
+        PROCEDURE id*(): STRING;
+        PROCEDURE exported*(): BOOLEAN;
+        PROCEDURE type*(): PType;
+        PROCEDURE recordType*(): PType 
     END;
     END;
+    PField* = POINTER TO Field;
 
 
     Record* = RECORD(NamedType)
     Record* = RECORD(NamedType)
-        PROCEDURE addField(f: Field; type: PType);
-        PROCEDURE findSymbol(id: STRING): PType;
+        PROCEDURE addField(f: PField);
+        PROCEDURE findSymbol(id: STRING): PField;
         PROCEDURE finalize();
         PROCEDURE finalize();
 
 
         fields: JsMap.Type;
         fields: JsMap.Type;
@@ -388,7 +391,7 @@ PROCEDURE Record.initializer(cx: Context.Type): STRING;
     RETURN "new " + cx.qualifyScope(SELF.scope) + SELF.cons + "()"
     RETURN "new " + cx.qualifyScope(SELF.scope) + SELF.cons + "()"
 END Record.initializer;
 END Record.initializer;
 
 
-PROCEDURE Record.addField(f: Field; type: PType);
+PROCEDURE Record.addField(f: PField);
 BEGIN
 BEGIN
     IF JsMap.has(SELF.fields, f.id()) THEN
     IF JsMap.has(SELF.fields, f.id()) THEN
         Errors.raise("duplicated field: '" + f.id() + "'");
         Errors.raise("duplicated field: '" + f.id() + "'");
@@ -396,15 +399,15 @@ BEGIN
     IF (SELF.base # NIL) & (SELF.base.findSymbol(f.id()) # NIL) THEN
     IF (SELF.base # NIL) & (SELF.base.findSymbol(f.id()) # NIL) THEN
         Errors.raise("base record already has field: '" + f.id() + "'");
         Errors.raise("base record already has field: '" + f.id() + "'");
     END;
     END;
-    JsMap.put(SELF.fields, f.id(), type);
+    JsMap.put(SELF.fields, f.id(), f);
     IF ~f.exported() THEN
     IF ~f.exported() THEN
         JsArray.addString(SELF.notExported, f.id());
         JsArray.addString(SELF.notExported, f.id());
     END;
     END;
 END Record.addField;
 END Record.addField;
 
 
-PROCEDURE Record.findSymbol(id: STRING): PType;
+PROCEDURE Record.findSymbol(id: STRING): PField;
 VAR
 VAR
-    result: PType;
+    result: PField;
 BEGIN
 BEGIN
     IF ~JsMap.find(SELF.fields, id, result) & (SELF.base # NIL) THEN
     IF ~JsMap.find(SELF.fields, id, result) & (SELF.base # NIL) THEN
         result := SELF.base.findSymbol(id);
         result := SELF.base.findSymbol(id);

+ 19 - 10
src/oberon/oberon_grammar.js

@@ -27,6 +27,10 @@ function makeAssignmentOrProcedureCall(designator, actualParameters, assignment)
               );
               );
 }
 }
 
 
+function makeIdentdef(ident){
+    return context(and(ident, optional("*")), Context.Identdef);
+}
+
 function makeDesignator(qualident, selector, actualParameters){
 function makeDesignator(qualident, selector, actualParameters){
     var designator = context(and(qualident, repeat(selector)), Context.Designator);
     var designator = context(and(qualident, repeat(selector)), Context.Designator);
     return { 
     return { 
@@ -50,16 +54,21 @@ function makeFieldList(identdef, identList, type){
 
 
 exports.language = {
 exports.language = {
     grammar: Grammar.make(
     grammar: Grammar.make(
-            makeDesignator,
-            makeProcedureHeading,
-            makeProcedureDeclaration,
-            makeFieldList,
-            ObContext.RecordDecl,
-            ObContext.VariableDeclaration,
-            Context.AddOperator,
-            Context.Expression,
-            Grammar.reservedWords
-            ),
+        makeIdentdef,
+        makeDesignator,
+        makeProcedureHeading,
+        makeProcedureDeclaration,
+        makeFieldList,
+        {
+            constDeclaration:   Context.ConstDecl, 
+            typeDeclaration:    Context.TypeDeclaration,
+            recordDecl:         ObContext.RecordDecl,
+            variableDeclaration: ObContext.VariableDeclaration,
+            addOperator:        Context.AddOperator,
+            expression:         Context.Expression
+        },
+        Grammar.reservedWords
+        ),
     stdSymbols: Symbols.makeStd(),
     stdSymbols: Symbols.makeStd(),
     types: {
     types: {
         implicitCast: Cast.implicit
         implicitCast: Cast.implicit

+ 28 - 0
test/test_unit_eberon.js

@@ -191,6 +191,34 @@ exports.suite = {
          "VAR a*: A;"),
          "VAR a*: A;"),
     fail()
     fail()
     ),
     ),
+"export as read-only": testWithContext(
+    context(grammar.declarationSequence, ""),
+    pass("TYPE T* = RECORD i-: INTEGER END;"),
+    fail(["TYPE T- = RECORD END;", 
+          "type cannot be exported as read-only using '-' mark (did you mean '*'?)"],
+         ["PROCEDURE p-(); END p;", 
+          "procedure cannot be exported as read-only using '-' mark (did you mean '*'?)"],
+         ["CONST c- = 123;", 
+          "constant cannot be exported as read-only using '-' mark (did you mean '*'?)"],
+         ["VAR i-: INTEGER;", 
+          "variable cannot be exported as read-only using '-' mark (did you mean '*'?)"])
+    ),
+"field exported as read-only is writable in current module": testWithContext(
+    context(grammar.statement,
+              "TYPE T* = RECORD i-: INTEGER END;"
+            + "VAR r: T;"
+            ),
+    pass("r.i := 123"),
+    fail()
+    ),
+"import as read-only": testWithModule(
+    "MODULE test; TYPE T* = RECORD f-: INTEGER END; END test.",
+    pass(),
+    fail(["MODULE m; IMPORT test; VAR r: test.T; BEGIN r.f := 123; END m.",
+          "cannot assign to read-only variable"],
+         ["MODULE m; IMPORT test; TYPE D = RECORD(test.T) END; VAR r: D; BEGIN r.f := 123; END m.",
+          "cannot assign to read-only variable"]
+        )),
 "STRING variable": testWithGrammar(
 "STRING variable": testWithGrammar(
     grammar.variableDeclaration,
     grammar.variableDeclaration,
     pass("s: STRING")
     pass("s: STRING")