Browse Source

implement string indexing + bug fixes

Vladislav Folts 11 years ago
parent
commit
0bf923ab6e

BIN
bin/compiled.zip


+ 32 - 27
src/context.js

@@ -282,7 +282,7 @@ exports.Designator = ChainedContext.extend({
         else if (s.isConst())
             this.__currentType = Type.constType(info);
         else if (s.isVariable())
-            this.__currentType = Type.variableType(info);
+            this.__currentType = info.type();
         else if (s.isProcedure())
             this.__currentType = Type.procedureType(info);
         this.__info = info;
@@ -292,7 +292,7 @@ exports.Designator = ChainedContext.extend({
         var t = this.__currentType;
         var pointerType;
         var isReadOnly = this.__info instanceof Type.Variable 
-                      && Type.isVariableReadOnly(this.__info);
+                      && this.__info.isReadOnly();
         if (t instanceof Type.Pointer){
             pointerType = t;
             this.__handleDeref();
@@ -304,9 +304,12 @@ exports.Designator = ChainedContext.extend({
             throw new Errors.Error("cannot designate '" + t.description() + "'");
 
         this.__denote(id, pointerType);
-        this.__info = Type.makeVariable(this.__currentType, isReadOnly);
+        this.__info = this._makeDenoteVar(this.__currentType, isReadOnly);
         this.__scope = undefined;
     },
+    _makeDenoteVar: function(type, isReadOnly){
+        return Type.makeVariable(type, isReadOnly);
+    },
     handleExpression: function(e){this.__indexExpression = e;},
     __handleIndexExpression: function(){
         var e = this.__indexExpression;
@@ -315,22 +318,34 @@ exports.Designator = ChainedContext.extend({
             throw new Errors.Error(
                 Type.intsDescription() + " expression expected, got '" + expType.description() + "'");
 
-        var type = this.__currentType;
+        var index = this._indexSequence(this.__currentType, this.__info);
+        var length = index.length;
+        var pValue = e.constValue();
+        if (pValue){
+            var value = pValue.value;
+            if (value < 0)
+                throw new Errors.Error("index is negative: " + value);
+            if (length != Type.openArrayLength && value >= length)
+                throw new Errors.Error("index out of bounds: maximum possible index is "
+                                     + (length - 1)
+                                     + ", got " + value );
+        }
+        this.__currentType = index.type;
+        this.__info = index.info;
+    },
+    _indexSequence: function(type, info){
         var isArray = type instanceof Type.Array;
         if (!isArray && !(type instanceof Type.String))
             throw new Errors.Error("ARRAY or string expected, got '" + type.description() + "'");
-        var arrayLen = isArray ? Type.arrayLength(type) : Type.stringLen(type);
-        if (!isArray && !arrayLen)
-            throw new Errors.Error("cannot index empty string" );
-
-        var value = e.constValue();
-        if (value && (!isArray || arrayLen != Type.openArrayLength) && value.value >= arrayLen)
-            throw new Errors.Error("index out of bounds: maximum possible index is "
-                                 + (arrayLen - 1)
-                                 + ", got " + value.value );
 
-        this.__currentType = isArray ? Type.arrayElementsType(type) : basicTypes.ch;
-        this.__info = Type.makeVariable(this.__currentType, Type.isVariableReadOnly(this.__info));
+        var length = isArray ? Type.arrayLength(type) : Type.stringLen(type);
+        if (!isArray && !length)
+            throw new Errors.Error("cannot index empty string" );
+        var indexType = isArray ? Type.arrayElementsType(type) : basicTypes.ch;
+        return { length: length,
+                 type: indexType,
+                 info: Type.makeVariable(indexType, info instanceof Type.Const || info.isReadOnly())
+               };
     },
     handleLiteral: function(s){
         if (s == "]" || s == ","){
@@ -575,16 +590,6 @@ exports.ProcDecl = ChainedContext.extend({
             return this.__addArgument(msg.name, msg.arg);
         return ChainedContext.prototype.handleMessage.call(this, msg);
     },
-    /*
-    checkResultType: function(s){
-        if (this.__id.exported()){
-            if (!s.scope().exports()[s.symbol().id()])
-                throw new Errors.Error(
-                    "exported PROCEDURE '" + this.__id.id()
-                    + "' uses non-exported type '" + s.symbol().id() + "'");
-        }
-    },
-    */
     handleReturn: function(type){
         var result = this.__type.result();
         if (!result)
@@ -1454,7 +1459,7 @@ exports.For = ChainedContext.extend({
         var s = getSymbol(this.parent(), id);
         if (!s.isVariable())
             throw new Errors.Error("'" + s.id() + "' is not a variable");
-        var type = Type.variableType(s.info());
+        var type = s.info().type();
         if (type !== basicTypes.integer)
             throw new Errors.Error(
                   "'" + s.id() + "' is a '" 
@@ -1594,7 +1599,7 @@ exports.VariableDeclaration = HandleSymbolAsType.extend({
             if (id.exported())
                 this.checkExport(varName);
             this.currentScope().addSymbol(Symbol.makeSymbol(varName, v), id.exported());
-            var t = Type.variableType(v);
+            var t = v.type();
             gen.write("var " + varName + " = " + t.initializer(this) + ";");
         }
 

+ 25 - 1
src/eberon/EberonString.ob

@@ -1,7 +1,31 @@
 MODULE EberonString;
-IMPORT Types;
+IMPORT JsString, Types;
+TYPE
+    ElementVariable = RECORD(Types.Variable)
+    END;
 VAR
     string*: POINTER TO Types.BasicType;
+
+PROCEDURE ElementVariable.idType(): JsString.Type;
+    RETURN JsString.make("string element")
+END ElementVariable.idType;
+
+PROCEDURE ElementVariable.isReadOnly(): BOOLEAN;
+    RETURN TRUE
+END ElementVariable.isReadOnly;
+
+PROCEDURE ElementVariable.type(): Types.PType;
+    RETURN Types.basic.ch
+END ElementVariable.type;
+
+PROCEDURE makeElementVariable*(): Types.PVariable;
+VAR
+    result: POINTER TO ElementVariable;
+BEGIN
+    NEW(result);
+    RETURN result
+END makeElementVariable;
+
 BEGIN
     string := Types.makeBasic("STRING", "''");
 END EberonString.

+ 36 - 19
src/eberon/eberon_context.js

@@ -22,15 +22,17 @@ function superMethodCallGenerator(context, id, type){
 }
 
 var MethodType = Type.Procedure.extend({
-    init: function EberonContext$MethodType(type, callGenerator){
+    init: function EberonContext$MethodType(id, type, callGenerator){
         Type.Procedure.prototype.init.call(this);
+        this.__id = id;
         this.__type = type;
         this.__callGenerator = callGenerator;
     },
     procType: function(){return this.__type;},
     args: function(){return this.__type.args();},
     result: function(){return this.__type.result();},
-    description: function(){return this.__type.description();},
+    description: function(){return "method " + this.__id;},
+    procDescription: function(){return this.__type.description();},
     callGenerator: function(context, id){return this.__callGenerator(context, id, this);}
 });
 
@@ -84,10 +86,34 @@ var MethodHeading = Context.Chained.extend({
 function getMethodSelf(){}
 function getMethodSuper(){}
 
+var MethodVariable = Type.Variable.extend({
+    init: function(type){
+        this.__type = type;
+    },
+    type: function(){return this.__type;},
+    isReadOnly: function(){return true;},
+    idType: function(){return "method";}
+});
+
 var Designator = Context.Designator.extend({
     init: function EberonContext$Designator(parent){
         Context.Designator.prototype.init.call(this, parent);
     },
+    _indexSequence: function(type, info){
+        if (type == EberonString.string()){
+            var indexType = Type.basic().ch;
+            return { length: undefined, 
+                     type: indexType,
+                     info: EberonString.makeElementVariable(indexType)
+                   };
+        }
+        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);
+    },
     handleLiteral: function(s){
         if (s == "SELF")
             this.handleSymbol(Symbol.makeFound(this.handleMessage(getMethodSelf)), "this");
@@ -161,8 +187,8 @@ var RecordType = Type.Record.extend({
         if (!Cast.areProceduresMatch(existing, type))
             throw new Errors.Error(
                   "overridden method '" + id + "' signature mismatch: should be '"
-                + existing.description() + "', got '" 
-                + type.description() + "'");
+                + existing.procDescription() + "', got '" 
+                + type.procDescription() + "'");
         
         this.__definedMethods.push(id);
     },
@@ -269,7 +295,7 @@ var RecordDecl = Context.RecordDecl.extend({
         if (msg instanceof MethodOrProcMsg)
             return this.type().addMethod(
                 msg.id,
-                new MethodType(msg.type, methodCallGenerator));
+                new MethodType(msg.id.id(), msg.type, methodCallGenerator));
         if (msg == Context.endParametersMsg) // not used
             return undefined;
         if (msg instanceof Context.AddArgumentMsg) // not used
@@ -285,7 +311,6 @@ var ProcOrMethodDecl = Context.ProcDecl.extend({
         this.__methodId = undefined;
         this.__methodType = undefined;
         this.__boundType = undefined;
-        this.__isNew = undefined;
         this.__endingId = undefined;
     },
     handleMessage: function(msg){
@@ -322,14 +347,8 @@ var ProcOrMethodDecl = Context.ProcDecl.extend({
     },
     setType: function(type){
         Context.ProcDecl.prototype.setType.call(this, type);
-        this.__methodType = new MethodType(type, methodCallGenerator);
-    },
-    handleLiteral: function(s){
-        if (s == "NEW"){
-            var boundType = this.__selfSymbol.info();
-            boundType.addMethod(this.__methodId, this.__methodType);
-            this.__isNew = true;
-        }
+        if (this.__methodId)
+            this.__methodType = new MethodType(this.__methodId.id(), type, methodCallGenerator);
     },
     handleIdent: function(id){
         if (this.__selfSymbol){
@@ -349,10 +368,8 @@ var ProcOrMethodDecl = Context.ProcDecl.extend({
                 // should throw
                 Context.ProcDecl.prototype.handleIdent.call(this, this.__endingId);
 
-            if (!this.__isNew){
-                var boundType = this.__selfSymbol.info();
-                boundType.defineMethod(this.__methodId, this.__methodType);
-            }
+            var boundType = this.__selfSymbol.info();
+            boundType.defineMethod(this.__methodId, this.__methodType);
         }
         Context.ProcDecl.prototype.endParse.call(this);
     },
@@ -371,7 +388,7 @@ var ProcOrMethodDecl = Context.ProcDecl.extend({
         return {
             symbol: Symbol.makeSymbol(
                 "method",  
-                Type.makeProcedure(new MethodType(this.__methodType.procType(), superMethodCallGenerator))),
+                Type.makeProcedure(new MethodType(id, this.__methodType.procType(), superMethodCallGenerator))),
             code: Type.typeName(baseType) + ".prototype." + id + ".call"
         };
     }

+ 1 - 1
src/ob/Operator.ob

@@ -478,7 +478,7 @@ BEGIN
     designator := left.designator();
     info := designator.info();
     IF ~(info IS Types.PVariable) 
-        OR Types.isVariableReadOnly(info(Types.PVariable)^) THEN
+        OR info(Types.PVariable).isReadOnly() THEN
         Errors.raise(JsString.concat(JsString.make("cannot assign to "), info.idType()));
     END; 
 

+ 1 - 1
src/ob/Procedure.ob

@@ -124,7 +124,7 @@ BEGIN
             Errors.raise(JsString.make("constant cannot be used as VAR parameter"));
         END;
         IF (info IS Types.PVariable) 
-         & Types.isVariableReadOnly(info(Types.PVariable)^) THEN
+         & info(Types.PVariable).isReadOnly() THEN
             Errors.raise(JsString.concat(info.idType(), JsString.make(" cannot be used as VAR parameter")));
         END;
     END;

+ 55 - 38
src/ob/Types.ob

@@ -50,18 +50,26 @@ TYPE
     PConst* = POINTER TO Const;
 
     Variable* = RECORD(Id)
-        type: PType;
-        readOnly: BOOLEAN
+        PROCEDURE type*(): PType;
+        PROCEDURE isReadOnly*(): BOOLEAN
     END;
 
     PVariable* = POINTER TO Variable;
 
-    VariableRef* = RECORD(Variable)
+    VariableImpl* = RECORD(Variable) (*TO FIX*)
+        mType: PType
+    END;
+    PVariableImpl* = POINTER TO VariableImpl;
+
+    ReadOnlyVariable = RECORD(VariableImpl)
+    END;
+
+    VariableRef* = RECORD(VariableImpl)
     END;
 
     PVariableRef* = POINTER TO VariableRef;
 
-    ExportedVariable = RECORD(Variable)
+    ExportedVariable = RECORD(ReadOnlyVariable)
     END;
 
     PExportedVariable = POINTER TO ExportedVariable;
@@ -249,11 +257,7 @@ END typeName;
 PROCEDURE ProcedureId.idType(): JsString.Type;
     RETURN JsString.make("procedure")
 END ProcedureId.idType;
-(*
-PROCEDURE String.idType(): JsString.Type;
-    RETURN JsString.make("string")
-END String.idType;
-*)
+
 PROCEDURE String.description(): JsString.Type;
 VAR
     prefix: JsString.Type;
@@ -298,37 +302,33 @@ PROCEDURE constValue*(c: Const): JS.var;
 END constValue;
 
 PROCEDURE Variable.idType(): JsString.Type;
-VAR
-    result: JsString.Type;
-BEGIN
-    IF SELF.readOnly THEN
-        result := JsString.make("read-only variable");
-    ELSE
-        result := JsString.make("variable");
-    END;
-    RETURN result
+    RETURN JsString.make("variable")
 END Variable.idType;
 
-PROCEDURE variableType*(v: Variable): PType;
-    RETURN v.type
-END variableType;
+PROCEDURE ReadOnlyVariable.idType(): JsString.Type;
+    RETURN JsString.make("read-only variable")
+END ReadOnlyVariable.idType;
+
+PROCEDURE VariableImpl.type(): PType;
+    RETURN SELF.mType
+END VariableImpl.type;
 
 PROCEDURE procedureType*(p: ProcedureId): PType;
     RETURN p.type
 END procedureType;
 
-PROCEDURE isVariableReadOnly*(v: Variable): BOOLEAN;
-    RETURN v.readOnly
-END isVariableReadOnly;
+PROCEDURE Variable.isReadOnly(): BOOLEAN;
+    RETURN FALSE
+END Variable.isReadOnly;
+
+PROCEDURE ReadOnlyVariable.isReadOnly(): BOOLEAN;
+    RETURN TRUE
+END ReadOnlyVariable.isReadOnly;
 
 PROCEDURE ExportedVariable.idType(): JsString.Type;
     RETURN JsString.make("imported variable")
 END ExportedVariable.idType;
-(*
-PROCEDURE BasicType.idType(): JsString.Type;
-    RETURN JsString.make("type")
-END BasicType.idType;
-*)
+
 PROCEDURE TypeId.idType(): JsString.Type;
     RETURN JsString.make("type")
 END TypeId.idType;
@@ -648,11 +648,30 @@ END makeConst;
 
 PROCEDURE makeVariable*(type: PType; readOnly: BOOLEAN): PVariable;
 VAR
-    result: PVariable;
-BEGIN
-    NEW(result);
-    result.type := type;
-    result.readOnly := readOnly;
+    result: PVariableImpl;
+
+    PROCEDURE make(): PVariableImpl;
+    VAR
+        result: PVariableImpl;
+    BEGIN
+        NEW(result);
+        RETURN result
+    END make;
+
+    PROCEDURE makeRO(): PVariableImpl;
+    VAR
+        result: POINTER TO ReadOnlyVariable;
+    BEGIN
+        NEW(result);
+        RETURN result
+    END makeRO;
+BEGIN
+    IF readOnly THEN
+        result := makeRO();
+    ELSE
+        result := make();
+    END;
+    result.mType := type;
     RETURN result
 END makeVariable;
 
@@ -661,8 +680,7 @@ VAR
     result: PVariableRef;
 BEGIN
     NEW(result);
-    result.type := type;
-    result.readOnly := FALSE;
+    result.mType := type;
     RETURN result
 END makeVariableRef;
 
@@ -671,8 +689,7 @@ VAR
     result: PExportedVariable;
 BEGIN
     NEW(result);
-    result.type := v.type;
-    result.readOnly := TRUE;
+    result.mType := v.type();
     RETURN result
 END makeExportedVariable;
 

+ 5 - 0
test/expected/eberon/string.js

@@ -7,9 +7,13 @@ var RTL$ = {
 var m = function (){
 var s = '';var s1 = '';var s2 = '';
 var b = false;
+var i = 0;
 
 function p1(a/*ARRAY OF CHAR*/){
 }
+
+function pChar(c/*CHAR*/){
+}
 s = s1 + s2;
 b = s1 == s2;
 b = s1 != s2;
@@ -19,4 +23,5 @@ b = s1 <= s2;
 b = s1 >= s2;
 p1(s);
 RTL$.assert(s.length == 0);
+pChar(s1.charCodeAt(i));
 }();

+ 6 - 0
test/input/eberon/string.ob

@@ -2,10 +2,14 @@ MODULE m;
 VAR 
     s, s1, s2: STRING;
     b: BOOLEAN;
+    i: INTEGER;
 
 PROCEDURE p1(a: ARRAY OF CHAR);
 END p1;
 
+PROCEDURE pChar(c: CHAR);
+END pChar;
+
 BEGIN
     s := s1 + s2;
     b := s1 = s2;
@@ -17,4 +21,6 @@ BEGIN
 
     p1(s);
     ASSERT(LEN(s) = 0);
+
+    pChar(s1[i]);
 END m.

+ 12 - 4
test/test_unit.js

@@ -72,8 +72,11 @@ return {
          ["noResult()", "procedure returning no result cannot be used in an expression"]
          )
     ),
-"string expression": testWithGrammar(
-    grammar.expression,
+"string expression": testWithContext(
+    context(grammar.expression,
+            "CONST cs = \"abc\";"
+            + "PROCEDURE charByRef(VAR c: CHAR): CHAR; RETURN c END charByRef;"
+           ),
     pass("\"\"",
          "\"a\"",
          "\"abc\"",
@@ -83,7 +86,8 @@ return {
          "0X"),
     fail(["\"", "unexpected end of string"],
          ["\"abc", "unexpected end of string"],
-         ["FFX", "undeclared identifier: 'FFX'"]
+         ["FFX", "undeclared identifier: 'FFX'"],
+         ["charByRef(cs[1])", "read-only variable cannot be used as VAR parameter"]
         )
     ),
 "parentheses": testWithGrammar(
@@ -1118,7 +1122,11 @@ return {
          ["CONST cs = \"\"; VAR i: INTEGER; BEGIN ASSERT(cs[i] = \"a\"); END",
           "cannot index empty string"],
          ["CONST cs = \"a\"; BEGIN ASSERT(cs[1] = \"a\"); END",
-          "index out of bounds: maximum possible index is 0, got 1"]
+          "index out of bounds: maximum possible index is 0, got 1"],
+         ["CONST ci = -1; VAR a: ARRAY 10 OF INTEGER; BEGIN ASSERT(a[ci] = 0); END",
+          "index is negative: -1"],
+         ["CONST ci = -1; PROCEDURE p(a: ARRAY OF INTEGER); BEGIN ASSERT(a[ci] = 0); END p; END",
+          "index is negative: -1"]
         )
     ),
 "multi-dimensional array expression": testWithGrammar(

+ 30 - 0
test/test_unit_eberon.js

@@ -122,6 +122,27 @@ exports.suite = {
     pass("o.f()"),
     fail(["o.p()", "procedure returning no result cannot be used in an expression"])
     ),
+"cannot assign to method": testWithContext(
+    context(grammar.statement,
+              "TYPE T = RECORD PROCEDURE p() END;"
+            + "VAR o: T;"
+            + "PROCEDURE T.p(); END T.p;"
+            ),
+    pass(),
+    fail(["o.p := o.p", "cannot assign to method"],
+         ["o.p := NIL", "cannot assign to method"])
+    ),
+"method cannot be referenced": testWithContext(
+    context(grammar.statement,
+              "TYPE T = RECORD PROCEDURE p() END;"
+            + "Proc = PROCEDURE();"
+            + "VAR o: T;"
+            + "PROCEDURE T.p(); END T.p;"
+            + "PROCEDURE proc(p: Proc); END proc;"
+            ),
+    pass(),
+    fail(["proc(o.p)", "type mismatch for argument 1: 'method p' cannot be converted to 'Proc'"])
+    ),
 "method super call": testWithContext(
     context(grammar.declarationSequence,
               "TYPE T = RECORD PROCEDURE p(); PROCEDURE pAbstract(); PROCEDURE pAbstract2() END;"
@@ -203,5 +224,14 @@ exports.suite = {
             "VAR s: STRING;"),
     pass("LEN(s)"),
     fail()
+    ),
+"STRING indexing": testWithContext(
+    context(grammar.expression,
+            "VAR s: STRING;"
+            + "PROCEDURE pCharByVar(VAR c: CHAR): CHAR; RETURN c END pCharByVar;"),
+    pass("s[0]"),
+    fail(["s[-1]", "index is negative: -1"],
+         ["pCharByVar(s[0])", "string element cannot be used as VAR parameter"]
+         )
     )
 };