Bladeren bron

bug fixes

Vladislav Folts 12 jaren geleden
bovenliggende
commit
fb267ff8c1
9 gewijzigde bestanden met toevoegingen van 300 en 79 verwijderingen
  1. 4 4
      src/cast.js
  2. 57 26
      src/context.js
  3. 2 2
      src/operator.js
  4. 33 29
      src/procedure.js
  5. 14 4
      src/scope.js
  6. 14 6
      src/type.js
  7. 81 0
      test/expected/man_or_boy.js
  8. 77 0
      test/input/man_or_boy.ob
  9. 18 8
      test/test_unit.js

+ 4 - 4
src/cast.js

@@ -37,8 +37,8 @@ function areTypesMatch(t1, t2){
 }
 
 function areProceduresMatch(p1, p2){
-    var args1 = p1.arguments();
-    var args2 = p2.arguments();
+    var args1 = p1.args();
+    var args2 = p2.args();
     if (args1.length != args2.length)
         return false;
 
@@ -64,12 +64,12 @@ function implicitCast(from, to){
         return doNoting;
 
     if (from instanceof Type.String){
-        if (to === Type.basic.char){
+        if (to === Type.basic.ch){
             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)
+        else if (to instanceof Type.Array && to.elementsType() == Type.basic.ch)
             return function(context, e){
                 return new Code.Expression(context.rtl().strToArray(e.code()), to);
             };

+ 57 - 26
src/context.js

@@ -59,7 +59,7 @@ function checkImplicitCast(from, to){
 
 function promoteTypeInExpression(e, type){
     var fromType = e.type();
-    if (type == Type.basic.char && fromType instanceof Type.String){
+    if (type == Type.basic.ch && fromType instanceof Type.String){
         var v = fromType.asChar();
         if (v !== undefined)
             return new Code.Expression(v, type);
@@ -121,7 +121,7 @@ exports.Integer = ChainedContext.extend({
     toInt: function(s){return parseInt(this.__result, 10);},
     endParse: function(){
         var n = this.toInt();
-        this.parent().handleConst(basicTypes.int, n, n.toString());
+        this.parent().handleConst(basicTypes.integer, n, n.toString());
     }
 });
 
@@ -293,7 +293,7 @@ exports.Designator = ChainedContext.extend({
     __handleIndexExpression: function(){
         var e = this.__indexExpression;
         var expType = e.type();
-        if (expType != basicTypes.int)
+        if (expType != basicTypes.integer)
             throw new Errors.Error("'INTEGER' expression expected, got '" + expType.name() + "'");
 
         var type = this.__currentType;
@@ -621,7 +621,7 @@ exports.ArrayDimensions = ChainedContext.extend({
     codeGenerator: function(){return Code.nullGenerator;},
     handleExpression: function(e){
         var type = e.type();
-        if (type !== basicTypes.int)
+        if (type !== basicTypes.integer)
             throw new Errors.Error("'INTEGER' constant expression expected, got '" + type.name() + "'");
         var value = e.constValue();
         if (value === undefined)
@@ -642,22 +642,22 @@ var numericOpTypeCheck = {
 
 var intOpTypeCheck = {
     expect: "INTEGER",
-    check: function(t){return t == basicTypes.int;}
+    check: function(t){return t == basicTypes.integer;}
 };
 
 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);
+        return [basicTypes.integer, basicTypes.real, basicTypes.ch].indexOf(t) != -1
+            || (t instanceof Type.Array && t.elementsType() == basicTypes.ch);
     }
 };
 
 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)
+        return [basicTypes.integer, basicTypes.real, basicTypes.set, basicTypes.bool, basicTypes.ch].indexOf(t) != -1
+            || (t instanceof Type.Array && t.elementsType() == basicTypes.ch)
             || t instanceof Type.Pointer
             || t instanceof Type.Procedure
             || t == Type.nil;
@@ -776,7 +776,7 @@ exports.MulOperator = ChainedContext.extend({
         else if (s == "/"){
             if (type == basicTypes.set)
                 o = op.setSymmetricDiff;
-            else if (type == basicTypes.int)
+            else if (type == basicTypes.integer)
                 throw new Errors.Error("operator DIV expected for integer division");
             else
                 o = assertNumericOp(type, s, op.divFloat);
@@ -994,7 +994,7 @@ exports.Expression = ChainedContext.extend({
         var code;
 
         if (this.__relation == "IN"){
-            if (leftType != basicTypes.int)
+            if (leftType != basicTypes.integer)
                 throw new Errors.Error("'INTEGER' expected as an element of SET, got '" + leftType.name() + "'");
             checkImplicitCast(rightType, basicTypes.set);
 
@@ -1108,10 +1108,10 @@ exports.Case = ChainedContext.extend({
             var v = type.asChar();
             if (v !== undefined){
                 gen.write(v);
-                type = basicTypes.char;
+                type = basicTypes.ch;
             }
         }
-        if (type != basicTypes.int && type != basicTypes.char)
+        if (type != basicTypes.integer && type != basicTypes.ch)
             throw new Errors.Error("'INTEGER' or 'CHAR' expected as CASE expression");
         this.__type = type;
         gen.write(";\n");
@@ -1186,7 +1186,7 @@ exports.CaseRange = ChainedContext.extend({
             value = type.asChar();
             if (value === undefined)
                 throw new Errors.Error("single-character string expected");
-            type = basicTypes.char;
+            type = basicTypes.ch;
         }
         this.handleLabel(type, value);
     },
@@ -1259,7 +1259,7 @@ exports.For = ChainedContext.extend({
         var s = getSymbol(this.parent(), id);
         if (!s.isVariable())
             throw new Errors.Error("'" + s.id() + "' is not a variable");
-        if (s.info().type() !== basicTypes.int)
+        if (s.info().type() !== basicTypes.integer)
             throw new Errors.Error(
                 "'" + s.id() + "' is a 'BOOLEAN' variable, 'FOR' control variable must be 'INTEGER'");
         this.codeGenerator().write("for (" + id + " = ");
@@ -1268,7 +1268,7 @@ exports.For = ChainedContext.extend({
     handleExpression: function(e){
         var type = e.type();
         var value = e.constValue();
-        if (type !== basicTypes.int)
+        if (type !== basicTypes.integer)
             throw new Errors.Error(
                 !this.__initExprParsed
                     ? "'INTEGER' expression expected to assign '" + this.__var
@@ -1352,7 +1352,7 @@ exports.ConstDecl = ChainedContext.extend({
     }
 });
 
-function checkIfFFieldCanBeExported(name, idents, hint){
+function checkIfFieldCanBeExported(name, idents, hint){
     for(var i = 0; i < idents.length; ++i){
         var id = idents[i];
         if (!id.exported())
@@ -1370,7 +1370,7 @@ exports.VariableDeclaration = ChainedContext.extend({
     },
     handleIdentef: function(id){this.__idents.push(id);},
     exportField: function(name){
-        checkIfFFieldCanBeExported(name, this.__idents, "variable");
+        checkIfFieldCanBeExported(name, this.__idents, "variable");
     },
     setType: function(type){this.__type = type;},
     typeName: function(){return undefined;},
@@ -1403,7 +1403,7 @@ exports.FieldListDeclaration = ChainedContext.extend({
     typeName: function(){return undefined;},
     handleIdentef: function(id) {this.__idents.push(id);},
     exportField: function(name){
-        checkIfFFieldCanBeExported(name, this.__idents, "field");
+        checkIfFieldCanBeExported(name, this.__idents, "field");
     },
     setType: function(type) {this.__type = type;},
     endParse: function(){
@@ -1495,20 +1495,46 @@ exports.ExpressionProcedureCall = ProcedureCall.extend({
     }
 });
 
+function isTypeRecursive(type, base){
+    if (type == base)
+        return true;
+    if (type instanceof Type.Record){
+        if (isTypeRecursive(type.baseType(), base))
+            return true;
+        var fields = type.ownFields();
+        for(var fieldName in fields){
+            if (isTypeRecursive(fields[fieldName], base))
+                return true;
+        }
+    }
+    else if (type instanceof Type.Array)
+        return isTypeRecursive(type.elementsType(), base);
+    return false;
+}
+
 exports.RecordDecl = ChainedContext.extend({
     init: function RecordDeclContext(context){
         ChainedContext.prototype.init.bind(this)(context);
         var id = this.genTypeName();
         this.__type = new Type.Record(id);
+        this.parent().setType(this.__type);
         var gen = this.codeGenerator();
         gen.write("var " + id + " = ");
     },
     addField: function(field, type){
+        if (isTypeRecursive(type, this.__type))
+            throw new Errors.Error("recursive field definition: '"
+                + field.id() + "'");
         this.__type.addField(field, type);
         if (field.exported())
             this.parent().exportField(field.id());
     },
-    setBaseType: function(type){this.__type.setBaseType(type);},
+    setBaseType: function(type){
+        if (isTypeRecursive(type, this.__type))
+            throw new Errors.Error("recursive inheritance: '"
+                + this.__type.name() + "'");
+        this.__type.setBaseType(type);
+    },
     endParse: function(){
         var type = this.__type;
         var baseType = type.baseType();
@@ -1523,7 +1549,6 @@ exports.RecordDecl = ChainedContext.extend({
         for(var f in ownFields)
             gen.write("this." + f + " = " + ownFields[f].initializer() + ";\n");
 
-        this.parent().setType(type);
         gen.closeScope();
         gen.closeScope(");\n");
     }
@@ -1533,18 +1558,24 @@ exports.TypeDeclaration = ChainedContext.extend({
     init: function TypeDeclarationContext(context){
         ChainedContext.prototype.init.bind(this)(context);
         this.__id = undefined;
+        this.__typeId = undefined;
+        this.__symbol = undefined;
+    },
+    handleIdentef: function(id){
+        this.__id = id;
+        this.__typeId = new Type.LazyTypeId();
+        this.__symbol = new Symbol.Symbol(this.__id.id(), this.__typeId);
+        this.currentScope().addSymbol(this.__symbol, this.__id.exported());
     },
-    handleIdentef: function(id){this.__id = id;},
     setType: function(type){
-        this.currentScope().addSymbol(
-            new Symbol.Symbol(this.__id.id(), new Type.TypeId(type)),
-            this.__id.exported());
+        this.__typeId.define(type);
+        this.currentScope().resolve(this.__symbol);
     },
     typeName: function(){return this.__id.id();},
     genTypeName: function(){return this.__id.id();},
     type: function(){return this.parent().type();},
     exportField: function(name){
-        checkIfFFieldCanBeExported(name, [this.__id], "record");
+        checkIfFieldCanBeExported(name, [this.__id], "record");
     }
 });
 

+ 2 - 2
src/operator.js

@@ -60,7 +60,7 @@ function pow2(e){
 
 function log2(e){
     return new Code.Expression("(Math.log(" + e.deref().code() + ") / Math.LN2) | 0",
-                               Type.basic.int, undefined, undefined, precedence.bitOr);
+                               Type.basic.integer, undefined, undefined, precedence.bitOr);
 }
 
 function assign(left, right, context){
@@ -75,7 +75,7 @@ function assign(left, right, context){
 
     var isArray = leftType instanceof Type.Array;
     if (isArray
-        && leftType.elementsType() == Type.basic.char
+        && leftType.elementsType() == Type.basic.ch
         && rightType instanceof Type.String){
         if (leftType.length() === undefined)
             throw new Errors.Error("string cannot be assigned to open " + leftType.description());

+ 33 - 29
src/procedure.js

@@ -43,7 +43,7 @@ var ProcCallGenerator = Class.extend({
         var isVarArg = false;
         var convert;
         if (this.__type){
-            var expectedArguments = this.__type.arguments();
+            var expectedArguments = this.__type.args();
             if (pos >= expectedArguments.length )
                 // ignore, handle error after parsing all arguments
                 return;
@@ -73,13 +73,13 @@ var ProcCallGenerator = Class.extend({
     resultType: function(){return this.__type ? this.__type.result() : undefined;},
     prolog: function(){return this.__id + "(";},
     checkArgumentsCount: function(count){
-        var procArgs = this.__type.arguments();
+        var procArgs = this.__type.args();
         if (count != procArgs.length)
             throw new Errors.Error(procArgs.length + " argument(s) expected, got "
                                  + this.__argumentsCount);
     },
     checkArgument: function(pos, e){
-        var arg = this.__type.arguments()[pos];
+        var arg = this.__type.args()[pos];
         var castOperation;
         var expectType = arg.type; // can be undefined for predefined functions (like NEW), dont check it in this case
         if (expectType){
@@ -115,7 +115,7 @@ var DefinedProc = Type.Procedure.extend({
         this.__arguments = args;
         this.__result = result;
     },
-    arguments: function(){return this.__arguments;},
+    args: function(){return this.__arguments;},
     result: function(){return this.__result;},
     description: function(){
         var name = this.name();
@@ -150,7 +150,7 @@ var Std = Type.Procedure.extend({
         this.__callGeneratorFactory = callGeneratorFactory;
     },
     description: function(){return "standard procedure " + this.name();},
-    arguments: function(){return this.__arguments;},
+    args: function(){return this.__arguments;},
     result: function(){return this.__result;},
     callGenerator: function(context, id){
         return this.__callGeneratorFactory(context, id, this);
@@ -185,7 +185,7 @@ var TwoArgToOperatorProcCallGenerator = ExpCallGenerator.extend({
 
 function setBitImpl(name, op){
     var args = [new Arg(Type.basic.set, true),
-                new Arg(Type.basic.int, false)];
+                new Arg(Type.basic.integer, false)];
     function operator(x, y){
         var value = y.constValue();
         if (value === undefined || value < 0 || value > 31)
@@ -208,7 +208,8 @@ function setBitImpl(name, op){
 }
 
 function incImpl(name, unary, op){
-    var args = [new Arg(Type.basic.int, true), new Arg(Type.basic.int, false)];
+    var args = [new Arg(Type.basic.integer, true),
+                new Arg(Type.basic.integer, false)];
     function operator(x, y){
         if (!y)
             return unary + x.code();
@@ -250,13 +251,13 @@ function bitShiftImpl(name, op){
             return op(args[0], args[1]);
         }
     });
-    var args = [new Arg(Type.basic.int, false),
-                new Arg(Type.basic.int, false)
+    var args = [new Arg(Type.basic.integer, false),
+                new Arg(Type.basic.integer, false)
                ];
     var proc = new Std(
         name,
         args,
-        Type.basic.int,
+        Type.basic.integer,
         function(context, id, type){
             return new CallGenerator(context, id, type);
         });
@@ -335,7 +336,7 @@ exports.predefined = [
                     return new CheckArgumentResult(type, false);
                 
                 // should throw error
-                ProcCallGenerator.prototype.checkArgument.call(this, pos, e);
+                return ProcCallGenerator.prototype.checkArgument.call(this, pos, e);
             },
             epilog: function(){return ".length";}
         });
@@ -345,7 +346,7 @@ exports.predefined = [
         var type = new Std(
             "LEN",
             args,
-            Type.basic.int,
+            Type.basic.integer,
             function(context, id, type){
                 return new LenProcCallGenerator(context, id, type);
             });
@@ -364,7 +365,7 @@ exports.predefined = [
             }
         });
         var name = "ODD";
-        var args = [new Arg(Type.basic.int, false)];
+        var args = [new Arg(Type.basic.integer, false)];
         var type = new Std(
             "ODD",
             args,
@@ -386,7 +387,7 @@ exports.predefined = [
             }
         });
 
-        var args = [new Arg(Type.basic.bool), new Arg(Type.basic.int)];
+        var args = [new Arg(Type.basic.bool), new Arg(Type.basic.integer)];
         var proc = new Std(
             "ASSERT",
             args,
@@ -434,13 +435,13 @@ exports.predefined = [
             init: function FloorProcCallGenerator(context, id, type){
                 ProcCallGenerator.prototype.init.call(this, context, id, type);
             },
-            prolog: function(){return "Math.floor(";},
+            prolog: function(){return "Math.floor(";}
         });
         var args = [new Arg(Type.basic.real, false)];
         var proc = new Std(
             "FLOOR",
             args,
-            Type.basic.int,
+            Type.basic.integer,
             function(context, id, type){
                 return new CallGenerator(context, id, type);
             });
@@ -457,7 +458,7 @@ exports.predefined = [
                 return new Code.Expression(e.code(), Type.basic.real, undefined, e.constValue(), e.maxPrecedence());
             }
         });
-        var args = [new Arg(Type.basic.int, false)];
+        var args = [new Arg(Type.basic.integer, false)];
         var proc = new Std(
             "FLT",
             args,
@@ -483,26 +484,29 @@ exports.predefined = [
             epilog: function(){return "";},
             checkArgument: function(pos, e){
                 var type = e.type();
-                if (type == Type.basic.char || type == Type.basic.set)
-                    this.__callExpression = new Code.Expression(e.code(), Type.basic.int, undefined, e.constValue());
+                if (type == Type.basic.ch || type == Type.basic.set)
+                    this.__callExpression = new Code.Expression(
+                        e.code(), Type.basic.integer, undefined, e.constValue());
                 else if (type == Type.basic.bool){
                     var code = Code.adjustPrecedence(e, precedence.conditional) + " ? 1 : 0";
                     var value = e.constValue();
                     if (value !== undefined)
                         value = value ? 1 : 0;
-                    this.__callExpression = new Code.Expression(code, Type.basic.int, undefined, value, precedence.conditional);
+                    this.__callExpression = new Code.Expression(
+                        code, Type.basic.integer, undefined, value, precedence.conditional);
                 }
                 else if (type instanceof Type.String){
                     var ch = type.asChar();
                     if (ch !== undefined)
-                        this.__callExpression = new Code.Expression("" + ch, Type.basic.int);
+                        this.__callExpression = new Code.Expression(
+                            "" + ch, Type.basic.integer);
                 }
                 
                 if (this.__callExpression)
                     return new CheckArgumentResult(type, false);
 
                 // should throw error
-                ProcCallGenerator.prototype.checkArgument.call(this, pos, e);
+                return ProcCallGenerator.prototype.checkArgument.call(this, pos, e);
             },
             callExpression: function(){return this.__callExpression;}
         });
@@ -512,7 +516,7 @@ exports.predefined = [
         var type = new Std(
             name,
             args,
-            Type.basic.int,
+            Type.basic.integer,
             function(context, id, type){
                 return new CallGenerator(context, id, type);
             });
@@ -525,14 +529,14 @@ exports.predefined = [
                 ExpCallGenerator.prototype.init.call(this, context, id, type);
             },
             callExpression: function(){
-                return new Code.Expression(this.args()[0].code(), Type.basic.char);
+                return new Code.Expression(this.args()[0].code(), Type.basic.ch);
             }
         });
         var name = "CHR";
         var type = new Std(
             name,
-            [new Arg(Type.basic.int, false)],
-            Type.basic.char,
+            [new Arg(Type.basic.integer, false)],
+            Type.basic.ch,
             function(context, id, type){
                 return new CallGenerator(context, id, type);
             });
@@ -553,7 +557,7 @@ exports.predefined = [
         var type = new Std(
             name,
             [new Arg(undefined, false),
-             new Arg(new Type.Array("ARRAY OF CHAR", undefined, Type.basic.char), true)],
+             new Arg(new Type.Array("ARRAY OF CHAR", undefined, Type.basic.ch), true)],
             undefined,
             function(context, id, type){
                 return new CallGenerator(context, id, type);
@@ -563,7 +567,7 @@ exports.predefined = [
     }(),
     function(){
         var args = [new Arg(Type.basic.real, true),
-                    new Arg(Type.basic.int, false)];
+                    new Arg(Type.basic.integer, false)];
         function operator(x, y){
             return op.mulInplace(x, op.pow2(y));
         }
@@ -581,7 +585,7 @@ exports.predefined = [
     }(),
     function(){
         var args = [new Arg(Type.basic.real, true),
-                    new Arg(Type.basic.int, true)];
+                    new Arg(Type.basic.integer, true)];
         function operator(x, y){
             return op.assign(y, op.log2(x)) +
                    "; " +

+ 14 - 4
src/scope.js

@@ -36,11 +36,14 @@ var Scope = Class.extend({
         if (this.findSymbol(id))
             throw new Errors.Error( "'" + id + "' already declared");
         this.__symbols[id] = symbol;
-
+    },
+    resolve: function(symbol){
+        var id = symbol.id();
         var i = this.__unresolved.indexOf(id);
         if (i != -1){
             var info = symbol.info();
-            if (!(info.type() instanceof Type.Record))
+            var type = info.type();
+            if (type !== undefined && !(type instanceof Type.Record))
                 throw new Errors.Error(
                     "'" + id + "' must be of RECORD type because it was used before in the declation of POINTER");
             this.__unresolved.splice(i, 1);
@@ -70,10 +73,17 @@ var Module = Scope.extend({
     },
     addSymbol: function(symbol, exported){
         if (exported)
-            if (!symbol.isType() || symbol.info().type() instanceof Type.Record)
-                this.__exports.push(symbol);
+            this.__exports.push(symbol);
         Scope.prototype.addSymbol.call(this, symbol, exported);
     },
+    resolve: function(symbol){
+        var i = this.__exports.indexOf(symbol);
+        if (i != -1)
+            // remove non-record types from generated exports
+            if (symbol.isType() && !(symbol.info().type() instanceof Type.Record))
+                this.__exports.splice(i, 1);
+        Scope.prototype.resolve.call(this, symbol);
+    },
     exports: function(){return this.__exports;}
 });
 

+ 14 - 6
src/type.js

@@ -16,10 +16,17 @@ var Type = Id.extend({
 var TypeId = Id.extend({
 	init: function TypeId(type){
 		Id.prototype.init.call(this);
-		this.__type = type;
+		this._type = type;
 	},
-	type: function(){return this.__type;},
-	description: function(){return 'type ' + this.__type.description();}
+	type: function(){return this._type;},
+	description: function(){return 'type ' + this._type.description();}
+});
+
+var LazyTypeId = TypeId.extend({
+	init: function LazyTypeId(){
+		TypeId.prototype.init.call(this);
+	},
+	define: function(type){return this._type = type;}
 });
 
 exports.String = Type.extend({
@@ -123,13 +130,13 @@ var NilType = Type.extend({
 
 var basic = {
 	bool: new BasicType("BOOLEAN", false),
-	char: new BasicType("CHAR", 0),
-	int: new BasicType("INTEGER", 0),
+	ch: new BasicType("CHAR", 0),
+	integer: new BasicType("INTEGER", 0),
 	real: new BasicType("REAL", 0),
 	set: new BasicType("SET", 0)
 };
 exports.basic = basic;
-exports.numeric = [basic.int, basic.real];
+exports.numeric = [basic.integer, basic.real];
 
 exports.nil = new NilType();
 
@@ -173,3 +180,4 @@ var Module = Id.extend({
 exports.Module = Module;
 exports.Type = Type;
 exports.TypeId = TypeId;
+exports.LazyTypeId = LazyTypeId;

+ 81 - 0
test/expected/man_or_boy.js

@@ -0,0 +1,81 @@
+var RTL$ = {
+    extend: function extend(methods){
+        methods.__proto__ = this.prototype; // make instanceof work
+
+        // to see constructor name in diagnostic
+        var result = methods.init;
+        methods.constructor = result.prototype.constructor;
+
+        result.prototype = methods;
+        result.extend = extend;
+        return result;
+    }
+};
+var JS = function(){return this;}();
+var test = function (){
+var State = RTL$.extend({
+	init: function State(){
+		this.f = null;
+		this.k = 0;
+		this.x1 = null;
+		this.x2 = null;
+		this.x3 = null;
+		this.x4 = null;
+		this.x5 = null;
+	}
+});
+var pB = null;
+
+function call(s/*PState*/){
+	return s.f(s);
+}
+
+function makeEmptyState(f/*Function*/){
+	var result = null;
+	result = new State();
+	result.f = f;
+	return result;
+}
+
+function makeState(f/*Function*/, k/*INTEGER*/, x1/*PState*/, x2/*PState*/, x3/*PState*/, x4/*PState*/, x5/*PState*/){
+	var result = null;
+	result = makeEmptyState(f);
+	result.k = k;
+	result.x1 = x1;
+	result.x2 = x2;
+	result.x3 = x3;
+	result.x4 = x4;
+	result.x5 = x5;
+	return result;
+}
+
+function F0(s/*PState*/){
+	return 0;
+}
+
+function F1(s/*PState*/){
+	return 1;
+}
+
+function Fn1(s/*PState*/){
+	return -1;
+}
+
+function A(s/*PState*/){
+	var res = 0;
+	if (s.k <= 0){
+		res = call(s.x4) + call(s.x5);
+	}
+	else {
+		res = call(makeState(pB, s.k, s.x1, s.x2, s.x3, s.x4, s.x5));
+	}
+	return res;
+}
+
+function B(s/*PState*/){
+	--s.k;
+	return call(makeState(A, s.k, s, s.x1, s.x2, s.x3, s.x4));
+}
+pB = B;
+JS.alert(call(makeState(A, 10, makeEmptyState(F1), makeEmptyState(Fn1), makeEmptyState(Fn1), makeEmptyState(F1), makeEmptyState(F0))));
+}();

+ 77 - 0
test/input/man_or_boy.ob

@@ -0,0 +1,77 @@
+(*http://www.chilton-computing.org.uk/acl/applications/algol/p006.htm*)
+
+MODULE test;
+IMPORT JS;
+
+TYPE
+    PState = POINTER TO State;
+    Function = PROCEDURE(s: PState): INTEGER;
+    State = RECORD
+        f: Function;
+        k: INTEGER;
+        x1, x2, x3, x4, x5: PState
+    END;
+
+VAR
+    pB: Function;
+
+PROCEDURE call(s: PState): INTEGER;
+    RETURN s.f(s)
+END call;
+
+PROCEDURE makeEmptyState(f: Function): PState;
+VAR
+    result: PState;
+BEGIN
+    NEW(result);
+    result.f := f;
+    RETURN result
+END makeEmptyState;
+
+PROCEDURE makeState(f: Function; k: INTEGER; x1, x2, x3, x4, x5: PState): PState;
+VAR
+    result: PState;
+BEGIN
+    result := makeEmptyState(f);
+    result.k := k;
+    result.x1 := x1;
+    result.x2 := x2;
+    result.x3 := x3;
+    result.x4 := x4;
+    result.x5 := x5;
+    RETURN result
+END makeState;
+
+PROCEDURE F0(s: PState):  INTEGER; BEGIN RETURN  0 END F0;
+PROCEDURE F1(s: PState):  INTEGER; BEGIN RETURN  1 END F1;
+PROCEDURE Fn1(s: PState): INTEGER; BEGIN RETURN -1 END Fn1;
+
+PROCEDURE A(s: PState): INTEGER;
+VAR
+    res: INTEGER;
+BEGIN
+  IF s.k <= 0 THEN
+    res := call(s.x4) + call(s.x5);
+  ELSE
+    res := call(makeState(pB, s.k, s.x1, s.x2, s.x3, s.x4, s.x5));
+  END;
+  RETURN res
+END A;
+
+PROCEDURE B(s: PState): INTEGER;
+BEGIN
+    DEC(s.k);
+    RETURN call(makeState(A, s.k, s, s.x1, s.x2, s.x3, s.x4))
+END B;
+
+BEGIN
+    pB := B;
+    JS.alert(call(makeState(
+        A,
+        10,
+        makeEmptyState(F1),
+        makeEmptyState(Fn1),
+        makeEmptyState(Fn1),
+        makeEmptyState(F1),
+        makeEmptyState(F0))))
+END test.

+ 18 - 8
test/test_unit.js

@@ -240,14 +240,24 @@ identifier: function(){
     test.expectError("CONST s = {1..v1};", "constant expression expected");
     test.expectError("CONST s = {10 - v1..15};", "constant expression expected");
 },
-"record declaration": function(){
-    var parse = function(s) { return parseUsingGrammar(Grammar.typeDeclaration, s); };
-    assert(parse("t = RECORD END"));
-    assert(parse("t = RECORD i: INTEGER END"));
-    assert(parse("t = RECORD i, j: INTEGER END"));
-    assert(!parse("t = RECORD i, j, i: INTEGER END"));
-    assert(parse("t = RECORD i, j: INTEGER; b: BOOLEAN END"));
-},
+"record declaration": testWithGrammar(
+    Grammar.typeDeclaration,
+    pass("T = RECORD END",
+         "T = RECORD i: INTEGER END",
+         "T = RECORD i, j: INTEGER END",
+         "T = RECORD i, j: INTEGER; b: BOOLEAN END",
+         "T = RECORD p: PROCEDURE(r: T) END",
+         "T = RECORD p: PROCEDURE(): T END"
+         ),
+    fail(["T = RECORD i, j, i: INTEGER END", "duplicated field: 'i'"],
+         ["T = RECORD r: T END", "recursive field definition: 'r'"],
+         ["T = RECORD a: ARRAY 10 OF T END", "recursive field definition: 'a'"],
+         ["T = RECORD a: ARRAY 3 OF ARRAY 5 OF T END", "recursive field definition: 'a'"],
+         ["T = RECORD r: RECORD rr: T END END", "recursive field definition: 'r'"],
+         ["T = RECORD (T) END", "recursive inheritance: 'T'"],
+         ["T = RECORD r: RECORD (T) END END", "recursive field definition: 'r'"]
+         )
+    ),
 "array declaration": function(){
     var test = setupWithContext(
           Grammar.typeDeclaration