Browse Source

check types in relations

Vladislav Folts 12 years ago
parent
commit
d829567f82
3 changed files with 242 additions and 86 deletions
  1. 86 51
      src/cast.js
  2. 99 34
      src/context.js
  3. 57 1
      test/test_unit.js

+ 86 - 51
src/cast.js

@@ -1,3 +1,4 @@
+"use strict";
 var Code = require("code.js");
 var Type = require("type.js");
 var ArrayType = Type.Array;
@@ -5,58 +6,92 @@ var PointerType = Type.Pointer;
 
 function doNoting(context, e){return e;}
 
+function findBaseType(base, type){
+    while (type && type != base)
+        type = type.baseType();
+    return type;
+}
+
+function findPointerBaseType(pBase, pType){
+    if (!findBaseType(pBase.baseType(), pType.baseType()))
+        return undefined;
+    return pBase;
+}
+
+function matchesToNIL(t){
+    return t instanceof PointerType || t.isProcedure();
+}
+
+function areTypesMatch(t1, t2){
+    if (t1 == t2)
+        return true;
+    if (t1 instanceof PointerType && t2 instanceof PointerType)
+        return areTypesMatch(t1.baseType(), t2.baseType());
+    if (t1.isProcedure() && t2.isProcedure())
+        return areProceduresMatch(t1, t2);
+    if (t1 == Type.nil && matchesToNIL(t2)
+        || t2 == Type.nil && matchesToNIL(t1))
+        return true;
+    return false;
+}
+
+function areProceduresMatch(p1, p2){
+    var args1 = p1.arguments();
+    var args2 = p2.arguments();
+    if (args1.length != args2.length)
+        return false;
+
+    for(var i = 0; i < args1.length; ++i){
+        var a1 = args1[i];
+        var a2 = args2[i];
+        if (a1.isVar != a2.isVar)
+            return false;
+        if (a1.type != p1 && a2.type != p2
+            &&!areTypesMatch(a1.type, a2.type))
+            return false;
+    }
+
+    var r1 = p1.result();
+    var r2 = p2.result();
+    if (r1 == p1 && r2 == p2)
+        return true;
+    return areTypesMatch(r1, r2);
+}
+
 function implicitCast(from, to){
-	if (from === to)
-		return doNoting;
-
-	if (from instanceof Type.String){
-		if (to === Type.basic.char){
-			var v = from.asChar();
-			if (v !== undefined)
-				return function(){return new Code.Expression(v, to);};
-		}
-		else if (to instanceof Type.Array && to.elementsType() == Type.basic.char)
-			return function(context, e){
-				return new Code.Expression(context.rtl().strToArray(e.code()), to);
-			};
-	}
-	else if (from instanceof ArrayType && to instanceof ArrayType)
-		return implicitCast(from.elementsType(), to.elementsType());
-	else if ((from instanceof PointerType && to instanceof PointerType)
-		|| (from instanceof Type.Record && to instanceof Type.Record)){
-		var toR = to instanceof PointerType ? to.baseType() : to;
-		var fromR = from.baseType();
-		while (fromR && fromR != toR)
-			fromR = fromR.baseType();
-		if (fromR)
-			return doNoting;
-	}
-	else if (from == Type.nil
-		&& (to instanceof PointerType || to.isProcedure()))
-		return doNoting;
-	else if (from.isProcedure() && to.isProcedure()){
-		var fromArgs = from.arguments();
-		var toArgs = to.arguments();
-		if (fromArgs.length == toArgs.length){
-			for(var i = 0; i < fromArgs.length; ++i){
-				var fromArg = fromArgs[i];
-				var toArg = toArgs[i];
-				if (toArg.isVar != fromArg.isVar)
-					return undefined;
-				if ((toArg.type != to || fromArg.type != from)
-					&& !implicitCast(fromArg.type, toArg.type))
-					return undefined;
-			}
-
-			var fromResult = from.result();
-			var toResult = to.result();
-			if (toResult == to && fromResult == from)
-				return doNoting;
-			if (implicitCast(fromResult, toResult))
-				return doNoting;
-		}
-	}
-	return undefined;
+    if (from === to)
+        return doNoting;
+
+    if (from instanceof Type.String){
+        if (to === Type.basic.char){
+            var v = from.asChar();
+            if (v !== undefined)
+                return function(){return new Code.Expression(v, to);};
+        }
+        else if (to instanceof Type.Array && to.elementsType() == Type.basic.char)
+            return function(context, e){
+                return new Code.Expression(context.rtl().strToArray(e.code()), to);
+            };
+    }
+    else if (from instanceof ArrayType && to instanceof ArrayType)
+        return implicitCast(from.elementsType(), to.elementsType());
+    else if (from instanceof PointerType && to instanceof PointerType){
+        if (findPointerBaseType(to, from))
+            return doNoting;
+    }
+    else if (from instanceof Type.Record && to instanceof Type.Record){
+        if (findBaseType(to, from))
+            return doNoting;
+    }
+    else if (from == Type.nil && matchesToNIL(to))
+        return doNoting;
+    else if (from.isProcedure() && to.isProcedure()){
+        if (areProceduresMatch(from, to))
+            return doNoting;
+    }
+    return undefined;
 }
 
+exports.areTypesMatch = areTypesMatch;
 exports.implicit = implicitCast;
+exports.findPointerBaseType = findPointerBaseType;

+ 99 - 34
src/context.js

@@ -31,7 +31,7 @@ function throwTypeMismatch(from, to){
 }
 
 function checkTypeMatch(from, to){
-    if (from !== to)
+    if (!Cast.areTypesMatch(from, to))
         throwTypeMismatch(from, to);
 }
 
@@ -576,21 +576,106 @@ exports.ArrayDimensions = ChainedContext.extend({
     }
 });
 
-function assertOpType(type, types, expected, literal, op){
-    if (types.indexOf(type) != -1)
-        return op;
-    throw new Errors.Error(
-        "operator '" + literal +
-        "' type mismatch: " + expected + " expected, got '" +
-        type.description() + "'");
+var numericOpTypeCheck = {
+    expect: "numeric type",
+    check: function(t){return [basicTypes.int, basicTypes.real].indexOf(t) != -1;}
+};
+
+var intOpTypeCheck = {
+    expect: "INTEGER",
+    check: function(t){return t == basicTypes.int;}
+};
+
+var orderOpTypeCheck = {
+    expect: "numeric type or CHAR or character array",
+    check: function(t){
+        return [basicTypes.int, basicTypes.real, basicTypes.char].indexOf(t) != -1
+            || (t instanceof Type.Array && t.elementsType() == basicTypes.char);
+    }
+};
+
+var equalOpTypeCheck = {
+    expect: "numeric type or SET or BOOLEAN OR CHAR or character array or POINTER or PROCEDURE",
+    check: function(t){
+        return [basicTypes.int, basicTypes.real, basicTypes.set, basicTypes.bool, basicTypes.char].indexOf(t) != -1
+            || (t instanceof Type.Array && t.elementsType() == basicTypes.char)
+            || t instanceof Type.Pointer
+            || t.isProcedure()
+            || t == Type.nil;
+    }
+};
+
+function assertOpType(type, check, literal){
+    if (!check.check(type))
+        throw new Errors.Error(
+            "operator '" + literal +
+            "' type mismatch: " + check.expect + " expected, got '" +
+            type.description() + "'");
 }
 
 function assertNumericOp(type, literal, op){
-    return assertOpType(type, [basicTypes.int, basicTypes.real], "numeric type", literal, op);
+    assertOpType(type, numericOpTypeCheck, literal);
+    return op;
 }
 
 function assertIntOp(type, literal, op){
-    return assertOpType(type, [basicTypes.int], "INTEGER", literal, op);
+    assertOpType(type, intOpTypeCheck, literal);
+    return op;
+}
+
+function useTypeInRelation(leftType, rightType){
+    if (leftType instanceof Type.Pointer && rightType instanceof Type.Pointer){
+        var type = Cast.findPointerBaseType(leftType, rightType);
+        if (!type)
+            type = Cast.findPointerBaseType(rightType, leftType);
+        if (type)
+            return type;
+    }
+    checkTypeMatch(rightType, leftType);
+    return leftType;
+}
+
+function relationOp(leftType, rightType, literal){
+    var o;
+    var check;
+    var type = useTypeInRelation(leftType, rightType);
+    switch (literal){
+        case "=":
+            o = op.equal;
+            check = equalOpTypeCheck;
+            break;
+        case "#":
+            o = op.notEqual;
+            check = equalOpTypeCheck;
+            break;
+        case "<":
+            o = op.less;
+            check = orderOpTypeCheck;
+            break;
+        case ">":
+            o = op.greater;
+            check = orderOpTypeCheck;
+            break;
+        case "<=":
+            if (type == basicTypes.set)
+                o = op.setInclL;
+            else {
+                o = op.eqLess;
+                check = orderOpTypeCheck;
+            }
+            break;
+        case ">=":
+            if (type == basicTypes.set)
+                o = op.setInclR;
+            else {
+                o = op.eqGreater;
+                check = orderOpTypeCheck;
+            }
+            break;
+        }
+    if (check)
+        assertOpType(type, check, literal);
+    return o;
 }
 
 exports.AddOperator = ChainedContext.extend({
@@ -869,37 +954,17 @@ exports.Expression = ChainedContext.extend({
                 throw new Errors.Error("type name expected");
             code = leftCode + " instanceof " + rightCode;
         }
-        else{
+        else {
             leftExpression = promoteTypeInExpression(leftExpression, rightType);
             rightExpression = promoteTypeInExpression(rightExpression, leftType);
             leftCode = leftExpression.code();
             rightCode = rightExpression.code();
-            checkImplicitCast(rightExpression.type(), leftExpression.type());
+            //checkImplicitCast(rightExpression.type(), leftExpression.type());
         }
 
-        var o;
-        switch (this.__relation){
-            case "=":
-                o = op.equal;
-                break;
-            case "#":
-                o = op.notEqual;
-                break;
-            case "<":
-                o = op.less;
-                break;
-            case ">":
-                o = op.greater;
-                break;
-            case "<=":
-                o = (leftType == basicTypes.set) ? op.setInclL : op.eqLess;
-                break;
-            case ">=":
-                o = (leftType == basicTypes.set) ? op.setInclR : op.eqGreater;
-                break;
-            }
         var value;
-        if (o){
+        if (!code){
+            var o = relationOp(leftExpression.type(), rightExpression.type(), this.__relation);
             var oResult = o(leftExpression, rightExpression, this);
             code = oResult.code();
             value = oResult.constValue();

+ 57 - 1
test/test_unit.js

@@ -79,6 +79,27 @@ function setupWithContext(grammar, source){
     return setup(grammar, makeContext);
 }
 
+function context(grammar, source){
+    return {grammar: grammar, source: source};
+}
+
+function pass(/*...*/){return Array.prototype.slice.call(arguments);}
+
+function fail(/*...*/){return Array.prototype.slice.call(arguments);}
+
+function testWithContext(context, pass, fail){
+    return function(){
+        var test = setupWithContext(context.grammar, context.source);
+        var i;
+        for(i = 0; i < pass.length; ++i)
+            test.parse(pass[i]);
+        for(i = 0; i < fail.length; ++i){
+            var f = fail[i];
+            test.expectError(f[0], f[1]);
+        }
+    };
+}
+
 var testSuite = {
 comment: function(){
     var test = setup(Grammar.expression);
@@ -298,6 +319,22 @@ identifier: function(){
     test.expectError("i(Derived)"
                    , "invalid type cast: 'Derived' is not an extension of 'INTEGER'");
 },
+"POINTER relations": testWithContext(
+    context(Grammar.expression,
+            "TYPE B = RECORD END; D = RECORD(B) END;"
+          + "VAR p1, p2: POINTER TO RECORD END; pb: POINTER TO B; pd: POINTER TO D;"),
+    pass("p1 = p2",
+         "p1 # p2",
+         "pb = pd",
+         "pd # pb"
+         ),
+    fail(["p1 < p2", "operator '<' type mismatch: numeric type or CHAR or character array expected, got 'POINTER TO anonymous RECORD'"],
+         ["p1 <= p2", "operator '<=' type mismatch: numeric type or CHAR or character array expected, got 'POINTER TO anonymous RECORD'"],
+         ["p1 > p2", "operator '>' type mismatch: numeric type or CHAR or character array expected, got 'POINTER TO anonymous RECORD'"],
+         ["p1 >= p2", "operator '>=' type mismatch: numeric type or CHAR or character array expected, got 'POINTER TO anonymous RECORD'"],
+         ["p1 = pb", "type mismatch: expected 'POINTER TO anonymous RECORD', got 'POINTER TO B'"]
+         )
+    ),
 "IS expression": function(){
     var test = setupWithContext(
           Grammar.expression
@@ -714,6 +751,16 @@ procedure: function(){
     test.expectError("PROCEDURE p(): INTEGER; RETURN TRUE END p"
                    , "RETURN 'INTEGER' expected, got 'BOOLEAN'");
 },
+"PROCEDURE relations": testWithContext(
+    context(Grammar.expression,
+            "VAR p1: PROCEDURE; p2: PROCEDURE;"),
+    pass("p1 = p2",
+         "p1 # p2",
+         "p1 = NIL",
+         "NIL # p1"
+         ),
+    fail()
+    ),
 "pass VAR argument as VAR parameter": function(){
     var test = setupWithContext(Grammar.procedureDeclaration,
                                 "PROCEDURE p1(VAR i: INTEGER); END p1;"
@@ -838,6 +885,15 @@ procedure: function(){
     test.expectError("intArray := \"abcd\""
                    , "type mismatch: 'intArray' is 'ARRAY OF INTEGER' and cannot be assigned to 'multi-character string' expression");
 },
+"string relations": testWithContext(
+    context(Grammar.expression,
+            "VAR ch: CHAR;"),
+    pass("ch = \"a\"",
+         "\"a\" = ch",
+         "ch # \"a\"",
+         "\"a\" # ch"
+        ),
+    fail(["ch = \"ab\"", "type mismatch: expected 'CHAR', got 'multi-character string'"])),
 "array assignment": function(){
     var test = setupWithContext(
           Grammar.statement
@@ -867,7 +923,7 @@ procedure: function(){
     test.expectError("r1 := r2", "type mismatch: 'r1' is 'T1' and cannot be assigned to 'T2' expression");
     test.expectError("r1 := b1", "type mismatch: 'r1' is 'T1' and cannot be assigned to 'Base1' expression");
 },
-"open array assignment fails": function(){
+"open Array assignment fails": function(){
     var test = setup(Grammar.procedureDeclaration);
     test.expectError("PROCEDURE p(s1, s2: ARRAY OF CHAR); BEGIN s1 := s2 END p"
                    , "cannot assign to read-only variable");