Forráskód Böngészése

improve runtime diagnostic for failing type guard (include module names)

Vladislav Folts 10 éve
szülő
commit
f8ca2bd829

BIN
bin/compiled.zip


+ 2 - 2
src/eberon/eberon_grammar.js

@@ -180,9 +180,9 @@ exports.language = {
         makeOpenArray: function(type){return new EbArray.OpenArray(type); }
     },
     codeGenerator: {
-        make: CodeGenerator.makeGenerator,
+        make: function(){ return new CodeGenerator.Generator(); },                                                                                                                                                                                          
         nil: CodeGenerator.nullGenerator()
-    },
+    },                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      
     rtl: {
         base: EbRtl.Type,
         methods: EbRtlCode.rtl.methods,

+ 69 - 16
src/ob/CodeGenerator.ob

@@ -4,10 +4,21 @@ IMPORT
 CONST
     kTab* = 09X;
 TYPE
+    Insertion* = RECORD
+        PROCEDURE Insertion(index: INTEGER);
+
+        index: INTEGER;
+    END;
+    PInsertion* = POINTER TO Insertion;
+
     IGenerator* = RECORD
         PROCEDURE write*(s: STRING);
         PROCEDURE openScope*();
         PROCEDURE closeScope*(ending: STRING);
+
+        PROCEDURE makeInsertion*(): PInsertion;
+        PROCEDURE insert*(i: Insertion; s: STRING);
+
         PROCEDURE result*(): STRING;
     END;
 
@@ -16,28 +27,43 @@ TYPE
     NullGenerator = RECORD(IGenerator)
     END;
 
-    SimpleGenerator = RECORD(NullGenerator)
+    SimpleGenerator* = RECORD(NullGenerator)
         mResult: STRING
     END;
 
     Indent* = RECORD
+        PROCEDURE Indent(indent: INTEGER);
+
         indent*: INTEGER;
         result*: STRING;
     END;
 
-    Generator = RECORD(IGenerator)
-        indent: Indent;
+    Generator* = RECORD(IGenerator)
+        PROCEDURE Generator*();
+
+        indents: ARRAY * OF Indent;
     END;
 
 VAR
     nullGenerator*: POINTER TO NullGenerator;
 
+PROCEDURE Insertion.Insertion(index: INTEGER)
+    | index(index);
+END;
+
 PROCEDURE NullGenerator.write(s: STRING); END;
 
 PROCEDURE NullGenerator.openScope(); END;
 
 PROCEDURE NullGenerator.closeScope(ending: STRING); END;
 
+PROCEDURE NullGenerator.makeInsertion(): PInsertion;
+    RETURN NIL;
+END;
+
+PROCEDURE NullGenerator.insert(i: Insertion; s: STRING);
+END;
+
 PROCEDURE NullGenerator.result(): STRING;
     RETURN "";
 END;
@@ -61,7 +87,7 @@ BEGIN
     RETURN result
 END;
 
-PROCEDURE indentText*(s: STRING; indent: INTEGER): STRING;
+PROCEDURE indentText(s: STRING; indent: INTEGER): STRING;
 VAR
     result: STRING;
 BEGIN
@@ -76,21 +102,22 @@ BEGIN
     RETURN result + String.substr(s, pos, LEN(s) - pos);
 END;
 
-PROCEDURE addIndentedText*(s: STRING; VAR indent: Indent);
+PROCEDURE addIndentedText(s: STRING; VAR indent: Indent);
 BEGIN
     indent.result := indent.result + indentText(s, indent.indent);
 END;
 
-PROCEDURE openScope*(VAR indent: Indent);
+PROCEDURE openScope(VAR indent: Indent);
 BEGIN
     INC(indent.indent);
     indent.result := indent.result + "{" + Stream.kCR + makeIndent(indent.indent);
 END;
 
-PROCEDURE closeScope*(ending: STRING; VAR indent: Indent);
+PROCEDURE closeScope(ending: STRING; VAR indent: Indent);
 BEGIN
     DEC(indent.indent);
-    indent.result := String.substr(indent.result, 0, LEN(indent.result) - 1) + "}";
+    lenWithoutLastIndent <- LEN(indent.result) - 1;
+    indent.result := String.substr(indent.result, 0, lenWithoutLastIndent) + "}";
     IF LEN(ending) # 0 THEN
         addIndentedText(ending, indent);
     ELSE
@@ -100,29 +127,55 @@ END;
 
 PROCEDURE Generator.write(s: STRING);
 BEGIN
-    addIndentedText(s, SELF.indent);
+    addIndentedText(s, SELF.indents[LEN(SELF.indents) - 1]);
 END;
 
 PROCEDURE Generator.openScope();
 BEGIN
-    openScope(SELF.indent);
+    openScope(SELF.indents[LEN(SELF.indents) - 1]);
 END;
 
 PROCEDURE Generator.closeScope(ending: STRING);
 BEGIN
-    closeScope(ending, SELF.indent);
+    i <- LEN(SELF.indents) - 1;
+    WHILE LEN(SELF.indents[i].result) = 0 DO
+        SELF.indents.remove(i);
+        DEC(i);
+    END;
+
+    closeScope(ending, SELF.indents[i]);
+END;
+
+PROCEDURE Generator.makeInsertion(): PInsertion;
+BEGIN
+    index <- LEN(SELF.indents) - 1;
+    result <- NEW Insertion(index);
+    SELF.indents.add(Indent(SELF.indents[index].indent));
+    RETURN result;
+END;
+
+PROCEDURE Generator.insert(i: Insertion; s: STRING);
+BEGIN
+    addIndentedText(s, SELF.indents[i.index]);
 END;
 
 PROCEDURE Generator.result(): STRING;
-    RETURN SELF.indent.result
+VAR
+    result: STRING;
+BEGIN
+    FOR i <- 0 TO LEN(SELF.indents) - 1 DO
+        result := result + SELF.indents[i].result;
+    END;
+    RETURN result;
 END;
 
-PROCEDURE makeSimpleGenerator*(): PIGenerator;
-    RETURN NEW SimpleGenerator();
+PROCEDURE Indent.Indent(indent: INTEGER)
+    | indent(indent);
 END;
 
-PROCEDURE makeGenerator*(): PIGenerator;
-    RETURN NEW Generator();
+PROCEDURE Generator.Generator();
+BEGIN
+    SELF.indents.add(Indent(0));
 END;
 
 BEGIN

+ 2 - 2
src/ob/ContextExpression.ob

@@ -913,7 +913,7 @@ END;
 
 PROCEDURE SetElement.SetElement(parent: PSet)
     | SUPER(parent),
-      code(CodeGenerator.makeSimpleGenerator());
+      code(NEW CodeGenerator.SimpleGenerator());
 END;
 
 PROCEDURE SetElement.codeGenerator(): CodeGenerator.PIGenerator;
@@ -926,7 +926,7 @@ BEGIN
     IF LEN(SELF.from) = 0 THEN
         SELF.from := SELF.code.result();
         SELF.fromValue := value;
-        SELF.code := CodeGenerator.makeSimpleGenerator();
+        SELF.code := NEW CodeGenerator.SimpleGenerator();
     ELSE
         SELF.to := SELF.code.result();
         SELF.toValue := value;

+ 1 - 1
src/ob/ContextLoop.ob

@@ -101,7 +101,7 @@ END;
 
 PROCEDURE For.For(parent: ContextHierarchy.PNode)
     | SUPER(parent),
-      toExpr(CodeGenerator.makeSimpleGenerator()),
+      toExpr(NEW CodeGenerator.SimpleGenerator()),
       by(1);
 END;
 

+ 17 - 3
src/ob/ContextModule.ob

@@ -1,7 +1,7 @@
 MODULE ContextModule;
 IMPORT
-    ContextHierarchy, Errors, LanguageContext, 
-    Scope, ScopeBase, String, Symbols, Types;
+    ContextHierarchy, ContextType, Errors, LanguageContext, 
+    Object, Scope, ScopeBase, String, Symbols, Types;
 TYPE
     Declaration* = RECORD(ContextHierarchy.Node)
         PROCEDURE findModule(name: STRING): Types.PModule;
@@ -11,6 +11,7 @@ TYPE
         imports: MAP OF Symbols.PSymbol;
         moduleScope: Scope.PModule;
         moduleGen: LanguageContext.PModuleGenerator;
+        scopeInfo: ContextType.PScopeInfoGenerator;
     END;
     PDeclaration = POINTER TO Declaration;
 
@@ -60,7 +61,10 @@ BEGIN
     SELF.moduleGen := root.language().moduleGenerator(
             SELF.name,
             moduleAliases);
-    SELF.codeGenerator().write(SELF.moduleGen.prolog());
+
+    code <- SELF.codeGenerator();
+    code.write(SELF.moduleGen.prolog());
+    SELF.scopeInfo := NEW ContextType.ScopeInfoGenerator(SELF.name, code, NIL);
 END;
 
 PROCEDURE Declaration.qualifyScope(scope: ScopeBase.PType): STRING;
@@ -83,6 +87,16 @@ BEGIN
     RETURN result;
 END;
 
+PROCEDURE Declaration.handleMessage(VAR msg: ContextHierarchy.Message): Object.PType;
+VAR
+    result: Object.PType;
+BEGIN
+    IF ~ContextType.handleDescribeScopeMsg(msg, SELF.scopeInfo^) THEN
+        result := SUPER(msg);
+    END;
+    RETURN result;
+END;
+
 PROCEDURE Import.handleIdent(id: STRING);
 BEGIN
     SELF.currentModule := id;

+ 5 - 1
src/ob/ContextProcedure.ob

@@ -20,6 +20,7 @@ TYPE
         type: Procedure.PType;
         multipleArguments: BOOLEAN;
         returnParsed: BOOLEAN;
+        scopeInfo: ContextType.PScopeInfoGenerator;
     END;
     PDeclaration = POINTER TO Declaration;
 
@@ -92,7 +93,9 @@ END;
 
 PROCEDURE Declaration.doBeginBody();
 BEGIN
-    SELF.codeGenerator().openScope();
+    code <- SELF.codeGenerator();
+    code.openScope();
+    SELF.scopeInfo := NEW ContextType.ScopeInfoGenerator(SELF.id.id(), code, SELF.parent());
 END;
 
 PROCEDURE Declaration.typeName(): STRING;
@@ -140,6 +143,7 @@ BEGIN
     ELSIF msg IS AddArgumentMsg THEN
         ASSERT(msg.arg # NIL);
         addArgument(SELF, msg.name, msg.arg^);
+    ELSIF ContextType.handleDescribeScopeMsg(msg, SELF.scopeInfo^) THEN
     ELSE
         result := SUPER(msg);
     END;

+ 83 - 3
src/ob/ContextType.ob

@@ -105,6 +105,29 @@ TYPE
         id-: STRING;
     END;
 
+    ScopeInfo* = RECORD
+        PROCEDURE ScopeInfo(id: STRING; depth: INTEGER);
+
+        id: STRING;
+        depth: INTEGER;
+    END;
+    PScopeInfo = POINTER TO ScopeInfo;
+
+    ScopeInfoGenerator* = RECORD
+        PROCEDURE ScopeInfoGenerator*(name: STRING; code: CodeGenerator.PIGenerator; parent: ContextHierarchy.PNode);
+
+        name: STRING;
+        code: CodeGenerator.PIGenerator;
+        parent: ContextHierarchy.PNode;
+        codeBegin: CodeGenerator.PInsertion;
+        info: PScopeInfo;
+    END;
+    PScopeInfoGenerator* = POINTER TO ScopeInfoGenerator;
+
+    DescribeScopeMsg* = RECORD(ContextHierarchy.Message)
+        result: PScopeInfo;
+    END;
+
 PROCEDURE HandleSymbolAsType.handleQIdent(q: ContextHierarchy.QIdent);
 BEGIN
     s <- ContextHierarchy.getQIdSymbolAndScope(SELF.root()^, q);
@@ -361,7 +384,7 @@ END;
 
 PROCEDURE Record.doGenerateConstructor(): STRING;
 BEGIN
-    gen <- CodeGenerator.makeGenerator();
+    gen <- NEW CodeGenerator.Generator();
     gen.write("function " + SELF.cons + "()");
     gen.openScope();
     gen.write(SELF.doGenerateBaseConstructorCallCode() 
@@ -374,10 +397,16 @@ PROCEDURE Record.generateInheritance(): STRING;
 VAR
     result: STRING;
 BEGIN
+    scopeMsg <- DescribeScopeMsg();
+    void <- SELF.parent().handleMessage(scopeMsg);
+    scope <- scopeMsg.result.id;
+
     base <- SELF.type.base;
-    IF base # NIL THEN
+    IF base = NIL THEN
+        result := SELF.cons + ".prototype.$scope = " + scope + ";" + Chars.ln;
+    ELSE
         qualifiedBase <- SELF.qualifyScope(base.scope) + base.name; 
-        result := SELF.root().language().rtl.extend(SELF.cons, qualifiedBase) + ";" + Chars.ln;
+        result := SELF.root().language().rtl.extend(SELF.cons, qualifiedBase, scope) + ";" + Chars.ln;
     END;
     RETURN result;
 END;
@@ -403,6 +432,8 @@ BEGIN
 END;
 
 PROCEDURE Record.endParse(): BOOLEAN;
+VAR
+    scopeMsg: DescribeScopeMsg;
 BEGIN
     SELF.codeGenerator().write(
           SELF.doGenerateConstructor()
@@ -551,4 +582,53 @@ PROCEDURE ForwardTypeMsg.ForwardTypeMsg(id: STRING)
     | id(id);
 END;
 
+PROCEDURE ScopeInfo.ScopeInfo(id: STRING; depth: INTEGER)
+    | id(id),
+      depth(depth);
+END;
+
+PROCEDURE ScopeInfoGenerator.ScopeInfoGenerator(name: STRING; code: CodeGenerator.PIGenerator; parent: ContextHierarchy.PNode)
+    | name(name),
+      code(code),
+      parent(parent),
+      codeBegin(code.makeInsertion());
+BEGIN
+END;
+
+PROCEDURE makeScopeInfo(name: STRING; code: CodeGenerator.IGenerator; parent: ContextHierarchy.PNode): PScopeInfo;
+VAR
+    id, description: STRING;
+BEGIN
+    id := "$scope";
+    depth <- 0;
+        
+    IF parent = NIL THEN
+        description := Chars.doubleQuote + name + Chars.doubleQuote;
+    ELSE
+        msg <- DescribeScopeMsg();
+        void <- parent.handleMessage(msg);
+        depth := msg.result.depth + 1;
+        description := msg.result.id + " + " + Chars.doubleQuote + "." + name + Chars.doubleQuote;
+        id := id + String.fromInt(depth);
+    END;
+
+    code.write("var " + id + " = " + description + ";" + Chars.ln);
+    RETURN NEW ScopeInfo(id, depth);
+END;
+
+PROCEDURE handleDescribeScopeMsg*(VAR msg: ContextHierarchy.Message; VAR s: ScopeInfoGenerator): BOOLEAN;
+BEGIN
+    result <- FALSE;
+    IF msg IS DescribeScopeMsg THEN
+        IF s.info = NIL THEN
+            code <- CodeGenerator.Generator();
+            s.info := makeScopeInfo(s.name, code, s.parent);
+            s.code.insert(s.codeBegin^, code.result());
+        END;
+        msg.result := s.info;
+        result := TRUE;
+    END;
+    RETURN result;
+END;
+
 END ContextType.

+ 1 - 1
src/ob/OberonRtl.ob

@@ -10,7 +10,7 @@ TYPE
         setInclR*: PROCEDURE(l, r: STRING): STRING;
         assertId*: PROCEDURE(): STRING;
         makeRef*: PROCEDURE(derefCode, propCode: STRING): STRING;
-        PROCEDURE extend*(cons, base: STRING): STRING;
+        PROCEDURE extend*(cons, base, scope: STRING): STRING;
         PROCEDURE makeArray*(dimensions: STRING): STRING;
         PROCEDURE makeCharArray*(dimensions: STRING): STRING;
         PROCEDURE typeGuard*(from, to: STRING): STRING;

+ 1 - 1
src/oberon/OberonContext.ob

@@ -29,7 +29,7 @@ TYPE
 
 PROCEDURE ProcedureCall.ProcedureCall(parent: ContextHierarchy.PNode)
     | SUPER(parent),
-      code(CodeGenerator.makeSimpleGenerator());
+      code(NEW CodeGenerator.SimpleGenerator());
 BEGIN
     NEW(SELF.attributes);
 END;

+ 1 - 1
src/oberon/oberon_grammar.js

@@ -148,7 +148,7 @@ exports.language = {
         makeOpenArray: function(type){return new Types.OpenArray(type); }
     },
     codeGenerator: {
-        make: CodeGenerator.makeGenerator,
+        make: function(){ return new CodeGenerator.Generator(); },
         nil: CodeGenerator.nullGenerator()
     },
     rtl: {

+ 13 - 6
src/rtl.js

@@ -29,11 +29,12 @@ Class.extend = function extend(methods){
     };
 
 var methods = {
-    extend: function(cons, base){
+    extend: function(cons, base, scope){
         function Type(){}
         Type.prototype = base.prototype;
         cons.prototype = new Type();
         cons.prototype.constructor = cons;
+        cons.prototype.$scope = scope;
     },
     typeGuard: function(from, to){
         if (!from)
@@ -42,19 +43,25 @@ var methods = {
             var fromStr;
             var toStr;
             
-            if (from && from.constructor && from.constructor.name)
-                fromStr = "" + from.constructor.name;
+            if (from && from.constructor && from.constructor.name){
+                var name = from.constructor.name;
+                var scope = from.$scope;
+                fromStr = scope ? scope + "." + name : name;
+            }
             else
                 fromStr = "" + from;
             
-            if (to.name)
+            if (to.name){
                 toStr = "" + to.name;
+                var scope = to.prototype.$scope;
+                toStr = scope ? scope + "." + toStr : toStr;
+            }
             else
                 toStr = "" + to;
             
-            var msg = "typeguard assertion failed";
+            var msg = "cannot cast";
             if (fromStr || toStr)               
-                msg += ": '" + fromStr + "' is not an extension of '" + toStr + "'";
+                msg += " to '" + toStr + "' from '" + fromStr + "'";
             throw new Error(msg);
         }
         return from;

+ 1 - 0
test/expected/array.js

@@ -22,6 +22,7 @@ function p2(a/*VAR ARRAY 10 OF INTEGER*/){
 }
 
 function testAssign(){
+	var $scope1 = $scope + ".testAssign";
 	function T(){
 	}
 	var aInts1 = RTL$.makeArray(3, 0);var aInts2 = RTL$.makeArray(3, 0);

+ 4 - 1
test/expected/eberon/map.js

@@ -69,13 +69,14 @@ function NestedForEach(){
 }
 
 function put(){
+	var $scope1 = $scope + ".put";
 	function T(){
 		this.field = 0;
 	}
 	function Derived(){
 		T.call(this);
 	}
-	RTL$.extend(Derived, T);
+	RTL$.extend(Derived, T, $scope1);
 	var m = {};
 	var s = '';
 	var a = RTL$.makeCharArray(3);
@@ -148,6 +149,7 @@ function assign(a/*MAP OF INTEGER*/){
 }
 
 function copyMapOfRecord(){
+	var $scope1 = $scope + ".copyMapOfRecord";
 	function T(){
 	}
 	var r1 = {};var r2 = {};
@@ -155,6 +157,7 @@ function copyMapOfRecord(){
 }
 
 function cloneMapOfRecord(){
+	var $scope1 = $scope + ".cloneMapOfRecord";
 	function T(){
 	}
 	var r1 = {};

+ 1 - 0
test/expected/errorsRT/cast.txt

@@ -0,0 +1 @@
+Error: cannot cast to 'm2.T' from 'm1.T'

+ 1 - 0
test/expected/errorsRT/cast_in_proc_scope.txt

@@ -0,0 +1 @@
+Error: cannot cast to 'm2.p1.T' from 'm1.T'

+ 6 - 2
test/expected/nodejs/modules/m1.js

@@ -1,22 +1,26 @@
 var RTL$ = require("test_rtl.js");
+var $scope = "m1";
 var ci = 123;
 function Base(){
 	this.i = 0;
 }
+Base.prototype.$scope = $scope;
 function T(){
 	Base.call(this);
 }
-RTL$.extend(T, Base);
+RTL$.extend(T, Base, $scope);
 function TPA(){
 }
+TPA.prototype.$scope = $scope;
 function ExportPointerOnly(){
 	Base.call(this);
 }
-RTL$.extend(ExportPointerOnly, Base);
+RTL$.extend(ExportPointerOnly, Base, $scope);
 var i = 0;
 function anonymous$1(){
 	this.i = 0;
 }
+anonymous$1.prototype.$scope = $scope;
 var pr = null;
 var pr2 = null;
 

+ 22 - 1
test/expected/proc.js

@@ -4,6 +4,7 @@ var i = 0;
 var byte = 0;
 
 function p1(arg1/*INTEGER*/){
+	var $scope1 = $scope + ".p1";
 	function T1(){
 		this.field1 = 0;
 	}
@@ -11,7 +12,7 @@ function p1(arg1/*INTEGER*/){
 		T1.call(this);
 		this.field2 = false;
 	}
-	RTL$.extend(T2, T1);
+	RTL$.extend(T2, T1, $scope1);
 	var i = 0;var j = 0;
 	var b = false;
 	var t1 = new T1();
@@ -69,6 +70,26 @@ function withByteResult4(){
 	b = 0 & 0xFF;
 	return b;
 }
+
+function inner1(){
+	var $scope1 = $scope + ".inner1";
+	
+	function inner2(){
+		var $scope2 = $scope1 + ".inner2";
+		
+		function inner3(){
+			var $scope3 = $scope2 + ".inner3";
+			function T(){
+			}
+		}
+	}
+	
+	function inner22(){
+		var $scope2 = $scope1 + ".inner22";
+		function T(){
+		}
+	}
+}
 byte = withByteResult();
 i = withByteResult();
 withByteArgument(byte);

+ 2 - 0
test/expected/proc_local.js

@@ -1,6 +1,7 @@
 var m = function (){
 
 function p1(arg1/*INTEGER*/){
+	var $scope1 = $scope + ".p1";
 	function T1(){
 		this.field1 = 0;
 	}
@@ -8,6 +9,7 @@ function p1(arg1/*INTEGER*/){
 	var t1 = new T1();
 	
 	function p2(arg2/*BOOLEAN*/){
+		var $scope2 = $scope1 + ".p2";
 		function T2(){
 			this.field2 = false;
 		}

+ 19 - 0
test/input/errorsRT/cast.ob

@@ -0,0 +1,19 @@
+MODULE m1;
+TYPE
+    T* = RECORD
+    END;
+
+END m1.
+
+MODULE m2;
+IMPORT m1;
+TYPE
+    T = RECORD(m1.T)
+    END;
+    PT = POINTER TO T;
+VAR
+    p: POINTER TO m1.T;
+BEGIN
+    NEW(p);
+    ASSERT(p(PT) # NIL);
+END m2.

+ 24 - 0
test/input/errorsRT/cast_in_proc_scope.ob

@@ -0,0 +1,24 @@
+MODULE m1;
+TYPE
+    T* = RECORD
+    END;
+
+END m1.
+
+MODULE m2;
+IMPORT m1;
+PROCEDURE p1();
+    TYPE
+        T = RECORD(m1.T)
+        END;
+        PT = POINTER TO T;
+    VAR
+        p: POINTER TO m1.T;
+BEGIN
+    NEW(p);
+    ASSERT(p(PT) # NIL);
+END p1;
+
+BEGIN
+    p1();
+END m2.

+ 12 - 0
test/input/proc.ob

@@ -73,6 +73,18 @@ BEGIN
 	RETURN b
 END withByteResult4;
 
+PROCEDURE inner1();
+    PROCEDURE inner2();
+        PROCEDURE inner3();
+            TYPE T = RECORD END;
+        END inner3;
+    END inner2;
+    
+    PROCEDURE inner22();
+        TYPE T = RECORD END;
+    END inner22;
+END inner1;
+
 BEGIN
 	byte := withByteResult();
 	i := withByteResult();

+ 31 - 1
test/test_compile.js

@@ -13,6 +13,11 @@ function normalizeLineEndings(text){
                .replace(/\s+$/,''); // ending spaces
 }
 
+function filterOutScopes(text){
+    return text.replace(/.*\$scope =.+\n/g, "")
+               .replace(/, \$scope\)/g, ")");
+}
+
 function filterOutRtlCode(text){
     var prefix = "var RTL$ = {";
     if (text.substr(0, prefix.length) != prefix)
@@ -27,6 +32,7 @@ function filterOutRtlCode(text){
 
 function compareResults(result, name, dirs){
     result = filterOutRtlCode(result);
+    result = filterOutScopes(result);
     fs.writeFileSync(path.join(dirs.output, name), result);
     var expected = fs.readFileSync(path.join(dirs.expected, name), "utf8");
     if (normalizeLineEndings(result) != normalizeLineEndings(expected))
@@ -88,6 +94,20 @@ function run(src, dirs, language){
     require(resultPath);
 }
 
+function expectRuntimeError(src, dirs, language){
+    var error = "";
+    try {
+        run(src, dirs, language);
+    }
+    catch (x){
+        error += x;
+    }
+    if (!error.length)
+        throw new Test.TestError("runtime error expected");
+    var resultName = path.basename(src).replace(".ob", ".txt");
+    compareResults(error, resultName, dirs);
+}
+
 function makeTest(test, src, dirs, grammar){
     return function(){test(src, dirs, grammar);};
 }
@@ -96,7 +116,7 @@ function makeTests(test, dirs, grammar){
     var output = dirs.output;
     if (fs.existsSync(output))
         rmTree(output);
-    fs.mkdirSync(output);
+    mkTree(output);
 
     var sources = fs.readdirSync(dirs.input);
     var tests = {};
@@ -109,6 +129,14 @@ function makeTests(test, dirs, grammar){
     return tests;
 }
 
+function mkTree(p){
+    if (fs.existsSync(p))
+        return;
+
+    mkTree(path.dirname(p));
+    fs.mkdirSync(p);
+}
+
 function rmTree(root){
     fs.readdirSync(root).forEach(function(file){
         var filePath = path.join(root, file);
@@ -156,6 +184,7 @@ function outputSubdir(dirs, subdir){
 function main(){
     var okDirs = makeTestDirs();
     var errDirs = makeTestDirs("errors");
+    var errRuntimeDirs = makeTestDirs("errorsRT");
     var runDirs = makeTestDirs("run");
     var nodejsDirs = makeTestDirs("nodejs");
     var oberonDirs = makeTestDirs("oberon");
@@ -167,6 +196,7 @@ function main(){
         return {
             "expect OK": makeTests(expectOk, outputSubdir(okDirs, subdir), language),
             "expect compile error": makeTests(expectError, outputSubdir(errDirs, subdir), language),
+            "expect runtime error": makeTests(expectRuntimeError, outputSubdir(errRuntimeDirs, subdir), language),
             "run": makeTests(run, outputSubdir(runDirs, subdir), language),
             "nodejs": makeTests(compileNodejs, outputSubdir(nodejsDirs, subdir), language)
         };

+ 5 - 1
test/test_unit_common.js

@@ -3,6 +3,7 @@
 var Class = require("rtl.js").Class;
 var Code = require("js/Code.js");
 var ContextHierarchy = require("js/ContextHierarchy.js");
+var ContextType = require("js/ContextType.js");
 var Errors = require("js/Errors.js");
 var oc = require("oc.js");
 var makeRTL = require("rtl_code.js").makeRTL;
@@ -40,7 +41,10 @@ var TestContext = Class.extend.call(ContextHierarchy.Root, {
         this.pushScope(new Scope.Module("test", language.stdSymbols));
     },
     qualifyScope: function(){return "";},
-    handleMessage: function(){},
+    handleMessage: function(msg){
+        if (msg instanceof ContextType.DescribeScopeMsg)
+            msg.result = new ContextType.ScopeInfo("test", 0);
+    },
     handleExpression: function(){},
     handleLiteral: function(){}
 });

+ 1 - 33
test/test_unit_eberon.js

@@ -77,39 +77,7 @@ var TestVar = Class.extend({
     type: function(){return this.__type;},
     setType: function(type){this.__type = type;}
 });
-/*
-function makeCodeSuite(){
-    return {
-        "insertion": pass(
-            function(){
-                var g = EberonCodeGenerator.makeGenerator();
-                g.write("a");
-                var i = g.makeInsertion();
-                g.write("b");
-                g.insert(i, "c");
-                assert(g.result() == "acb");
-            },
-            function(){
-                var g = EberonCodeGenerator.makeGenerator();
-                g.write("ab");
-                var i1 = g.makeInsertion();
-                var i2 = g.makeInsertion();
-                g.write("cd");
-                g.insert(i1, "123");
-                g.insert(i2, "345");
-                assert(g.result() == "ab123345cd");
-            },
-            function(){
-                var g = EberonCodeGenerator.makeGenerator();
-                g.write("ab");
-                var i = g.makeInsertion();
-                g.write("cd");
-                assert(g.result() == "abcd");
-            }
-        )
-    };
-}
-*/
+
 exports.suite = {
 //"code": makeCodeSuite(),
 "arithmetic operators": testWithContext(