Browse Source

Fix special characters (for javascript) escaping in Oberon string literals.

Vladislav Folts 11 years ago
parent
commit
7d72add28f

+ 4 - 0
snapshot/assert.js

@@ -0,0 +1,4 @@
+exports.ok = function(condition){
+	if (!condition)
+		throw new Error("assertion failed");
+}

+ 99 - 0
snapshot/cast.js

@@ -0,0 +1,99 @@
+"use strict";
+var Code = require("code.js");
+var Type = require("type.js");
+var ArrayType = Type.Array;
+var PointerType = Type.Pointer;
+var ProcedureType = Type.Procedure;
+
+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 instanceof ProcedureType;
+}
+
+function areTypesMatch(t1, t2){
+    if (t1 == t2)
+        return true;
+    if (t1 instanceof PointerType && t2 instanceof PointerType)
+        return areTypesMatch(t1.baseType(), t2.baseType());
+    if (t1 instanceof ProcedureType && t2 instanceof ProcedureType)
+        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.args();
+    var args2 = p2.args();
+    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.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.ch)
+            return function(context, e){
+                return new Code.Expression(context.rtl().strToArray(e.code()), to);
+            };
+    }
+    else if (from instanceof ArrayType && to instanceof ArrayType)
+        return (to.length() === undefined || to.length() === from.length())
+            && 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 instanceof ProcedureType && to instanceof ProcedureType){
+        if (areProceduresMatch(from, to))
+            return doNoting;
+    }
+    return undefined;
+}
+
+exports.areTypesMatch = areTypesMatch;
+exports.implicit = implicitCast;
+exports.findPointerBaseType = findPointerBaseType;

+ 148 - 0
snapshot/code.js

@@ -0,0 +1,148 @@
+"use strict";
+
+var Class = require("rtl.js").Class;
+var Type = require("type.js");
+
+var NullCodeGenerator = Class.extend({
+	init: function NullCodeGenerator(){},
+	write: function(){},
+    openScope: function(){},
+    closeScope: function(){},
+    getResult: function(){}
+});
+
+exports.Generator = Class.extend({
+	init: function CodeGenerator(){
+		this.__result = "";
+		this.__indent = "";
+	},
+	write: function(s){
+		this.__result += s.replace(/\n/g, "\n" + this.__indent);
+	},
+	openScope: function(){
+		this.__indent += "\t";
+		this.__result += "{\n" + this.__indent;
+	},
+	closeScope: function(ending){
+		this.__indent = this.__indent.substr(1);
+		this.__result = this.__result.substr(0, this.__result.length - 1) + "}";
+		if (ending)
+			this.write(ending);
+		else
+			this.write("\n");
+	},
+	getResult: function(){return this.__result;}
+});
+
+exports.SimpleGenerator = Class.extend({
+	init: function SimpleCodeGenerator(code){this.__result = code ? code : "";},
+	write: function(s){this.__result += s;},
+	result: function(){return this.__result;}
+});
+
+var Expression = Class.extend({
+	init: function Expression(code, type, designator, constValue, maxPrecedence){
+        this.__code = code;
+        this.__type = type;
+        this.__designator = designator;
+        this.__constValue = constValue;
+        this.__maxPrecedence = maxPrecedence;
+    },
+    code: function(){return this.__code;},
+    type: function(){return this.__type;},
+    designator: function(){return this.__designator;},
+    constValue: function(){return this.__constValue;},
+    maxPrecedence: function(){return this.__maxPrecedence;},
+    isTerm: function(){return !this.__designator && this.__maxPrecedence === undefined;},
+    deref: function(){
+        if (!this.__designator)
+            return this;
+        if (this.__type instanceof Type.Array || this.__type instanceof Type.Record)
+            return this;
+        var info = this.__designator.info();
+        if (!(info instanceof Type.VariableRef))
+            return this;
+        return new Expression(this.__code + ".get()", this.__type);
+    },
+    ref: function(){
+        if (!this.__designator)
+            return this;
+        
+        var info = this.__designator.info();
+        if (info instanceof Type.VariableRef)
+            return this;
+
+        return new Expression(this.__designator.refCode(), this.__type);
+    }
+});
+
+function adjustPrecedence(e, precedence){
+    var code = e.code();
+    if (e.maxPrecedence() > precedence)
+        code = "(" + code + ")";
+    return code;
+}
+
+function genExport(symbol){
+    if (symbol.isVariable())
+        return "function(){return " + symbol.id() + ";}";
+    if (symbol.isType()){
+        var type = symbol.info().type();
+        if (!(type instanceof Type.Record 
+              || (type instanceof Type.Pointer && !type.baseType().name())))
+            return undefined;
+    }
+    return symbol.id();
+}
+
+var ModuleGenerator = Class.extend({
+    init: function Code$ModuleGenerator(name, imports){
+        this.__name = name;
+        this.__imports = imports;
+    },
+    prolog: function(){
+        var result = "";            
+        var modules = this.__imports;
+        for(var name in modules){
+            var alias = modules[name];
+            if (result.length)
+                result += ", ";
+            result += alias;
+        }
+        return "var " + this.__name + " = function " + "(" + result + "){\n";
+    },
+    epilog: function(exports){
+        var result = "";
+        for(var access in exports){
+            var e = exports[access];
+            var code = genExport(e);
+            if (code){
+                if (result.length)
+                    result += ",\n";
+                result += "\t" + e.id() + ": " + code;
+            }
+        }
+        if (result.length)
+            result = "return {\n" + result + "\n}\n";
+        
+        result += "}(";
+
+        var modules = this.__imports;
+        var importList = "";
+        for(var name in modules){
+            if (importList.length)
+                importList += ", ";
+            importList += name;
+        }
+        result += importList;
+        result += ");\n";
+
+        return result;
+    }
+});
+
+exports.nullGenerator = new NullCodeGenerator();
+exports.Expression = Expression;
+exports.adjustPrecedence = adjustPrecedence;
+exports.genExport = genExport;
+exports.ModuleGenerator = ModuleGenerator;

+ 1843 - 0
snapshot/context.js

@@ -0,0 +1,1843 @@
+"use strict";
+
+var Cast = require("cast.js");
+var Code = require("code.js");
+var Errors = require("errors.js");
+var Lexer = require("lexer.js");
+var Module = require("module.js");
+var op = require("operator.js");
+var Parser = require("parser.js");
+var Procedure = require("procedure.js");
+var Class = require("rtl.js").Class;
+var Scope = require("scope.js");
+var Symbol = require("symbol.js");
+var Type = require("type.js");
+
+var basicTypes = Type.basic;
+
+function getSymbolAndScope(context, id){
+    var s = context.findSymbol(id);
+    if (!s)
+        throw new Errors.Error(
+            context instanceof Type.Module
+                ? "identifier '" + id + "' is not exported by module '" + context.name() + "'"
+                : "undeclared identifier: '" + id + "'");
+    return s;
+}
+
+function getSymbol(context, id){
+    return getSymbolAndScope(context, id).symbol();
+}
+
+function unwrapTypeId(type){
+    if (!(type instanceof Type.TypeId))
+        throw new Errors.Error("type name expected");
+    return type;
+}
+
+function unwrapType(type){
+    return unwrapTypeId(type).type();
+}
+
+function getTypeSymbol(context, id){
+    return unwrapType(getSymbol(context, id).info());
+}
+
+function throwTypeMismatch(from, to){
+    throw new Errors.Error("type mismatch: expected '" + to.description() +
+                           "', got '" + from.description() + "'");
+}
+
+function checkTypeMatch(from, to){
+    if (!Cast.areTypesMatch(from, to))
+        throwTypeMismatch(from, to);
+}
+
+function checkImplicitCast(from, to){
+    var result = Cast.implicit(from, to);
+    if (!result)
+        throwTypeMismatch(from, to);
+    return result;
+}
+
+function promoteTypeInExpression(e, type){
+    var fromType = e.type();
+    if (type == Type.basic.ch && fromType instanceof Type.String){
+        var v = fromType.asChar();
+        if (v !== undefined)
+            return new Code.Expression(v, type);
+    }
+    return e;
+}
+
+function promoteExpressionType(context, left, right){
+    var rightType = right.type();
+    if (!left)
+        return right;
+
+    var leftType = left.type();
+    if (rightType === undefined)
+        return right;
+    
+    return checkImplicitCast(rightType, leftType)(context, right);
+}
+
+function checkTypeCast(from, to, msg){
+    if (from instanceof Type.Pointer)
+        from = from.baseType();
+    if (to instanceof Type.Pointer)
+        to = to.baseType();
+
+    var t = to.baseType();
+    while (t && t != from)
+        t = t.baseType();
+    if (!t)
+        throw new Errors.Error(msg + ": '" + to.description()
+                             + "' is not an extension of '" + from.description() + "'");
+}
+
+var ChainedContext = Class.extend({
+    init: function ChainedContext(parent){this.__parent = parent;},
+    parent: function(){return this.__parent;},
+    codeGenerator: function(){return this.__parent.codeGenerator();},
+    findSymbol: function(id){return this.__parent.findSymbol(id);},
+    currentScope: function(s){return this.__parent.currentScope();},
+    pushScope: function(scope){this.__parent.pushScope(scope);},
+    popScope: function(){this.__parent.popScope();},
+    setType: function(type){this.__parent.setType(type);},
+    setDesignator: function(d){this.__parent.setDesignator(d);},
+    handleExpression: function(e){this.__parent.handleExpression(e);},
+    handleLiteral: function(s){this.__parent.handleLiteral(s);},
+    handleConst: function(type, value, code){this.__parent.handleConst(type, value, code);},
+    genTypeName: function(){return this.__parent.genTypeName();},
+    genVarName: function(id){return this.__parent.genVarName(id);},
+    qualifyScope: function(scope){return this.__parent.qualifyScope(scope);},
+    rtl: function(){return this.__parent.rtl();}
+});
+
+exports.Integer = ChainedContext.extend({
+    init: function IntegerContext(context){
+        ChainedContext.prototype.init.call(this, context);
+        this.__result = "";
+        this.__isHex = false;
+    },
+    isLexem: function(){return true;},
+    handleChar: function(c){this.__result += c;},
+    handleLiteral: function(){this.__isHex = true;},
+    toInt: function(s){return parseInt(this.__result, 10);},
+    endParse: function(){
+        var n = this.toInt();
+        this.parent().handleConst(basicTypes.integer, n, n.toString());
+    }
+});
+
+exports.HexInteger = exports.Integer.extend({
+    init: function HexIntegerContext(context){
+        exports.Integer.prototype.init.call(this, context);
+    },
+    toInt: function(s){return parseInt(this.__result, 16);}
+});
+
+exports.Real = ChainedContext.extend({
+    init: function RealContext(context){
+        ChainedContext.prototype.init.call(this, context);
+        this.__result = "";
+    },
+    isLexem: function(){return true;},
+    handleChar: function(c){this.__result += c;},
+    handleLiteral: function(s){
+        if (s == "D") // LONGREAL
+            s = "E";
+        this.__result += s;
+    },
+    endParse: function(){
+        var n = Number(this.__result);
+        this.parent().handleConst(basicTypes.real, n, n.toString());
+    }
+});
+
+function escapeString(s){
+    var result = "\"";
+    for(var i = 0; i < s.length; ++i){
+        var c = s[i];
+        if (c == '"')
+            result += "\\\"";
+        else
+            result += s[i];
+    }
+    return result + "\"";
+}
+
+exports.String = ChainedContext.extend({
+    init: function StringContext(context){
+        ChainedContext.prototype.init.call(this, context);
+        this.__result = undefined;
+    },
+    handleString: function(s){this.__result = s;},
+    toStr: function(s){return s;},
+    endParse: function(){
+        var s = this.toStr(this.__result);
+        this.parent().handleConst(new Type.String(s), s, escapeString(s));
+        }
+});
+
+exports.Char = exports.String.extend({
+    init: function CharContext(context){
+        exports.String.prototype.init.call(this, context);
+        this.__result = "";
+    },
+    handleChar: function(c){this.__result += c;},
+    toStr: function(s){return String.fromCharCode(parseInt(s, 16));}
+});
+
+exports.BaseType = ChainedContext.extend({
+    init: function BaseTypeContext(context){
+        ChainedContext.prototype.init.call(this, context);
+    },
+    handleSymbol: function(s){
+        this.parent().setBaseType(unwrapType(s.symbol().info()));
+    }
+});
+
+var DesignatorInfo = Class.extend({
+    init: function(code, refCode, type, info, scope){
+        this.__code = code;
+        this.__refCode = refCode;
+        this.__type = type;
+        this.__info = info;
+        this.__scope = scope;
+    },
+    code: function(){return this.__code;},
+    refCode: function(){return this.__refCode(this.__code);},
+    type: function(){return this.__type;},
+    info: function(){return this.__info;},
+    scope: function(){return this.__scope;}
+});
+
+exports.QualifiedIdentificator = ChainedContext.extend({
+    init: function QualifiedIdentificator(context){
+        ChainedContext.prototype.init.call(this, context);
+        this.__module = undefined;
+        this.__id = undefined;
+        this.__code = "";
+    },
+    setIdent: function(id){
+        this.__id = id;
+    },
+    handleLiteral: function(){
+        var s = getSymbol(this, this.__id);
+        if (!s.isModule())
+            return false; // stop parsing
+        this.__module = s.info();
+        this.__code = this.__id + ".";
+        return undefined;
+    },
+    endParse: function(){
+        var s = getSymbolAndScope(this.__module ? this.__module : this, this.__id);
+        var code = this.__code + this.__id;
+        if (this.__module && s.symbol().isVariable())
+            code += "()";
+        this.parent().handleSymbol(s, code);
+    }
+});
+
+var Identdef = Class.extend({
+    init: function Identdef(id, exported){
+        this.__id = id;
+        this.__exported = exported;
+    },
+    id: function(){return this.__id;},
+    exported: function(){return this.__exported;}
+});
+
+exports.Identdef = ChainedContext.extend({
+    init: function IdentdefContext(context){
+        ChainedContext.prototype.init.call(this, context);
+        this.__id = undefined;
+        this.__export = false;
+    },
+    setIdent: function(id){this.__id = id;},
+    handleLiteral: function(){this.__export = true;},
+    endParse: function(){
+        this.parent().handleIdentef(new Identdef(this.__id, this.__export));
+    }
+});
+
+exports.Designator = ChainedContext.extend({
+    init: function Context$Designator(context){
+        ChainedContext.prototype.init.call(this, context);
+        this.__currentType = undefined;
+        this.__info = undefined;
+        this.__scope = undefined;
+        this.__code = new Code.SimpleGenerator();
+        this.__indexExpression = undefined;
+        this.__derefCode = undefined;
+        this.__propCode = undefined;
+    },
+    handleSymbol: function(found, code){
+        this.__scope = found.scope();
+        var s = found.symbol();
+        var info = s.info();
+        if (info instanceof Type.Type || s.isType() || s.isProcedure())
+            this.__currentType = info;
+        else if (s.isVariable() || s.isConst())
+            this.__currentType = info.type();
+        this.__info = info;
+        this.__code.write(code);
+    },
+    setIdent: function(id){
+        var t = this.__currentType;
+        var isReadOnly = this.__info instanceof Type.Variable 
+                      && this.__info.isReadOnly();
+        if (t instanceof Type.Pointer){
+            this.__handleDeref();
+            isReadOnly = false;
+        }
+        else if (!(t instanceof Type.Record
+                || t instanceof Type.Module
+                || t instanceof Module.AnyType))
+            throw new Errors.Error("cannot designate '" + t.description() + "'");
+
+        this.__denote(id);
+        this.__info = new Type.Variable(this.__currentType, isReadOnly);
+        this.__scope = undefined;
+    },
+    codeGenerator: function(){return this.__code;},
+    handleExpression: function(e){this.__indexExpression = e;},
+    __handleIndexExpression: function(){
+        var e = this.__indexExpression;
+        var expType = e.type();
+        if (expType != basicTypes.integer)
+            throw new Errors.Error("'INTEGER' expression expected, got '" + expType.description() + "'");
+
+        var type = this.__currentType;
+        if (!(type instanceof Type.Array))
+            throw new Errors.Error("ARRAY expected, got '" + type.description() + "'");
+        var value = e.constValue();
+        if (value !== undefined && value >= type.length())
+            throw new Errors.Error("index out of bounds: maximum possible index is "
+                                 + (type.length() - 1)
+                                 + ", got " + value );
+
+        this.__currentType = type.elementsType();
+        this.__info = new Type.Variable(this.__currentType, this.__info.isReadOnly());
+    },
+    handleLiteral: function(s){
+        if (s == "]" || s == ","){
+            this.__handleIndexExpression();
+            var indexCode = this.__indexExpression.deref().code();
+            this.__propCode = indexCode;
+            this.__code = new Code.SimpleGenerator(this.__derefCode + "[" + indexCode + "]");
+            }
+        if (s == "[" || s == ","){
+            this.__derefCode = this.__code.result();
+            this.__code = new Code.SimpleGenerator();
+        }
+        else if (s == "^"){
+            this.__handleDeref();
+            this.__info = new Type.VariableRef(this.__currentType);
+        }
+    },
+    __handleDeref: function(){
+        if (!(this.__currentType instanceof Type.Pointer))
+            throw new Errors.Error("POINTER TO type expected, got '"
+                                 + this.__currentType.description() + "'");
+        this.__currentType = this.__currentType.baseType();
+        if (this.__currentType instanceof Type.NonExportedRecord)
+            throw new Errors.Error("POINTER TO non-exported RECORD type cannot be dereferenced");
+    },
+    handleTypeCast: function(type){
+        if (this.__currentType instanceof Type.Record){
+            if (!(this.__info instanceof Type.VariableRef))
+                throw new Errors.Error(
+                    "invalid type cast: a value variable and cannot be used in typeguard");
+            if (!(type instanceof Type.Record))
+                throw new Errors.Error(
+                    "invalid type cast: RECORD type expected as an argument of RECORD type guard, got '"
+                  + type.description() + "'");
+        }
+        else if (this.__currentType instanceof Type.Pointer)
+            if (!(type instanceof Type.Pointer))
+                throw new Errors.Error(
+                    "invalid type cast: POINTER type expected as an argument of POINTER type guard, got '"
+                  + type.description() + "'");
+
+        checkTypeCast(this.__currentType, type, "invalid type cast");
+
+        var baseType = type instanceof Type.Pointer ? type.baseType() : type;
+        var castName = this.qualifyScope(baseType.scope()) + baseType.cons();
+        var code = this.rtl().typeGuard(this.__code.result(), castName);
+        this.__code = new Code.SimpleGenerator(code);
+
+        this.__currentType = type;
+    },
+    __denote: function(id){
+        var t = this.__currentType;
+        var fieldType = t.findSymbol(id);
+        if (!fieldType)
+            throw new Errors.Error("Type '" + t.name() + "' has no '" + id + "' field");
+        this.__derefCode = this.__code.result();
+        this.__propCode = "\"" + id + "\"";
+        this.__code.write("." + id);
+        this.__currentType = fieldType;
+    },
+    endParse: function(){
+        var code = this.__code.result();
+        var self = this;
+        var refCode = function(code){return self.__makeRefCode(code);};
+        this.parent().setDesignator(
+            new DesignatorInfo(code, refCode, this.__currentType, this.__info, this.__scope));
+    },
+    __makeRefCode: function(code){
+        if ((this.__currentType instanceof Type.Array)
+            || (this.__currentType instanceof Type.Record)
+            || (this.__info instanceof Type.VariableRef))
+            return code;
+        if (this.__derefCode)
+            return this.rtl().makeRef(this.__derefCode, this.__propCode);
+        return "{set: function($v){" + code + " = $v;}, get: function(){return " + code + ";}}";
+    }
+});
+
+exports.Type = ChainedContext.extend({
+    init: function Context$Type(context){
+        ChainedContext.prototype.init.call(this, context);
+    },
+    handleSymbol: function(s){
+        this.parent().handleSymbol(s);
+    }
+});
+
+var HandleSymbolAsType = ChainedContext.extend({
+    init: function Context$HandleSymbolAsType(context){
+        ChainedContext.prototype.init.call(this, context);
+    },
+    handleSymbol: function(s){
+        this.setType(unwrapType(s.symbol().info()));
+    }
+});
+
+exports.FormalType = HandleSymbolAsType.extend({
+    init: function FormalType(context){
+        HandleSymbolAsType.prototype.init.call(this, context);
+        this.__arrayDimension = 0;
+    },
+    setType: function(type){           
+        for(var i = 0; i < this.__arrayDimension; ++i)
+            type = new Type.Array("ARRAY OF " + type.name()
+                               , undefined
+                               , type);
+        this.parent().setType(type);
+
+    },
+    handleLiteral: function(s){
+        if (s == "ARRAY")
+            ++this.__arrayDimension;
+    }
+});
+
+var ProcArg = Class.extend({
+    init: function(type, isVar){
+        this.type = type;
+        this.isVar = isVar;
+    },
+    description: function(){
+        return (this.isVar ? "VAR " : "") + this.type.description();
+    }
+});
+
+exports.FormalParameters = ChainedContext.extend({
+    init: function FormalParametersContext(context){
+        ChainedContext.prototype.init.call(this, context);
+        this.__arguments = [];
+        this.__result = undefined;
+
+        var parent = this.parent();
+        this.__type = new Procedure.Type(parent.typeName());
+        parent.setType(this.__type);
+    },
+    addArgument: function(name, arg){
+        this.__arguments.push(arg);
+    },
+    handleSymbol: function(s){
+        var resultType = unwrapType(s.symbol().info());
+        if (resultType instanceof Type.Array)
+            throw new Errors.Error("the result type of a procedure cannot be an ARRAY");
+        if (resultType instanceof Type.Record)
+            throw new Errors.Error("the result type of a procedure cannot be a RECORD");
+        this.__result = resultType;
+    },
+    endParse: function(){
+        this.__type.define(this.__arguments, this.__result);
+    }
+});
+
+exports.FormalParametersProcDecl = exports.FormalParameters.extend({
+    init: function FormalParametersProcDeclContext(context){
+        exports.FormalParameters.prototype.init.call(this, context);
+    },
+    addArgument: function(name, arg){
+        exports.FormalParameters.prototype.addArgument.call(this, name, arg);
+        this.parent().addArgument(name, arg);
+    },
+    endParse: function(){
+        exports.FormalParameters.prototype.endParse.call(this);
+        this.parent().endParameters();
+    }
+});
+
+exports.ProcDecl = ChainedContext.extend({
+    init: function ProcDeclContext(context){
+        ChainedContext.prototype.init.call(this, context);
+        this.__id = undefined;
+        this.__firstArgument = true;
+        this.__type = undefined;
+        this.__returnParsed = false;
+        this.__outerScope = this.parent().currentScope();
+    },
+    handleIdentef: function(id){
+        this.__id = id;
+        this.codeGenerator().write("\nfunction " + id.id() + "(");
+        this.parent().pushScope(new Scope.Procedure());
+    },
+    setIdent: function(id){
+        if (this.__id.id() != id)
+            throw new Errors.Error("mismatched procedure names: '" + this.__id.id()
+                                 + "' at the begining and '" + id + "' at the end");
+        this.codeGenerator().closeScope();
+        this.parent().popScope();
+    },
+    typeName: function(){return undefined;},
+    setType: function(type){
+        var procSymbol = new Symbol.Symbol(this.__id.id(), type);
+        this.__outerScope.addSymbol(procSymbol, this.__id.exported());
+        this.__type = type;
+    },
+    addArgument: function(name, arg){
+        if (name == this.__id.id())
+            throw new Errors.Error("argument '" + name + "' has the same name as procedure");
+        var readOnly = !arg.isVar && (arg.type instanceof Type.Array);
+        var v = arg.isVar ? new Type.VariableRef(arg.type)
+                          : new Type.Variable(arg.type, readOnly);
+        var s = new Symbol.Symbol(name, v);
+        this.currentScope().addSymbol(s);
+
+        var code = this.codeGenerator();
+        if (!this.__firstArgument)
+            code.write(", ");
+        else
+            this.__firstArgument = false;
+        code.write(name + "/*" + arg.description() + "*/");
+    },
+    endParameters: function(){
+        var code = this.codeGenerator();
+        code.write(")");
+        code.openScope();
+    },
+    handleReturn: function(type){
+        var result = this.__type.result();
+        if (!result)
+            throw new Errors.Error("unexpected RETURN in PROCEDURE declared with no result type");
+        if (!Cast.implicit(type, result))
+            throw new Errors.Error(
+                "RETURN '" + result.description() + "' expected, got '"
+                + type.description() + "'");
+        this.__returnParsed = true;
+    },
+    endParse: function(){
+        var result = this.__type.result();
+        if (result && !this.__returnParsed)
+            throw new Errors.Error("RETURN expected at the end of PROCEDURE declared with '"
+                                 + result.name() + "' result type");
+    }
+});
+
+exports.Return = ChainedContext.extend({
+    init: function ReturnContext(context){
+        ChainedContext.prototype.init.call(this, context);
+        this.__type = undefined;
+        this.__code = new Code.SimpleGenerator();
+    },
+    codeGenerator: function(){return this.__code;},
+    handleExpression: function(e){
+        this.__type = e.type();
+        var designator = e.designator();
+        if (designator)
+            writeDerefDesignatorCode(designator, this.__code);
+    },
+    endParse: function(){
+        var parent = this.parent();
+        parent.codeGenerator().write("return " + this.__code.result() + ";\n");
+        parent.handleReturn(this.__type);
+    }
+});
+
+exports.ProcParams = HandleSymbolAsType.extend({
+    init: function Context$ProcParams(context){
+        HandleSymbolAsType.prototype.init.call(this, context);
+        this.__isVar = false;
+        this.__argNamesForType = [];
+    },
+    handleLiteral: function(s){
+        if (s == "VAR")
+            this.__isVar = true;
+    },
+    setIdent: function(id){ this.__argNamesForType.push(id);},
+    setType: function(type){
+        var names = this.__argNamesForType;
+        for(var i = 0; i < names.length; ++i){
+            var name = names[i];
+            this.parent().addArgument(name, new Procedure.Arg(type, this.__isVar));
+        }
+        this.__isVar = false;
+        this.__argNamesForType = [];
+    }
+});
+
+exports.PointerDecl = ChainedContext.extend({
+    init: function Context$PointerDecl(context){
+        ChainedContext.prototype.init.call(this, context);
+    },
+    handleSymbol: function(s){
+        var typeId = unwrapTypeId(s.symbol().info());
+        this.__setTypeId(typeId);
+    },
+    __setTypeId: function(typeId){
+        if (!(typeId instanceof Type.ForwardTypeId)){
+            var type = typeId.type();
+            if (!(type instanceof Type.Record))
+                throw new Errors.Error(
+                    "RECORD is expected as a POINTER base type, got '" + type.description() + "'");
+        }
+
+        var parent = this.parent();
+        var name = parent.isAnonymousDeclaration() 
+            ? undefined
+            : parent.genTypeName();
+        var pointerType = new Type.Pointer(name, typeId);
+        parent.setType(pointerType);
+    },
+    setType: function(type){
+        var typeId = new Type.TypeId(type);
+        this.currentScope().addType(typeId);
+        this.__setTypeId(typeId);
+    },
+    findSymbol: function(id){
+        var parent = this.parent();
+        var existing = parent.findSymbol(id);
+        if (existing)
+            return existing;
+
+        var scope = this.currentScope();
+        scope.addUnresolved(id);
+        var resolve = function(){return getSymbol(parent, id).info().type();};
+
+        return new Symbol.Found(
+            new Symbol.Symbol(id, new Type.ForwardTypeId(resolve)),
+            scope
+            );
+    },
+    isAnonymousDeclaration: function(){return true;},
+    exportField: function(field){
+        throw new Errors.Error( "cannot export anonymous RECORD field: '" + field + "'");
+    }
+});
+
+exports.ArrayDecl = HandleSymbolAsType.extend({
+    init: function Context$ArrayDecl(context){
+        HandleSymbolAsType.prototype.init.call(this, context);
+        this.__dimensions = undefined;
+    },
+    handleDimensions: function(dimensions){this.__dimensions = dimensions;},
+    setType: function(type){
+        var initializer = type instanceof Type.Array || type instanceof Type.Record
+            ? "function(){return " + type.initializer(this) + ";}"
+            : type.initializer(this);
+        var dimensions = "";
+        for(var i = this.__dimensions.length; i-- ;){
+            var length = this.__dimensions[i];
+            dimensions = length + (dimensions.length ? ", " + dimensions : "");
+            var arrayInit = !i
+                ? this.rtl().makeArray(dimensions + ", " + initializer)
+                : undefined;
+            type = new Type.Array("ARRAY OF " + type.name()
+                               , arrayInit
+                               , type
+                               , length);
+        }
+
+        this.__type = type;
+    },
+    isAnonymousDeclaration: function(){return true;},
+    endParse: function(){this.parent().setType(this.__type);}
+});
+
+exports.ArrayDimensions = ChainedContext.extend({
+    init: function ArrayDimensionsContext(context){
+        ChainedContext.prototype.init.call(this, context);
+        this.__dimensions = [];
+    },
+    codeGenerator: function(){return Code.nullGenerator;},
+    handleExpression: function(e){
+        var type = e.type();
+        if (type !== basicTypes.integer)
+            throw new Errors.Error("'INTEGER' constant expression expected, got '" + type.description() + "'");
+        var value = e.constValue();
+        if (value === undefined)
+            throw new Errors.Error("constant expression expected as ARRAY size");
+        if (value <= 0)
+            throw new Errors.Error("array size must be greater than 0, got " + value);
+        this.__dimensions.push(value);
+    },
+    endParse: function(){
+        this.parent().handleDimensions(this.__dimensions);
+    }
+});
+
+var numericOpTypeCheck = {
+    expect: "numeric type",
+    check: function(t){return Type.numeric.indexOf(t) != -1;}
+};
+
+var intOpTypeCheck = {
+    expect: "INTEGER",
+    check: function(t){return t == basicTypes.integer;}
+};
+
+var orderOpTypeCheck = {
+    expect: "numeric type or CHAR or character array",
+    check: function(t){
+        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.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;
+    }
+};
+
+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, intOp){
+    assertOpType(type, numericOpTypeCheck, literal);
+    return (intOp && type == basicTypes.integer)
+           ? intOp : op;
+}
+
+function assertIntOp(type, 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({
+    init: function AddOperatorContext(context){
+        ChainedContext.prototype.init.call(this, context);
+    },
+    handleLiteral: function(s){
+        var parent = this.parent();
+        var type = parent.type();
+        var o;
+        if (s == "+")
+            o = (type == basicTypes.set) ? op.setUnion
+                                         : assertNumericOp(type, s, op.add, op.addInt);
+        else if (s == "-")
+            o = (type == basicTypes.set) ? op.setDiff
+                                         : assertNumericOp(type, s, op.sub, op.subInt);
+        else if (s == "OR"){
+            if (type != basicTypes.bool)
+                throw new Errors.Error("BOOLEAN expected as operand of 'OR', got '"
+                                     + type.description() + "'");
+            o = op.or;
+        }
+        if (o)
+            parent.handleBinaryOperator(o);
+    }
+});
+
+exports.MulOperator = ChainedContext.extend({
+    init: function MulOperatorContext(context){
+        ChainedContext.prototype.init.call(this, context);
+    },
+    handleLiteral: function(s){
+        var parent = this.parent();
+        var type = parent.type();
+        var o;
+        if (s == "*")
+            o = (type == basicTypes.set) ? op.setIntersection
+                                         : assertNumericOp(type, s, op.mul, op.mulInt);
+        else if (s == "/"){
+            if (type == basicTypes.set)
+                o = op.setSymmetricDiff;
+            else if (type == basicTypes.integer)
+                throw new Errors.Error("operator DIV expected for integer division");
+            else
+                o = assertNumericOp(type, s, op.div);
+        }
+        else if (s == "DIV")
+            o = assertIntOp(type, s, op.divInt);
+        else if (s == "MOD")
+            o = assertIntOp(type, s, op.mod);
+        else if (s == "&"){
+            if (type != basicTypes.bool)
+                throw new Errors.Error("BOOLEAN expected as operand of '&', got '"
+                                     + type.description() + "'");
+            o = op.and;
+        }
+
+        if (o)
+            parent.handleOperator(o);
+    }
+});
+
+function writeDerefDesignatorCode(designator, code){
+    var info = designator.info();
+    if (info instanceof Type.VariableRef)
+        code.write(".get()");
+}
+
+exports.Term = ChainedContext.extend({
+    init: function TermContext(context){
+        ChainedContext.prototype.init.call(this, context);
+        this.__operator = undefined;
+        this.__expression = undefined;
+    },
+    type: function(){return this.__expression.type();},
+    setDesignator: function(d){
+        var value;
+        var info = d.info();
+        if (info instanceof Type.Const)
+            value = info.value();
+        this.handleExpression(
+            new Code.Expression(d.code(), d.type(), d, value));
+    },
+    handleOperator: function(o){this.__operator = o;},
+    handleConst: function(type, value, code){
+        this.handleExpression(new Code.Expression(
+            code, type, undefined, value));
+    },
+    handleFactor: function(e){
+        var type = e.type();
+        if (!type)
+            throw new Errors.Error("procedure returning no result cannot be used in an expression");
+        this.handleExpression(e);
+    },
+    endParse: function(){this.parent().handleTerm(this.__expression);},
+    handleExpression: function(e){
+        e = promoteExpressionType(this, this.__expression, e);
+        if (this.__operator)
+            e = this.__expression ? this.__operator(this.__expression, e)
+                                  : this.__operator(e);
+        this.__expression = e;
+    }
+});
+
+exports.Factor = ChainedContext.extend({
+    init: function FactorContext(context){
+        ChainedContext.prototype.init.call(this, context);
+    },
+    type: function(){return this.parent().type();},
+    handleLiteral: function(s){
+        var parent = this.parent();
+        if (s == "NIL")
+            parent.handleConst(Type.nil, undefined, "null");
+        else if (s == "TRUE")
+            parent.handleConst(basicTypes.bool, true, "true");
+        else if (s == "FALSE")
+            parent.handleConst(basicTypes.bool, false, "false");
+        else if (s == "~"){
+            parent.setType(basicTypes.bool);
+            parent.handleOperator(op.not);
+        }
+    },
+    handleFactor: function(e){this.parent().handleFactor(e);}
+});
+
+exports.Set = ChainedContext.extend({
+    init: function SetContext(context){
+        ChainedContext.prototype.init.call(this, context);
+        this.__value = 0;
+        this.__expr = "";
+    },
+    handleElement: function(from, fromValue, to, toValue){
+        if (fromValue !== undefined && (!to || toValue !== undefined)){
+            if (to)
+                for(var i = fromValue; i <= toValue; ++i)
+                    this.__value |= 1 << i;
+            else
+                this.__value |= 1 << fromValue;
+        }
+        else{
+            if (this.__expr.length)
+                this.__expr += ", ";
+            if (to)
+                this.__expr += "[" + from + ", " + to + "]";
+            else
+                this.__expr += from;
+        }
+    },
+    endParse: function(){
+        var parent = this.parent();
+        if (!this.__expr.length)
+            parent.handleConst(basicTypes.set, this.__value, this.__value.toString());
+        else{
+            var code = this.rtl().makeSet(this.__expr);
+            if (this.__value)
+                code += " | " + this.__value;
+            var e = new Code.Expression(code, basicTypes.set);
+            parent.handleFactor(e);
+        }
+    }
+});
+
+exports.SetElement = ChainedContext.extend({
+    init: function SetElementContext(context){
+        ChainedContext.prototype.init.call(this, context);
+        this.__from = undefined;
+        this.__fromValue = undefined;
+        this.__to = undefined;
+        this.__toValue = undefined;
+        this.__expr = new Code.SimpleGenerator();
+    },
+    codeGenerator: function(){return this.__expr;},
+    handleExpression: function(e){
+        var value = e.constValue();
+        if (!this.__from)
+            {
+            this.__from = this.__expr.result();
+            this.__fromValue = value;
+            this.__expr = new Code.SimpleGenerator();
+            }
+        else{
+            this.__to = this.__expr.result();
+            this.__toValue = value;
+        }
+    },
+    endParse: function(){
+        this.parent().handleElement(this.__from, this.__fromValue, this.__to, this.__toValue);
+    }
+});
+
+function constValueCode(value){
+    if (typeof value == "string")
+        return escapeString(value);
+    return value.toString();
+}
+
+exports.SimpleExpression = ChainedContext.extend({
+    init: function SimpleExpressionContext(context){
+        ChainedContext.prototype.init.call(this, context);
+        this.__unaryOperator = undefined;
+        this.__binaryOperator = undefined;
+        this.__type = undefined;
+        this.__exp = undefined;
+    },
+    handleTerm: function(e){
+        var type = e.type();
+        this.setType(type);
+
+        var o;
+        switch(this.__unaryOperator){
+            case "-":
+                o = (type == basicTypes.set) ? op.setComplement
+                                             : assertNumericOp(type, this.__unaryOperator, op.negate);
+                break;
+            case "+":
+                o = assertNumericOp(type, this.__unaryOperator, op.unaryPlus);
+                break;
+            }
+        if (o){
+            this.__exp = o(e);
+            this.__unaryOperator = undefined;
+        }
+        else
+            this.__exp = this.__exp ? this.__binaryOperator(this.__exp, e) : e;
+    },
+    handleLiteral: function(s){this.__unaryOperator = s;},
+    type: function(){return this.__type;},
+    setType: function(type){
+        if (type === undefined || this.__type === undefined)
+            this.__type = type;
+        else
+            checkImplicitCast(type, this.__type);
+    },
+    handleBinaryOperator: function(o){this.__binaryOperator = o;},
+    endParse: function(){
+        this.parent().handleSimpleExpression(this.__exp);
+    }
+});
+
+exports.Expression = ChainedContext.extend({
+    init: function ExpressionContext(context){
+        ChainedContext.prototype.init.call(this, context);
+        this.__relation = undefined;
+        this.__expression = undefined;
+    },
+    handleSimpleExpression: function(e){
+        if (!this.__expression){
+            this.__expression = e;
+            return;
+        }
+
+        var leftExpression = this.__expression;
+        var leftType = leftExpression.type();
+        var leftCode = leftExpression.code();
+        var rightExpression = e;
+        var rightType = rightExpression.type();
+        var rightCode = rightExpression.code();
+        var code;
+
+        if (this.__relation == "IN"){
+            if (leftType != basicTypes.integer)
+                throw new Errors.Error("'INTEGER' expected as an element of SET, got '" + leftType.name() + "'");
+            checkImplicitCast(rightType, basicTypes.set);
+
+            code = "1 << " + leftCode + " & " + rightCode;
+        }
+        else if (this.__relation == "IS"){
+            if (!(leftType instanceof Type.Pointer))
+                throw new Errors.Error("POINTER to type expected before 'IS'");
+
+            rightType = unwrapType(rightType);
+            if (!(rightType instanceof Type.Record))
+                throw new Errors.Error("RECORD type expected after 'IS'");
+
+            checkTypeCast(leftType, rightType, "invalid type test");
+
+            code = leftCode + " instanceof " + rightCode;
+        }
+        else {
+            leftExpression = promoteTypeInExpression(leftExpression, rightType);
+            rightExpression = promoteTypeInExpression(rightExpression, leftType);
+            leftCode = leftExpression.code();
+            rightCode = rightExpression.code();
+            //checkImplicitCast(rightExpression.type(), leftExpression.type());
+        }
+
+        var value;
+        if (!code){
+            var o = relationOp(leftExpression.type(), rightExpression.type(), this.__relation);
+            var oResult = o(leftExpression, rightExpression, this);
+            code = oResult.code();
+            value = oResult.constValue();
+        }
+
+        this.__expression = new Code.Expression(code, basicTypes.bool, undefined, value);
+    },
+    handleLiteral: function(relation){
+        this.__relation = relation;
+    },
+    codeGenerator: function(){return Code.nullGenerator;},
+    endParse: function(){
+        var parent = this.parent();
+        parent.codeGenerator().write(this.__expression.code());
+        parent.handleExpression(this.__expression);
+    }
+});
+
+function handleIfExpression(e){
+    var type = e.type();
+    if (type !== basicTypes.bool)
+        throw new Errors.Error("'BOOLEAN' expression expected, got '" + type.description() + "'");
+}
+
+var IfContextBase = ChainedContext.extend({
+    init: function(context){
+        ChainedContext.prototype.init.call(this, context);
+    },
+    endParse: function(){
+        var gen = this.codeGenerator();
+        gen.write(")");
+        gen.openScope();
+    },
+    handleExpression: handleIfExpression
+});
+
+exports.If = IfContextBase.extend({
+    init: function IfContext(context){
+        ChainedContext.prototype.init.call(this, context);
+        this.codeGenerator().write("if (");
+    }
+});
+
+exports.ElseIf = IfContextBase.extend({
+    init: function ElseIfContext(context){
+        ChainedContext.prototype.init.call(this, context);
+        var gen = this.codeGenerator();
+        gen.closeScope();
+        gen.write("else if (");
+    }
+});
+
+exports.Else = ChainedContext.extend({
+    init: function ElseContext(context){
+        ChainedContext.prototype.init.call(this, context);
+        var gen = this.codeGenerator();
+        gen.closeScope();
+        gen.write("else ");
+        gen.openScope();
+    }
+});
+
+exports.emitEndStatement = function(context){
+    context.codeGenerator().write(";\n");
+};
+
+exports.emitIfEnd = function(context){
+    context.codeGenerator().closeScope();
+};
+
+exports.Case = ChainedContext.extend({
+    init: function CaseContext(context){
+        ChainedContext.prototype.init.call(this, context);
+        this.__type = undefined;
+        this.__firstCase = true;
+        this.genVarName("$c");
+        this.codeGenerator().write("$c = ");
+    },
+    handleExpression: function(e){
+        var type = e.type();
+        var gen = this.codeGenerator();
+        if (type instanceof Type.String){
+            var v = type.asChar();
+            if (v !== undefined){
+                gen.write(v);
+                type = basicTypes.ch;
+            }
+        }
+        if (type != basicTypes.integer && type != basicTypes.ch)
+            throw new Errors.Error("'INTEGER' or 'CHAR' expected as CASE expression");
+        this.__type = type;
+        gen.write(";\n");
+    },
+    beginCase: function(){
+        if (this.__firstCase)
+            this.__firstCase = false;
+        else
+            this.codeGenerator().write("else ");
+    },
+    handleLabelType: function(type){
+        if (type !== this.__type)
+            throw new Errors.Error(
+                "label must be '" + this.__type.name() + "' (the same as case expression), got '"
+                + type.name() + "'");
+    }
+});
+
+exports.CaseLabelList = ChainedContext.extend({
+    init: function CaseLabelListContext(context){
+        ChainedContext.prototype.init.call(this, context);
+        this.__glue = "";
+    },
+    handleLabelType: function(type){this.parent().handleLabelType(type);},
+    handleRange: function(from, to){
+        if (!this.__glue)
+            this.parent().caseLabelBegin();
+
+        var cond = to === undefined
+            ? "$c === " + from
+            : "($c >= " + from + " && $c <= " + to + ")";
+        this.codeGenerator().write(this.__glue + cond);
+        this.__glue = " || ";
+    },
+    endParse: function(){this.parent().caseLabelEnd();}
+});
+
+exports.CaseLabel = ChainedContext.extend({
+    init: function CaseLabelContext(context){
+        ChainedContext.prototype.init.call(this, context);
+    },
+    caseLabelBegin: function(){
+        this.parent().beginCase();
+        this.codeGenerator().write("if (");
+    },
+    caseLabelEnd: function(){
+        var gen = this.codeGenerator();
+        gen.write(")");
+        gen.openScope();
+    },
+    handleLabelType: function(type){this.parent().handleLabelType(type);},
+    handleRange: function(from, to){this.parent().handleRange(from, to);},
+    endParse: function(){this.codeGenerator().closeScope();}
+});
+
+exports.CaseRange = ChainedContext.extend({
+    init: function CaseRangeContext(context){
+        ChainedContext.prototype.init.call(this, context);
+        this.__from = undefined;
+        this.__to = undefined;
+    },
+    codeGenerator: function(){return Code.nullGenerator;}, // suppress any output
+    handleLabel: function(type, v){
+        this.parent().handleLabelType(type);
+        if (this.__from === undefined )
+            this.__from = v;
+        else
+            this.__to = v;
+    },
+    handleConst: function(type, value){
+        if (type instanceof Type.String){
+            value = type.asChar();
+            if (value === undefined)
+                throw new Errors.Error("single-character string expected");
+            type = basicTypes.ch;
+        }
+        this.handleLabel(type, value);
+    },
+    setIdent: function(id){
+        var s = getSymbol(this.parent(), id);
+        if (!s.isConst())
+            throw new Errors.Error("'" + id + "' is not a constant");
+        
+        var type = s.info().type();
+        if (type instanceof Type.String)
+            this.handleConst(type, undefined);
+        else
+            this.handleLabel(type, s.info().value());
+    },
+    endParse: function(){this.parent().handleRange(this.__from, this.__to);}
+});
+
+exports.While = ChainedContext.extend({
+    init: function WhileContext(context){
+        ChainedContext.prototype.init.call(this, context);
+        var gen = this.codeGenerator();
+        gen.write("while (true)");
+        gen.openScope();
+        gen.write("if (");
+    },
+    handleExpression: handleIfExpression,
+    endParse: function(){
+        var gen = this.codeGenerator();
+        gen.write(")");
+        gen.openScope();
+    }
+});
+
+exports.emitWhileEnd = function(context){
+    var gen = context.codeGenerator();
+    gen.closeScope(" else break;\n");
+    gen.closeScope();
+};
+
+exports.Repeat = ChainedContext.extend({
+    init: function RepeatContext(context){
+        ChainedContext.prototype.init.call(this, context);
+        var gen = context.codeGenerator();
+        gen.write("do ");
+        gen.openScope();
+    }
+});
+
+exports.Until = ChainedContext.extend({
+    init: function UntilContext(context){
+        ChainedContext.prototype.init.call(this, context);
+        var gen = context.codeGenerator();
+        gen.closeScope(" while (");
+    },
+    handleExpression: handleIfExpression,
+    endParse: function(){this.codeGenerator().write(");\n");}
+});
+
+exports.For = ChainedContext.extend({
+    init: function ForContext(context){
+        ChainedContext.prototype.init.call(this, context);
+        this.__var = undefined;
+        this.__initExprParsed = false;
+        this.__toExpr = new Code.SimpleGenerator();
+        this.__toParsed = false;
+        this.__by_parsed = false;
+        this.__by = undefined;
+    },
+    setIdent: function(id){
+        var s = getSymbol(this.parent(), id);
+        if (!s.isVariable())
+            throw new Errors.Error("'" + s.id() + "' is not a variable");
+        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 + " = ");
+        this.__var = id;
+    },
+    handleExpression: function(e){
+        var type = e.type();
+        var value = e.constValue();
+        if (type !== basicTypes.integer)
+            throw new Errors.Error(
+                !this.__initExprParsed
+                    ? "'INTEGER' expression expected to assign '" + this.__var
+                      + "', got '" + type.description() + "'"
+                    : !this.__toParsed
+                    ? "'INTEGER' expression expected as 'TO' parameter, got '" + type.description() + "'"
+                    : "'INTEGER' expression expected as 'BY' parameter, got '" + type.description() + "'"
+                    );
+        if (!this.__initExprParsed)
+            this.__initExprParsed = true;
+        else if (!this.__toParsed)
+            this.__toParsed = true;
+        else if ( value === undefined )
+            throw new Errors.Error("constant expression expected as 'BY' parameter");
+        else
+            this.__by = value;
+    },
+    codeGenerator: function(){
+        if (this.__initExprParsed && !this.__toParsed)
+            return this.__toExpr;
+        if (this.__toParsed && !this.__by_parsed)
+            return Code.nullGenerator; // suppress output for BY expression
+        
+        return this.parent().codeGenerator();
+    },
+    handleBegin: function(){
+        this.__by_parsed = true;
+
+        var relation = this.__by < 0 ? " >= " : " <= ";
+        var step = this.__by === undefined
+                            ? "++" + this.__var
+                            : this.__var + (this.__by < 0
+                                    ? " -= " + -this.__by
+                                    : " += " +  this.__by);
+        var s = "; " + this.__var + relation + this.__toExpr.result() + "; " + step + ")";
+        var gen = this.codeGenerator();
+        gen.write(s);
+        gen.openScope();
+    },
+    endParse: function(){this.codeGenerator().closeScope();}
+});
+
+exports.emitForBegin = function(context){context.handleBegin();};
+
+exports.CheckAssignment = ChainedContext.extend({
+    init: function Context$CheckAssignment(context){
+        ChainedContext.prototype.init.call(this, context);
+    },
+    handleLiteral: function(s){
+        if (s == "=")
+            throw new Errors.Error("did you mean ':=' (statement expected, got expression)?");
+    }
+});
+
+exports.Assignment = ChainedContext.extend({
+    init: function AssignmentContext(context){
+        ChainedContext.prototype.init.call(this, context);
+        this.__left = undefined;
+    },
+    codeGenerator: function(){/*throw new Error("Test");*/ return Code.nullGenerator;},
+    setDesignator: function(d){
+        this.__left = new Code.Expression(d.code(), d.type(), d);
+    },
+    handleExpression: function(e){
+        this.parent().codeGenerator().write(op.assign(this.__left, e, this));
+    }
+});
+
+exports.ConstDecl = ChainedContext.extend({
+    init: function ConstDeclContext(context){
+        ChainedContext.prototype.init.call(this, context);
+        this.__id = undefined;
+        this.__type = undefined;
+        this.__value = undefined;
+    },
+    handleIdentef: function(id){
+        this.__id = id;
+        this.codeGenerator().write("var " + id.id() + " = ");
+    },
+    handleExpression: function(e){
+        var value = e.constValue();
+        if (value === undefined)
+            throw new Errors.Error("constant expression expected");
+        this.__type = e.type();
+        this.__value = value;
+    },
+    endParse: function(){
+        var c = new Type.Const(this.__type, this.__value);
+        this.currentScope().addSymbol(new Symbol.Symbol(this.__id.id(), c), this.__id.exported());
+        this.codeGenerator().write(";\n");
+    }
+});
+
+function checkIfFieldCanBeExported(name, idents, hint){
+    for(var i = 0; i < idents.length; ++i){
+        var id = idents[i];
+        if (!id.exported())
+            throw new Errors.Error(
+                "field '" + name + "' can be exported only if " + hint + " '" +
+                id.id() + "' itself is exported too");
+    }
+}
+
+exports.VariableDeclaration = HandleSymbolAsType.extend({
+    init: function Context$VariableDeclaration(context){
+        HandleSymbolAsType.prototype.init.call(this, context);
+        this.__idents = [];
+        this.__type = undefined;
+    },
+    handleIdentef: function(id){this.__idents.push(id);},
+    exportField: function(name){
+        checkIfFieldCanBeExported(name, this.__idents, "variable");
+    },
+    setType: function(type){this.__type = type;},
+    typeName: function(){return undefined;},
+    isAnonymousDeclaration: function(){return true;},
+    endParse: function(){
+        var v = new Type.Variable(this.__type);
+        var idents = this.__idents;
+        var gen = this.codeGenerator();
+        for(var i = 0; i < idents.length; ++i){
+            var id = idents[i];
+            if (id.exported()
+                && (this.__type instanceof Type.Record
+                    || this.__type instanceof Type.Array))
+                throw new Errors.Error("only scalar type variables can be exported");
+            var varName = id.id();
+            this.currentScope().addSymbol(new Symbol.Symbol(varName, v), id.exported());
+            var t = v.type();
+            gen.write("var " + varName + " = " + t.initializer(this) + ";");
+        }
+
+        gen.write("\n");
+    }
+});
+
+exports.FieldListDeclaration = HandleSymbolAsType.extend({
+    init: function Context$FieldListDeclaration(context){
+        HandleSymbolAsType.prototype.init.call(this, context);
+        this.__idents = [];
+        this.__type = undefined;
+    },
+    typeName: function(){return undefined;},
+    handleIdentef: function(id) {this.__idents.push(id);},
+    exportField: function(name){
+        checkIfFieldCanBeExported(name, this.__idents, "field");
+    },
+    setType: function(type) {this.__type = type;},
+    isAnonymousDeclaration: function(){return true;},
+    endParse: function(){
+        var idents = this.__idents;
+        var parent = this.parent();
+        for(var i = 0; i < idents.length; ++i)
+            parent.addField(idents[i], this.__type);
+    }
+});
+
+function assertProcType(type){
+    if (!(type instanceof Type.Procedure) && !(type instanceof Module.AnyType))
+        throw new Errors.Error("PROCEDURE expected, got '" + type.description() + "'");
+}
+
+exports.ActualParameters = ChainedContext.extend({
+    init: function ActualParametersContext(context){
+        ChainedContext.prototype.init.call(this, context);
+        this.parent().hasActualParameters();
+    }
+});
+
+var ProcedureCall = ChainedContext.extend({
+    init: function ProcedureCallContext(context){
+        ChainedContext.prototype.init.call(this, context);
+        this.__type = undefined;
+        this.__procCall = undefined;
+        this.__code = new Code.SimpleGenerator();
+    },
+    setDesignator: function(d){
+        var type = d.type();
+        assertProcType(type);
+        this.__type = type;
+        this.__procCall = type.callGenerator(this, d.code());
+        this.__callExpression = undefined;
+    },
+    codeGenerator: function(){return this.__code;},
+    type: function(){return this.__type;},
+    hasActualParameters: function(){},
+    handleExpression: function(e){this.__procCall.handleArgument(e);},
+    callExpression: function(){return this.__callExpression;},
+    endParse: function(){this.__callExpression = this.__procCall.end();}
+});
+
+exports.StatementProcedureCall = ProcedureCall.extend({
+    init: function StatementProcedureCallContext(context){
+        ProcedureCall.prototype.init.call(this, context);
+    },
+    endParse: function(){
+        ProcedureCall.prototype.endParse.call(this);
+        var e = this.callExpression();
+        var type = e.type();
+        if  (type && !(type instanceof Module.AnyType ))
+            throw new Errors.Error("procedure returning a result cannot be used as a statement");
+        this.parent().codeGenerator().write(e.code());
+    }
+});
+
+exports.ExpressionProcedureCall = ProcedureCall.extend({
+    init: function ExpressionProcedureCall(context){
+        ProcedureCall.prototype.init.call(this, context);
+        this.__designator = undefined;
+        this.__hasActualParameters = false;
+    },
+    setDesignator: function(d){
+        this.__designator = d;
+    },
+    hasActualParameters: function(){
+        ProcedureCall.prototype.setDesignator.call(this, this.__designator);
+        this.__hasActualParameters = true;
+    },
+    endParse: function(){
+        var parent = this.parent();
+        if (this.__hasActualParameters){
+            ProcedureCall.prototype.endParse.call(this);
+            parent.handleFactor(this.callExpression());
+        }
+        else{
+            var d = this.__designator;
+            var info = d.info();
+            if (info instanceof Type.Procedure){
+                if (info instanceof Procedure.Std)
+                    throw new Errors.Error(info.description() + " cannot be referenced");
+                var scope = d.scope();
+                if (scope && scope.id() == "procedure")
+                    throw new Errors.Error("local procedure '" + d.code() + "' cannot be referenced");
+            }
+            parent.setDesignator(d);
+        }
+    }
+});
+
+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.call(this, context);
+        var parent = this.parent();
+        var cons = parent.genTypeName();
+        var name = parent.isAnonymousDeclaration() ? undefined : cons;
+        this.__type = new Type.Record(name, cons, context.currentScope());
+        parent.setType(this.__type);
+        parent.codeGenerator().write("var " + cons + " = ");
+    },
+    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){
+        if (!(type instanceof Type.Record))
+            throw new Errors.Error(
+                "RECORD type is expected as a base type, got '"
+                + type.description()
+                + "'");
+        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();
+        var gen = this.codeGenerator();
+        gen.write((baseType ? baseType.name() + ".extend" : this.rtl().extendId()) + "(");
+        gen.openScope();
+        gen.write("init: function " + this.__type.cons() + "()");
+        gen.openScope();
+        if (baseType)
+            gen.write(baseType.name() + ".prototype.init.call(this);\n");
+        var ownFields = type.ownFields();
+        for(var f in ownFields)
+            gen.write("this." + f + " = " + ownFields[f].initializer(this) + ";\n");
+
+        gen.closeScope();
+        gen.closeScope(");\n");
+    }
+});
+
+exports.TypeDeclaration = ChainedContext.extend({
+    init: function TypeDeclarationContext(context){
+        ChainedContext.prototype.init.call(this, context);
+        this.__id = undefined;
+        this.__symbol = undefined;
+    },
+    handleIdentef: function(id){
+        var typeId = new Type.LazyTypeId();
+        var symbol = this.currentScope().addType(typeId, id);
+        this.__id = id;
+        this.__symbol = symbol;
+    },
+    setType: function(type){
+        this.__symbol.info().define(type);
+        this.currentScope().resolve(this.__symbol);
+    },
+    typeName: function(){return this.__id.id();},
+    genTypeName: function(){return this.__id.id();},
+    isAnonymousDeclaration: function(){return false;},
+    type: function(){return this.parent().type();},
+    exportField: function(name){
+        checkIfFieldCanBeExported(name, [this.__id], "record");
+    }
+});
+
+exports.TypeSection = ChainedContext.extend({
+    init: function TypeSection(context){
+        ChainedContext.prototype.init.call(this, context);
+    },
+    endParse: function(){
+        var unresolved = this.currentScope().unresolved();
+        if (unresolved.length)
+            throw new Errors.Error("no declaration found for '" + unresolved.join("', '") + "'");
+    }
+});
+
+exports.TypeCast = ChainedContext.extend({
+    init: function TypeCastContext(context){
+        ChainedContext.prototype.init.call(this, context);
+        this.__type = undefined;
+    },
+    handleSymbol: function(s){
+        s = s.symbol();
+        if (!s.isType())
+            return; // this is not a type cast, may be procedure call
+        this.__type = s.info().type();
+    },
+    endParse: function(){
+        if (this.__type === undefined)
+            return false;
+        this.parent().handleTypeCast(this.__type);
+        return true;
+    }
+});
+
+exports.ModuleDeclaration = ChainedContext.extend({
+    init: function ModuleDeclarationContext(context){
+        ChainedContext.prototype.init.call(this, context);
+        this.__name = undefined;
+        this.__imports = {};
+        this.__moduleScope = undefined;
+        this.__moduleGen = undefined;
+    },
+    setIdent: function(id){
+        var parent = this.parent();
+        if (this.__name === undefined ) {
+            this.__name = id;
+            this.__moduleScope = new Scope.Module(id);
+            parent.pushScope(this.__moduleScope);
+        }
+        else if (id === this.__name){
+            var scope = parent.currentScope();
+            scope.strip();
+            var exports = scope.exports();
+            scope.module().info().defineExports(exports);
+            this.codeGenerator().write(this.__moduleGen.epilog(exports));
+        }
+        else
+            throw new Errors.Error("original module name '" + this.__name + "' expected, got '" + id + "'" );
+    },
+    findModule: function(name){
+        if (name == this.__name)
+            throw new Errors.Error("module '" + this.__name + "' cannot import itself");
+        return this.parent().findModule(name);
+    },
+    handleImport: function(modules){
+        var scope = this.currentScope();
+        var moduleAliases = {};
+        for(var i = 0; i < modules.length; ++i){
+            var s = modules[i];
+            var name = s.info().name();
+            this.__imports[name] = s;
+            scope.addSymbol(s);
+            moduleAliases[name] = s.id();
+        }
+        this.__moduleGen = this.parent().makeModuleGenerator(
+                this.__name,
+                moduleAliases);
+        this.codeGenerator().write(this.__moduleGen.prolog());
+    },
+    qualifyScope: function(scope){
+        if (scope != this.__moduleScope && scope instanceof Scope.Module)
+            return this.__imports[scope.module().id()].id() + ".";
+        return "";
+    }
+});
+
+var ModuleImport = ChainedContext.extend({
+    init: function ModuleImport(context){
+        ChainedContext.prototype.init.call(this, context);
+        this.__import = {};
+        this.__currentModule = undefined;
+        this.__currentAlias = undefined;
+    },
+    setIdent: function(id){
+        this.__currentModule = id;
+    },
+    handleLiteral: function(s){
+        if (s == ":=")
+            this.__currentAlias = this.__currentModule;
+        else if (s == ",")
+            this.__handleImport();
+    },
+    endParse: function(){
+        if (this.__currentModule)
+            this.__handleImport();
+
+        var modules = [];
+        var unresolved = [];
+        for(var alias in this.__import){
+            var moduleName = this.__import[alias];
+            var module = this.parent().findModule(moduleName);
+            if (!module)
+                unresolved.push(moduleName);
+            else
+                modules.push(new Symbol.Symbol(alias, module));
+        }
+        if (unresolved.length)
+            throw new Errors.Error("module(s) not found: " + unresolved.join(", "));
+        
+        this.parent().handleImport(modules);
+    },
+    __handleImport: function(){
+        var alias = this.__currentAlias;
+        if (!alias)
+            alias = this.__currentModule;
+        else
+            this.__currentAlias = undefined;
+        
+        for(var a in this.__import){
+            if (a == alias)
+                throw new Errors.Error("duplicated alias: '" + alias +"'");
+            if (this.__import[a] == this.__currentModule)
+                throw new Errors.Error("module already imported: '" + this.__currentModule +"'");
+        }
+        this.__import[alias] = this.__currentModule;
+    }
+});
+exports.ModuleImport = ModuleImport;
+
+exports.Context = Class.extend({
+    init: function Context(code, moduleGeneratorFactory, rtl, moduleResolver){
+        this.__code = code;
+        this.__moduleGeneratorFactory = moduleGeneratorFactory;
+        this.__scopes = [];
+        this.__gen = 0;
+        this.__vars = [];
+        this.__rtl = rtl;
+        this.__moduleResolver = moduleResolver;
+    },
+    genTypeName: function(){
+        ++this.__gen;
+        return "anonymous$" + this.__gen;
+    },
+    genVarName: function(id){
+        if (this.__vars.indexOf(id) === -1) {
+            this.__code.write("var " + id + ";\n");
+            this.__vars.push(id);
+        }
+    },
+    findSymbol: function(ident){
+        for(var i = this.__scopes.length; i--;){
+            var scope = this.__scopes[i];
+            var s = scope.findSymbol(ident);
+
+            if (s)
+                return new Symbol.Found(s, scope);
+        }
+        return undefined;
+    },
+    currentScope: function(){return this.__scopes[this.__scopes.length - 1];},
+    pushScope: function(scope){this.__scopes.push(scope);},
+    popScope: function(){this.__scopes.pop();},
+    handleExpression: function(){},
+    handleLiteral: function(){},
+    codeGenerator: function(){return this.__code;},
+    makeModuleGenerator: function(name, imports){
+        return this.__moduleGeneratorFactory(name, imports);
+    },
+    rtl: function(){return this.__rtl;},
+    findModule: function(name){
+        if (name == "JS"){
+            return new Module.JS();
+        }
+        return this.__moduleResolver ? this.__moduleResolver(name) : undefined;
+    }
+});

+ 20 - 0
snapshot/errors.js

@@ -0,0 +1,20 @@
+"use strict";
+/*
+do not use Class here - IE8 does not understand overloeded toString method (using Class.extend).
+
+var Class = require("rtl.js").Class;
+
+exports.Error = Class.extend({
+	init: function CompileError(msg) {this.__msg = msg;},
+	toString: function(){return this.__msg;}
+});
+*/
+function CompileError(msg){
+    this.__msg = msg;
+}
+
+CompileError.prototype.toString = function(){
+    return this.__msg;
+};
+
+exports.Error = CompileError;

+ 199 - 0
snapshot/grammar.js

@@ -0,0 +1,199 @@
+"use strict";
+
+var Context = require("context.js");
+var Lexer = require("lexer.js");
+var Parser = require("parser.js");
+var Class = require("rtl.js").Class;
+
+var character = Lexer.character;
+var literal = Lexer.literal;
+var digit = Lexer.digit;
+var hexDigit = Lexer.hexDigit;
+var ident = Lexer.ident;
+var point = Lexer.point;
+var separator = Lexer.separator;
+
+var and = Parser.and;
+var or = Parser.or;
+var optional = Parser.optional;
+var repeat = Parser.repeat;
+
+var context = Parser.context;
+var emit = Parser.emit;
+var required = Parser.required;
+
+var qualident = context(and(optional(and(ident, ".")), ident),
+                        Context.QualifiedIdentificator);
+var identdef = context(and(ident, optional("*")),
+                       Context.Identdef);
+var selector = or(and(point, ident)
+                // break recursive declaration of expList
+                , and("[", function(stream, context){return expList(stream, context);}, "]")
+                , "^"
+                , context(and("(", qualident, ")"), Context.TypeCast)
+                );
+var designator = context(and(qualident, repeat(selector)), Context.Designator);
+var type = or(context(qualident, Context.Type),
+              function(stream, context){return strucType(stream, context);} // break recursive declaration of strucType
+             );
+var identList = and(identdef, repeat(and(",", identdef)));
+var variableDeclaration = context(and(identList, ":", type), Context.VariableDeclaration);
+
+var integer = or(context(and(digit, repeat(hexDigit), "H", separator), Context.HexInteger)
+               , context(and(digit, repeat(digit), separator), Context.Integer));
+
+var scaleFactor = and(or("E", "D"), optional(or("+", "-")), digit, repeat(digit));
+var real = context(and(digit, repeat(digit), point, repeat(digit), optional(scaleFactor))
+                 , Context.Real);
+
+var number = or(real, integer);
+
+var string = or(context(Lexer.string, Context.String)
+              , context(and(digit, repeat(hexDigit), "X"), Context.Char));
+
+var factor = context(
+    or(string, number, "NIL", "TRUE", "FALSE"
+     , function(stream, context){return set(stream, context);} // break recursive declaration of set
+     , context(and(designator
+                 // break recursive declaration of actualParameters
+                 , optional(function(stream, context){return actualParameters(stream, context);})
+                  )
+             , Context.ExpressionProcedureCall)
+     , and("(", function(stream, context){return expression(stream, context);}
+         , required(")", "no matched ')'"))
+     , and("~", function(stream, context){
+                    return factor(stream, context);}) // break recursive declaration of factor
+     )
+    , Context.Factor);
+var addOperator = context(or("+", "-", "OR"), Context.AddOperator);
+var mulOperator = context(or("*", "/", "DIV", "MOD", "&"), Context.MulOperator);
+var term = context(and(factor, repeat(and(mulOperator, factor))), Context.Term);
+var simpleExpression = context(
+        and(optional(or("+", "-"))
+          , term
+          , repeat(and(addOperator, term)))
+      , Context.SimpleExpression);
+var relation = or("=", "#", "<=", "<", ">=", ">", "IN", "IS");
+var expression = context(and(simpleExpression, optional(and(relation, simpleExpression)))
+                       , Context.Expression);
+var constExpression = expression;
+
+var element = context(and(expression, optional(and("..", expression))), Context.SetElement);
+var set = and("{", context(optional(and(element, repeat(and(",", element)))), Context.Set)
+            , "}");
+
+var expList = and(expression, repeat(and(",", expression)));
+var actualParameters = and("(", context(optional(expList), Context.ActualParameters), ")");
+var procedureCall = context(and(designator, optional(actualParameters))
+                          , Context.StatementProcedureCall);
+
+var assignment = context(and(designator, context(or(":=", "="), Context.CheckAssignment)
+                       , required(expression, "expression expected"))
+                       , Context.Assignment);
+
+var statement = optional(or(
+                   emit(assignment, Context.emitEndStatement)
+                 , emit(procedureCall, Context.emitEndStatement)
+                   // break recursive declaration of ifStatement/caseStatement/whileStatement/repeatStatement
+                 , function(stream, context){return ifStatement(stream, context);}
+                 , function(stream, context){return caseStatement(stream, context);}
+                 , function(stream, context){return whileStatement(stream, context);}
+                 , function(stream, context){return repeatStatement(stream, context);}
+                 , function(stream, context){return forStatement(stream, context);}
+                 ));
+var statementSequence = and(statement, repeat(and(";", statement)));
+
+var ifStatement = and("IF", context(expression, Context.If), "THEN", statementSequence
+                    , repeat(and("ELSIF", context(expression, Context.ElseIf), "THEN", statementSequence))
+                    , optional(and("ELSE", context(statementSequence, Context.Else)))
+                    , emit("END", Context.emitIfEnd));
+
+var label = or(integer, string, ident);
+var labelRange = context(and(label, optional(and("..", label))), Context.CaseRange);
+var caseLabelList = context(and(labelRange, repeat(and(",", labelRange))), Context.CaseLabelList);
+var caseParser = optional(context(and(caseLabelList, ":", statementSequence), Context.CaseLabel));
+var caseStatement = and("CASE", context(and(expression
+                      , "OF", caseParser, repeat(and("|", caseParser)), "END")
+                      , Context.Case));
+
+var whileStatement = and("WHILE", context(expression, Context.While), "DO", statementSequence
+                       , repeat(and("ELSIF", context(expression, Context.ElseIf), "DO", statementSequence))
+                       , emit("END", Context.emitWhileEnd)
+                       );
+var repeatStatement = and("REPEAT", context(statementSequence, Context.Repeat)
+                        , "UNTIL", context(expression, Context.Until));
+
+var forStatement = context(and("FOR", ident, ":=", expression, "TO", expression
+                             , optional(and("BY", constExpression))
+                             , emit("DO", Context.emitForBegin)
+                             , statementSequence, required("END", "END expected (FOR)"))
+                         , Context.For);
+
+var fieldList = context(and(identList, ":", type), Context.FieldListDeclaration);
+var fieldListSequence = and(fieldList, repeat(and(";", fieldList)));
+
+var arrayType = and("ARRAY", context(and(
+                        context(and(constExpression, repeat(and(",", constExpression)))
+                              , Context.ArrayDimensions)
+                  , "OF", type), Context.ArrayDecl));
+
+var baseType = context(qualident, Context.BaseType);
+var recordType = and("RECORD", context(and(optional(and("(", baseType, ")")), optional(fieldListSequence)
+                                     , "END"), Context.RecordDecl));
+
+var pointerType = and("POINTER", "TO", context(type, Context.PointerDecl));
+
+var formalType = context(and(repeat(and("ARRAY", "OF")), qualident), Context.FormalType);
+var fpSection = and(optional(literal("VAR")), ident, repeat(and(",", ident)), ":", formalType);
+var formalParameters = and(
+          "("
+        , optional(context(and(fpSection, repeat(and(";", fpSection))), Context.ProcParams))
+        , required( ")" )
+        , optional(and(":", qualident)));
+
+var procedureType = and("PROCEDURE"
+                      , context(optional(formalParameters), Context.FormalParameters)
+                        );
+var strucType = or(arrayType, recordType, pointerType, procedureType);
+var typeDeclaration = context(and(identdef, "=", strucType), Context.TypeDeclaration);
+
+var procedureHeading = and("PROCEDURE"
+                         , identdef
+                         , context(optional(formalParameters), Context.FormalParametersProcDecl));
+var procedureDeclaration = context(
+      and(procedureHeading, ";"
+          // break recursive declaration of procedureBody
+        , function(stream, context){return procedureBody(stream, context);}
+        , ident)
+    , Context.ProcDecl);
+
+var constantDeclaration = context(and(identdef, "=", constExpression), Context.ConstDecl);
+
+var declarationSequence = and(optional(and("CONST", repeat(and(constantDeclaration, required(";")))))
+                            , optional(and("TYPE", context(repeat(and(typeDeclaration, required(";"))), Context.TypeSection)))
+                            , optional(and("VAR", repeat(and(variableDeclaration, required(";")))))
+                            , repeat(and(procedureDeclaration, ";")));
+var procedureBody = and(declarationSequence
+                      , optional(and("BEGIN", statementSequence))
+                      , optional(context(and("RETURN", expression), Context.Return))
+                      , required("END", "END expected (PROCEDURE)"));
+
+var imprt = and(ident, optional(and(":=", ident)));
+var importList = and("IMPORT", imprt, repeat(and(",", imprt)));
+var modul = context(and("MODULE", ident, ";",
+                        context(optional(and(importList, ";")), Context.ModuleImport),
+                        declarationSequence,
+                        optional(and("BEGIN", statementSequence)),
+                        required("END", "END expected (MODULE)"), ident, point),
+                     Context.ModuleDeclaration);
+
+exports.declarationSequence = declarationSequence;
+exports.expression = expression;
+exports.ident = ident;
+exports.module = modul;
+exports.procedureBody = procedureBody;
+exports.procedureDeclaration = procedureDeclaration;
+exports.procedureHeading = procedureHeading;
+exports.statement = statement;
+exports.typeDeclaration = typeDeclaration;
+exports.variableDeclaration = variableDeclaration;

+ 161 - 0
snapshot/lexer.js

@@ -0,0 +1,161 @@
+"use strict";
+
+var Errors = require("errors.js");
+var Stream = require("oberon.js/Stream.js");
+
+function isDigit(c) {return c >= '0' && c <= '9';}
+
+exports.digit = function(stream, context){
+    if (Stream.eof(stream))
+        return false;
+
+    var c = Stream.getChar(stream);
+    if (!isDigit(c))
+        return false;
+    context.handleChar(c);
+    return true;
+};
+
+exports.hexDigit = function(stream, context){
+    var c = Stream.getChar(stream);
+    if (!isDigit(c) && (c < 'A' || c > 'F'))
+        return false;
+    context.handleChar(c);
+    return true;
+};
+
+exports.point = function(stream, context){
+    if (Stream.eof(stream) 
+        || Stream.getChar(stream) != '.'
+        || (!Stream.eof(stream) && Stream.peekChar(stream) == '.')) // not a diapason ".."
+        return false;
+    context.handleLiteral(".");
+    return true;
+};
+
+exports.character = function(stream, context){
+    var c = stream.getChar();
+    if (c == '"')
+        return false;
+    context.handleChar(c);
+    return true;
+};
+
+function string(stream, context){
+    if (Stream.eof(stream))
+        return false;
+
+    var c = Stream.getChar(stream);
+    if (c != '"')
+        return false;
+
+    var result = "";
+    var parsed = false;
+    Stream.read(stream, function(c){
+        if (c == '"'){
+            parsed = true;
+            return false;
+        }
+        result += c;
+        return true;
+    });
+    if (!parsed)
+        throw new Errors.Error("unexpected end of string");
+
+    Stream.next(stream, 1);
+    context.handleString(result);
+    return true;
+}
+
+function isLetter(c) { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');}
+
+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 jsReservedWords 
+    = ["break", "case", "catch", "continue", "debugger", "default", "delete",
+       "do", "else", "finally", "for", "function", "if", "in", "instanceof",
+       "new", "return", "switch", "this", "throw", "try", "typeof", "var",
+       "void", "while", "with",
+       "Math" // Math is used in generated code for some functions so it is reserved word from code generator standpoint
+       ];
+
+exports.ident = function(stream, context){
+    if (Stream.eof(stream) || !isLetter(Stream.peekChar(stream)))
+        return false;
+    
+    var savePos = Stream.pos(stream);
+    var result = "";
+    Stream.read(stream, function(c){
+        if (!isLetter(c) && !isDigit(c) /*&& c != '_'*/)
+            return false;
+        result += c;
+        return true;
+    });
+
+    if (reservedWords.indexOf(result) != -1){
+        Stream.setPos(stream, savePos);
+        return false;
+    }
+    if (jsReservedWords.indexOf(result) != -1)
+        result += "$";
+
+    context.setIdent(result);
+    return true;
+};
+
+function skipComment(stream){
+    if (Stream.peekStr(stream, 2) != "(*")
+        return false;
+
+    Stream.next(stream, 2);
+    while (Stream.peekStr(stream, 2) != "*)"){
+        if (Stream.eof(stream))
+            throw new Errors.Error("comment was not closed");
+        if (!skipComment(stream))
+            Stream.next(stream, 1);
+        }
+    Stream.next(stream, 2);
+    return true;
+}
+
+function readSpaces(c){return ' \t\n\r'.indexOf(c) != -1;}
+
+exports.skipSpaces = function(stream, context){
+    if (context && context.isLexem && context.isLexem())
+        return;
+
+    do {
+        Stream.read(stream, readSpaces);
+    }
+    while (skipComment(stream));
+};
+
+exports.separator = function(stream, context){
+    return Stream.eof(stream) || !isLetter(Stream.peekChar(stream));
+};
+
+exports.literal = function(s){
+    return function(stream, context){
+        if (Stream.peekStr(stream, s.length) != s)
+            return false;
+        Stream.next(stream, s.length);
+        
+        if ((!context.isLexem || !context.isLexem())
+            && isLetter(s[s.length - 1])
+            && !Stream.eof(stream)
+           ){
+            var next = Stream.peekChar(stream);
+            if (isLetter(next) || isDigit(next))
+                return false;
+        }
+        
+        var result = context.handleLiteral(s);
+        return result === undefined || result;
+    };
+};
+
+exports.string = string;

+ 93 - 0
snapshot/module.js

@@ -0,0 +1,93 @@
+"use strict";
+
+var Code = require("code.js");
+var Errors = require("errors.js");
+var Procedure = require("procedure.js");
+var Symbol = require("symbol.js");
+var Type = require("type.js");
+
+var AnyTypeProc = Type.Procedure.extend({
+    init: function AnyTypeProc(){
+        Type.Procedure.prototype.init.call("PROCEDURE: JS.var");
+    },
+    result: function(){return any;}
+});
+
+var anyProc = new AnyTypeProc();
+
+var AnyType = Type.Basic.extend({
+    init: function AnyType(){
+        Type.Basic.prototype.init.call(this, "JS.var");
+    },
+    findSymbol: function(){return this;},
+    callGenerator: function(context, id){
+        return new Procedure.CallGenerator(context, id, anyProc);
+    }
+});
+
+var any = new AnyType();
+
+var doProcId = "do$";
+var varTypeId = "var$";
+
+var doProcSymbol = (function(){
+    var description = "JS predefined procedure 'do'";
+
+    var DoProcCallGenerator = Procedure.CallGenerator.extend({
+        init: function DoProcCallGenerator(context, id, type){
+            Procedure.CallGenerator.prototype.init.call(this, context, id, type);
+            this.__code = undefined;
+        },
+        prolog: function(id){return "";},
+        checkArgument: function(pos, e){
+            var type = e.type();
+            if (!(type instanceof Type.String))
+                throw new Errors.Error(
+                    "string is expected as an argument of " + description
+                    + ", got " + type.description());
+            
+            this.__code = type.value();
+            return Procedure.CallGenerator.prototype.checkArgument.call(this, pos, e);
+        },
+        epilog: function(){return "";},
+        writeArgumentCode: function(){},
+        callExpression: function(){
+            return new Code.Expression(this.__code);
+        }
+    });
+
+    var args = [new Procedure.Arg(undefined, false)];
+    var ProcType = Type.Procedure.extend({
+        init: function(){
+            Type.Procedure.prototype.init.call(this, doProcId);
+        },
+        description: function(){return description;},
+        args: function(){return args;},
+        result: function(){return undefined;},
+        callGenerator: function(context, id){
+            return new DoProcCallGenerator(context, id, this);
+        }
+        });
+
+    return new Symbol.Symbol(doProcId, new ProcType());
+})();
+
+var varTypeSymbol = function(){
+    return new Symbol.Symbol(varTypeId, new Type.TypeId(any));
+}();
+
+var JSModule = Type.Module.extend({
+    init: function Module$JSModule(){
+        Type.Module.prototype.init.call(this);
+    },
+    name: function(){return "this";},
+    findSymbol: function(id){
+        return new Symbol.Found(
+            id == doProcId ? doProcSymbol
+          : id == varTypeId ? varTypeSymbol
+          : new Symbol.Symbol(id, any));
+    }
+});
+
+exports.AnyType = AnyType;
+exports.JS = JSModule;

+ 96 - 0
snapshot/nodejs.js

@@ -0,0 +1,96 @@
+"use strict";
+
+var Rtl = require("rtl.js");
+var Code = require("code.js");
+var Context = require("context.js");
+var oc = require("oc.js");
+
+var fs = require("fs");
+var path = require("path");
+
+var ModuleGenerator = Rtl.Class.extend({
+    init: function Nodejs$ModuleGenerator(name, imports){
+        this.__name = name;
+        this.__imports = imports;
+    },
+    prolog: function(){
+        var result = "";            
+        var modules = this.__imports;
+        for(var name in modules){
+            var alias = modules[name];
+            result += "var " + alias + " = " + (name == "this" 
+                ? "GLOBAL"
+                : "require(\"" + name + ".js\")") + ";\n";
+        }
+        return result;
+    },
+    epilog: function(exports){
+        var result = "";
+        for(var access in exports){
+            var e = exports[access];
+            var code = Code.genExport(e);
+            if (code){
+                result += "exports." + e.id() + " = " + code + ";\n";
+            }
+        }
+        return result;
+    }
+});
+
+var RtlCodeUsingWatcher = Rtl.Code.extend({
+    init: function(){
+        Rtl.Code.prototype.init.call(this);
+        this.__used = false;
+    },
+    get: function(func){
+        this.__used = true;
+        return Rtl.Code.prototype.get.call(this, func);
+    },
+    used: function(){return this.__used;},
+    reset: function(){this.__used = false;}
+});
+
+function writeCompiledModule(name, code, outDir){
+    var filePath = path.join(outDir, name + ".js");
+    fs.writeFileSync(filePath, code);
+    }
+
+function compile(sources, handleErrors, outDir){
+    var rtlCodeWatcher = new RtlCodeUsingWatcher();
+    var rtl = new Rtl.RTL(rtlCodeWatcher);
+    var moduleCode = function(name, imports){return new ModuleGenerator(name, imports);};
+
+    var compiledFilesStack = [];
+    oc.compileModules(
+            sources,
+            function(name){
+                var fileName = name;
+                if (!path.extname(fileName).length)
+                    fileName += ".ob";
+                compiledFilesStack.push(fileName);
+                return fs.readFileSync(fileName, "utf8");
+            },
+            function(moduleResolver){return new Context.Context(
+                new Code.Generator(),
+                moduleCode,
+                rtl,
+                moduleResolver);},
+            function(e){handleErrors("File \"" + compiledFilesStack[compiledFilesStack.length - 1] + "\", " + e);},
+            function(name, code){
+                if (rtlCodeWatcher.used()){
+                    code = "var " + rtl.name() + " = require(\"" + rtl.name() 
+                         + ".js\")." + rtl.name() + ";\n" + code;
+                    rtlCodeWatcher.reset();
+                }
+                writeCompiledModule(name, code, outDir);
+                compiledFilesStack.pop();
+            });
+
+    var rtlCode = rtl.generate();
+    if (rtlCode){
+        rtlCode += "\nexports." + rtl.name() + " = " + rtl.name() + ";";
+        writeCompiledModule(rtl.name(), rtlCode, outDir);
+    }
+}
+
+exports.compile = compile;

+ 42 - 0
snapshot/oberon.js/JsString.js

@@ -0,0 +1,42 @@
+var RTL$ = require("RTL$.js").RTL$;
+var JS = GLOBAL;
+var Type = RTL$.extend({
+	init: function Type(){
+	}
+});
+
+function len(self/*Type*/){
+	var result = 0;
+	result = self.length;
+	return result;
+}
+
+function at(self/*Type*/, pos/*INTEGER*/){
+	var result = 0;
+	result = self[pos];
+	return result;
+}
+
+function indexOf(self/*Type*/, c/*CHAR*/){
+	var result = 0;
+	result = self.indexOf(JS.String.fromCharCode(c));
+	return result;
+}
+
+function indexOfFrom(self/*Type*/, c/*CHAR*/, pos/*INTEGER*/){
+	var result = 0;
+	result = self.indexOf(JS.String.fromCharCode(c), pos);
+	return result;
+}
+
+function substr(self/*Type*/, pos/*INTEGER*/, len/*INTEGER*/){
+	var result = null;
+	result = self.substr(pos, len);
+	return result;
+}
+exports.Type = Type;
+exports.len = len;
+exports.at = at;
+exports.indexOf = indexOf;
+exports.indexOfFrom = indexOfFrom;
+exports.substr = substr;

+ 23 - 0
snapshot/oberon.js/RTL$.js

@@ -0,0 +1,23 @@
+var RTL$ = {
+    extend: function extend(methods){
+        function Type(){
+            for(var m in methods)
+                this[m] = methods[m];
+        }
+        Type.prototype = this.prototype;
+
+        var result = methods.init;
+        result.prototype = new Type(); // inherit this.prototype
+        result.prototype.constructor = result; // to see constructor name in diagnostic
+        
+        result.extend = extend;
+        return result;
+    },
+    assert: function (condition, code){
+        if (!condition)
+            throw new Error("assertion failed"
+                          + ((code !== undefined) ? " with code " + code : ""));
+    }
+};
+
+exports.RTL$ = RTL$;

+ 87 - 0
snapshot/oberon.js/Stream.js

@@ -0,0 +1,87 @@
+var RTL$ = require("RTL$.js").RTL$;
+var JsString = require("JsString.js");
+var Type = RTL$.extend({
+	init: function Type(){
+		this.s = null;
+		this.pos = 0;
+	}
+});
+
+function make(text/*Type*/){
+	var result = null;
+	result = new Type();
+	result.s = text;
+	return result;
+}
+
+function eof(self/*Type*/){
+	return self.pos == JsString.len(self.s);
+}
+
+function pos(self/*Type*/){
+	return self.pos;
+}
+
+function setPos(self/*Type*/, pos/*INTEGER*/){
+	RTL$.assert(pos <= JsString.len(self.s));
+	self.pos = pos;
+}
+
+function next(self/*Type*/, n/*INTEGER*/){
+	RTL$.assert((self.pos + n | 0) <= JsString.len(self.s));
+	self.pos = self.pos + n | 0;
+}
+
+function peekChar(self/*Type*/){
+	RTL$.assert(!eof(self));
+	return JsString.at(self.s, self.pos);
+}
+
+function getChar(self/*Type*/){
+	var result = 0;
+	RTL$.assert(!eof(self));
+	result = JsString.at(self.s, self.pos);
+	++self.pos;
+	return result;
+}
+
+function peekStr(self/*Type*/, len/*INTEGER*/){
+	var max = 0;
+	max = JsString.len(self.s) - self.pos | 0;
+	if (len > max){
+		len = max;
+	}
+	return JsString.substr(self.s, self.pos, len);
+}
+
+function read(self/*Type*/, f/*ReaderProc*/){
+	while (true){
+		if (!eof(self) && f(peekChar(self))){
+			next(self, 1);
+		} else break;
+	}
+	return !eof(self);
+}
+
+function lineNumber(self/*Type*/){
+	var line = 0;
+	var lastPos = 0;
+	lastPos = JsString.indexOf(self.s, 13);
+	while (true){
+		if (lastPos != -1 && lastPos < self.pos){
+			++line;
+			lastPos = JsString.indexOfFrom(self.s, 13, lastPos + 1 | 0);
+		} else break;
+	}
+	return line + 1 | 0;
+}
+exports.make = make;
+exports.eof = eof;
+exports.pos = pos;
+exports.setPos = setPos;
+exports.next = next;
+exports.peekChar = peekChar;
+exports.getChar = getChar;
+exports.peekStr = peekStr;
+exports.read = read;
+exports.lineNumber = lineNumber;

+ 132 - 0
snapshot/oc.js

@@ -0,0 +1,132 @@
+"use strict";
+
+var Code = require("code.js");
+var Context = require("context.js");
+var Errors = require("errors.js");
+var Grammar = require("grammar.js");
+var Lexer = require("lexer.js");
+var ImportRTL = require("rtl.js");
+var Stream = require("oberon.js/Stream.js");
+
+var RTL = ImportRTL.RTL;
+var Class = ImportRTL.Class;
+
+var CompiledModule = Class.extend({
+    init: function CompiledModule(symbol, code, exports){
+        this.__symbol = symbol;
+        this.__code = code;
+        this.__exports = exports;
+    },
+    symbol: function(){return this.__symbol;},
+    code: function(){return this.__code;},
+    exports: function(){return this.__exports;}
+});
+
+function compileModule(stream, context, handleErrors){
+    Lexer.skipSpaces(stream, context);  
+    try {
+        if (!Grammar.module(stream, context))
+            throw new Errors.Error("syntax error");
+    }
+    catch (x) {
+        if (x instanceof Errors.Error) {
+            //console.log(context.getResult());
+            if (handleErrors){
+                handleErrors("line " + Stream.lineNumber(stream) + ": " + x);
+                return undefined;
+            }
+        }
+        throw x;
+    }
+    var scope = context.currentScope();
+    return new CompiledModule(
+            scope.module(),
+            context.codeGenerator().getResult(),
+            scope.exports());
+}
+
+function compileModulesFromText(
+        text,
+        contextFactory,
+        resolveModule,
+        handleCompiledModule,
+        handleErrors){
+    var stream = Stream.make(text);
+    do {
+        var context = contextFactory(resolveModule);
+        var module = compileModule(stream, context, handleErrors);
+        if (!module)
+            return;
+        handleCompiledModule(module);
+        Lexer.skipSpaces(stream);
+    }
+    while (!Stream.eof(stream));
+}
+
+var ModuleResolver = Class.extend({
+    init: function Oc$ModuleResolver(compile, handleCompiledModule, moduleReader){
+        this.__modules = {};
+        this.__compile = compile;
+        this.__moduleReader = moduleReader;
+        this.__handleCompiledModule = handleCompiledModule;
+    },
+    compile: function(text){
+        this.__compile(text, this.__resolveModule.bind(this), this.__handleModule.bind(this));
+    },
+    __resolveModule: function(name){
+        if (this.__moduleReader && !this.__modules[name])
+            this.compile(this.__moduleReader(name));
+        return this.__modules[name];
+    },
+    __handleModule: function(module){
+        var symbol = module.symbol();
+        var moduleName = symbol.id();
+        this.__modules[moduleName] = symbol.info();
+        this.__handleCompiledModule(moduleName, module.code());
+    }
+});
+
+function makeResolver(contextFactory, handleCompiledModule, handleErrors, moduleReader){
+    return new ModuleResolver(
+        function(text, resolveModule, handleModule){
+            compileModulesFromText(text,
+                                   contextFactory,
+                                   resolveModule,
+                                   handleModule,
+                                   handleErrors);
+        },
+        handleCompiledModule,
+        moduleReader
+        );
+}
+
+function compileModules(names, moduleReader, contextFactory, handleErrors, handleCompiledModule){
+    var resolver = makeResolver(contextFactory, handleCompiledModule, handleErrors, moduleReader);
+    names.forEach(function(name){ resolver.compile(moduleReader(name)); });
+}
+
+function compile(text, handleErrors){
+    var result = "";
+    var rtl = new RTL();
+    var moduleCode = function(name, imports){return new Code.ModuleGenerator(name, imports);};
+    var resolver = makeResolver(
+            function(moduleResolver){
+                return new Context.Context(new Code.Generator(),
+                                           moduleCode,
+                                           rtl,
+                                           moduleResolver);
+            },
+            function(name, code){result += code;},
+            handleErrors
+            );
+    resolver.compile(text);
+
+    var rtlCode = rtl.generate();
+    if (rtlCode)
+        result = rtlCode + result;
+    return result;
+}
+
+exports.compileModule = compileModule;
+exports.compile = compile;
+exports.compileModules = compileModules;

+ 24 - 0
snapshot/oc_nodejs.js

@@ -0,0 +1,24 @@
+"use strict";
+
+var nodejs = require("nodejs.js");
+
+function main(){
+    if (process.argv.length <= 3){
+        console.info("Usage: <oc_nodejs> <output dir> <input oberon module file(s)>");
+        return -1;
+    }
+
+    var outDir = process.argv[2];
+    var sources = process.argv.slice(3);
+    var errors = "";
+    nodejs.compile(sources, function(e){errors += e;}, outDir);
+    if (errors.length){
+        console.error(errors);
+        return -2;
+    }
+
+    console.info("OK!");
+    return 0;
+}
+
+process.exit(main());

+ 179 - 0
snapshot/operator.js

@@ -0,0 +1,179 @@
+"use strict";
+
+var Cast = require("cast.js");
+var Code = require("code.js");
+var Errors = require("errors.js");
+var Type = require("type.js");
+
+var precedence = {
+    unary: 4,
+    mulDivMod: 5,
+    addSub: 6,
+    shift: 7,
+    relational: 8,
+    equal: 9,
+    bitAnd: 10,
+    bitXor: 11,
+    bitOr: 12,
+    and: 13,
+    or: 14,
+    conditional: 15,
+    assignment: 17
+};
+
+function makeBinary(op, code, precedence, resultPrecedence){
+    return function(left, right, context){
+        var leftValue = left.constValue();
+        var rightValue = right.constValue();
+        var value = (leftValue !== undefined && rightValue !== undefined)
+            ? op(leftValue, rightValue) : undefined;
+
+        var leftCode = Code.adjustPrecedence(left.deref(), precedence);
+
+        // right code needs parentheses even if it has the same percedence
+        var rightCode = Code.adjustPrecedence(right.deref(), precedence - 1);
+        var expCode = (typeof code == "function")
+                    ? code(leftCode, rightCode, context)
+                    : leftCode + code + rightCode;
+        return new Code.Expression(expCode, left.type(), undefined, value, resultPrecedence ? resultPrecedence : precedence);
+    };
+}
+
+function makeUnary(op, code){
+    return function(e){
+        var type = e.type();
+        var value = e.constValue();
+        if (value !== undefined)
+            value = op(value, type) ;
+        var expCode = code + Code.adjustPrecedence(e.deref(), precedence.unary);
+        return new Code.Expression(expCode, type, undefined, value);
+    };
+}
+
+var mul = makeBinary(function(x, y){return x * y;}, " * ", precedence.mulDivMod);
+var div = makeBinary(function(x, y){return x / y;}, " / ", precedence.mulDivMod);
+
+function pow2(e){
+    return new Code.Expression("Math.pow(2, " + e.deref().code() + ")",
+                               Type.basic.real);
+}
+
+function log2(e){
+    return new Code.Expression("(Math.log(" + e.deref().code() + ") / Math.LN2) | 0",
+                               Type.basic.integer, undefined, undefined, precedence.bitOr);
+}
+
+function assign(left, right, context){
+    var info = left.designator().info();
+    if (!(info instanceof Type.Variable) || info.isReadOnly())
+        throw new Errors.Error("cannot assign to " + info.idType());
+
+    var leftCode = left.code();
+    var leftType = left.type();
+    var rightCode = right.code();
+    var rightType = right.type();
+
+    var isArray = leftType instanceof Type.Array;
+    if (isArray
+        && 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());
+        if (rightType.length() > leftType.length())
+            throw new Errors.Error(
+                leftType.length() + "-character ARRAY is too small for "
+                + rightType.length() + "-character string");
+        return context.rtl().assignArrayFromString(leftCode, rightCode);
+    }
+
+    var castOperation = Cast.implicit(rightType, leftType);
+    if (!castOperation)
+        throw new Errors.Error("type mismatch: '" + leftCode
+                             + "' is '" + leftType.description()
+                             + "' and cannot be assigned to '" + rightType.description() + "' expression");
+
+    if (isArray && rightType instanceof Type.Array){
+        if (leftType.length() === undefined)
+            throw new Errors.Error("'" + leftCode
+                                 + "' is open '" + leftType.description()
+                                 + "' and cannot be assigned");
+    }
+    
+    if (isArray || rightType instanceof Type.Record)
+        return context.rtl().copy(rightCode, leftCode);
+
+    var castCode = castOperation(context, right.deref()).code();
+    rightCode = castCode ? castCode : rightCode;
+    return leftCode + (info instanceof Type.VariableRef 
+                      ? ".set(" + rightCode + ")"
+                      : " = " + rightCode);
+}
+
+function makeInplace(code, altOp){
+    return function(left, right){
+        var info = left.designator().info();
+        if (info instanceof Type.VariableRef)
+            return assign(left, altOp(left, right));
+        return left.code() + code + right.deref().code();
+    };
+}
+
+function makeBinaryInt(op, code, prec){
+    return makeBinary(
+            function(x, y){return op(x, y) | 0;},
+            function(x, y){return x + code + y + " | 0";},
+            prec,
+            precedence.bitOr);
+}
+
+var operators = {
+    add:    makeBinary(   function(x, y){return x + y;}, " + ", precedence.addSub),
+    addInt: makeBinaryInt(function(x, y){return x + y;}, " + ", precedence.addSub),
+    sub:    makeBinary(   function(x, y){return x - y;}, " - ", precedence.addSub),
+    subInt: makeBinaryInt(function(x, y){return x - y;}, " - ", precedence.addSub),
+    mul:    mul,
+    mulInt: makeBinaryInt(function(x, y){return x * y;}, " * ", precedence.mulDivMod),
+    div:    div,
+    divInt: makeBinaryInt(function(x, y){return x / y;}, " / ", precedence.mulDivMod),
+    mod:        makeBinary(function(x, y){return x % y;}, " % ", precedence.mulDivMod),
+    setUnion:   makeBinary(function(x, y){return x | y;}, " | ", precedence.bitOr),
+    setDiff:    makeBinary(function(x, y){return x & ~y;}, " & ~", precedence.bitAnd),
+    setIntersection: makeBinary(function(x, y){return x & y;}, " & ", precedence.bitAnd),
+    setSymmetricDiff: makeBinary(function(x, y){return x ^ y;}, " ^ ", precedence.bitXor),
+    setInclL:   makeBinary(
+            function(x, y){return (x & y) == x;},
+            function(x, y, context){return context.rtl().setInclL(x, y);}),
+    setInclR:   makeBinary(
+            function(x, y){return (x & y) == y;},
+            function(x, y, context){return context.rtl().setInclR(x, y);}),
+
+    or:         makeBinary(function(x, y){return x || y;}, " || ", precedence.or),
+    and:        makeBinary(function(x, y){return x && y;}, " && ", precedence.and),
+
+    equal:      makeBinary(function(x, y){return x == y;}, " == ", precedence.equal),
+    notEqual:   makeBinary(function(x, y){return x != y;}, " != ", precedence.equal),
+    less:       makeBinary(function(x, y){return x < y;}, " < ", precedence.relational),
+    greater:    makeBinary(function(x, y){return x > y;}, " > ", precedence.relational),
+    eqLess:     makeBinary(function(x, y){return x <= y;}, " <= ", precedence.relational),
+    eqGreater:  makeBinary(function(x, y){return x >= y;}, " >= ", precedence.relational),
+
+    not:        makeUnary(function(x){return !x;}, "!"),
+    negate:     makeUnary(function(x){return -x;}, "-"),
+    unaryPlus:  makeUnary(function(x){return x;}, ""),
+    setComplement: makeUnary(function(x){return ~x;}, "~"),
+
+    lsl:        makeBinary(function(x, y){return x << y;}, " << ", precedence.shift),
+    asr:        makeBinary(function(x, y){return x >> y;}, " >> ", precedence.shift),
+    ror:        makeBinary(function(x, y){return x >>> y;}, " >>> ", precedence.shift),
+
+    assign:     assign,
+    mulInplace: makeInplace(" *= ", mul),
+    divInplace: makeInplace(" /= ", div),
+    
+    pow2:       pow2,
+    log2:       log2
+};
+
+for(var p in operators)
+    exports[p] = operators[p];
+exports.precedence = precedence;

+ 109 - 0
snapshot/parser.js

@@ -0,0 +1,109 @@
+"use strict";
+
+var assert = require("assert.js").ok;
+var Errors = require("errors.js");
+var Lexer = require("lexer.js");
+var Stream = require("oberon.js/Stream.js");
+
+function implicitParser(p){
+	return typeof p === "string" ? Lexer.literal(p) : p;
+}
+
+function argumentsToParsers(args){
+	var parsers = Array.prototype.slice.call(args);
+	for(var i = 0; i < parsers.length; ++i)
+		parsers[i] = implicitParser(parsers[i]);
+	return parsers;
+}
+
+exports.and = function(/*...*/){
+	assert(arguments.length >= 2);
+	var parsers = argumentsToParsers(arguments);
+
+	return function(stream, context){
+		for(var i = 0; i < parsers.length; ++i){
+			if (i)
+				Lexer.skipSpaces(stream, context);
+			
+			var p = parsers[i];
+			if (!p(stream, context))
+				return false;
+		}
+		return true;
+	};
+};
+
+exports.or = function(/*...*/){
+	assert(arguments.length >= 2);
+	var parsers = argumentsToParsers(arguments);
+
+	return function(stream, context){
+		for(var i = 0; i < parsers.length; ++i){
+			var p = parsers[i];
+			var savePos = Stream.pos(stream);
+			if (p(stream, context))
+				return true;
+			Stream.setPos(stream, savePos);
+		}
+		return false;
+	};
+};
+
+exports.repeat = function(p){
+	return function(stream, context){
+			var savePos = Stream.pos(stream);
+			while (!Stream.eof(stream) && p(stream, context)){
+				Lexer.skipSpaces(stream, context);
+				savePos = Stream.pos(stream);
+			}
+			Stream.setPos(stream, savePos);
+			return true;
+		};
+};
+
+exports.optional = function(parser){
+	assert(arguments.length == 1);
+	var p = implicitParser(parser);
+
+	return function(stream, context){
+		var savePos = Stream.pos(stream);
+		if ( !p(stream, context))
+			Stream.setPos(stream, savePos);
+		return true;
+		};
+};
+
+exports.required = function(parserOrString, error){
+	var parser = implicitParser(parserOrString);
+	
+	return function(stream, context){
+		if (!parser(stream, context))
+			throw new Errors.Error(error 
+					? error 
+					: ("'" + parserOrString + "' expected"));
+		return true;
+	};
+};
+
+exports.context = function(parser, ContextFactory){
+	return function(stream, child){
+		var context = new ContextFactory(child);
+		if (!parser(stream, context))
+			return false;
+		if (context.endParse)
+			return context.endParse() !== false;
+		return true;
+	};
+};
+
+exports.emit = function(parser, action){
+	assert(action);
+	var p = implicitParser(parser);
+
+	return function(stream, context){
+		if (!p(stream, context))
+			return false;
+		action(context);
+		return true;
+	};
+};

+ 617 - 0
snapshot/procedure.js

@@ -0,0 +1,617 @@
+"use strict";
+
+var Cast = require("cast.js");
+var Class = require("rtl.js").Class;
+var Code = require("code.js");
+var Errors = require("errors.js");
+var op = require("operator.js");
+var precedence = require("operator.js").precedence;
+var Symbol = require("symbol.js");
+var Type = require("type.js");
+
+var Arg = Class.extend({
+    init: function(type, isVar){
+        this.type = type;
+        this.isVar = isVar;
+    },
+    description: function(){
+        return (this.isVar ? "VAR " : "") + this.type.description();
+    }
+});
+exports.Arg = Arg;
+
+var CheckArgumentResult = Arg.extend({
+    init: function(type, isVar, convert){
+        Arg.prototype.init.call(this, type, isVar);
+        this.convert = convert;
+    }
+});
+
+var ProcCallGenerator = Class.extend({
+    init: function ProcCallGenerator(context, id, type){
+        this.__context = context;
+        this.__id = id;
+        this.__type = type;
+        this.__argumentsCount = 0;
+        this.__code = new Code.SimpleGenerator();
+        this.writeCode(this.prolog());
+    },
+    //id: function(){return this.__id;},
+    context: function(){return this.__context;},
+    handleArgument: function(e){
+        var pos = this.__argumentsCount++;
+        var isVarArg = false;
+        var convert;
+        if (this.__type.args){
+            var expectedArguments = this.__type.args();
+            if (pos >= expectedArguments.length )
+                // ignore, handle error after parsing all arguments
+                return;
+            
+            var arg = this.checkArgument(pos, e);
+            isVarArg = arg.isVar;
+            convert = arg.convert;
+        }
+        this.writeArgumentCode(e, pos, isVarArg, convert);
+    },
+    writeArgumentCode: function(e, pos, isVar, convert){
+        e = isVar ? e.ref() : e.deref();
+        var code = (convert ? convert(this.__context, e) : e).code();
+        var prefix = pos ? ", " : "";
+        this.writeCode(prefix + code);
+    },
+    end: function(){
+        if (this.__type.args)
+            this.checkArgumentsCount(this.__argumentsCount);
+        return this.callExpression();
+    },
+    callExpression: function(){
+        this.writeCode(this.epilog());
+        return new Code.Expression(this.__code.result(),
+                                   this.resultType());
+    },
+    resultType: function(){return this.__type ? this.__type.result() : undefined;},
+    prolog: function(){return this.__id + "(";},
+    checkArgumentsCount: function(count){
+        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.args()[pos];
+        var castOperation;
+        var expectType = arg.type; // can be undefined for predefined functions (like NEW), dont check it in this case
+        if (expectType){
+            var type = e.type();
+            castOperation = Cast.implicit(type, expectType);
+            if (!castOperation)
+                throw new Errors.Error("type mismatch for argument " + (pos + 1) + ": '" + type.description()
+                                     + "' cannot be converted to '" + expectType.description() + "'");
+        }
+        if (arg.isVar){
+            var designator = e.designator();
+            if (!designator)
+                throw new Errors.Error("expression cannot be used as VAR parameter");
+            var info = designator.info();
+            if (info instanceof Type.Const)
+                throw new Errors.Error("constant cannot be used as VAR parameter");
+            if (info.isReadOnly())
+                throw new Errors.Error(info.idType() + " cannot be used as VAR parameter");
+        }
+        return new CheckArgumentResult(arg.type, arg.isVar, castOperation);
+    },
+    epilog: function(){return ")";},
+    writeCode: function(s){this.__code.write(s);}
+});
+
+var DefinedProc = Type.Procedure.extend({
+    init: function DefinedProc(name){
+        Type.Procedure.prototype.init.call(this, name);
+        this.__arguments = undefined;
+        this.__result = undefined;
+    },
+    define: function(args, result){
+        this.__arguments = args;
+        this.__result = result;
+    },
+    args: function(){return this.__arguments;},
+    result: function(){return this.__result;},
+    description: function(){
+        var name = this.name();
+        if (name)
+            return name;
+        return 'PROCEDURE' + this.__dumpProcArgs()
+            + (this.__result ? ": " + this.__result.name() : "");
+        },
+    callGenerator: function(context, id){
+        return new ProcCallGenerator(context, id, this);
+    },
+    __dumpProcArgs: function(){
+        if (!this.__arguments.length)
+            return this.__result ? "()" : "";
+        
+        var result = "(";
+        for(var i = 0; i < this.__arguments.length; ++i){
+            if (i)
+                result += ", ";
+            result += this.__arguments[i].description();
+        }
+        result += ")";
+        return result;
+    }
+});
+
+var Std = Type.Procedure.extend({
+    init: function Std(name, args, result, callGeneratorFactory){
+        Type.Procedure.prototype.init.call(this, name);
+        this.__arguments = args;
+        this.__result = result;
+        this.__callGeneratorFactory = callGeneratorFactory;
+    },
+    description: function(){return "standard procedure " + this.name();},
+    args: function(){return this.__arguments;},
+    result: function(){return this.__result;},
+    callGenerator: function(context, id){
+        return this.__callGeneratorFactory(context, id, this);
+    }
+});
+
+var ExpCallGenerator = ProcCallGenerator.extend({
+    init: function ExpCallGenerator(context, id, type){
+        ProcCallGenerator.prototype.init.call(this, context, id, type);
+        this.__args = [];
+    },
+    prolog: function(){return "";},
+    epilog: function(){return "";},
+    writeArgumentCode: function(){},
+    checkArgument: function(pos, e){
+        this.__args.push(e);
+        return ProcCallGenerator.prototype.checkArgument.call(this, pos, e);
+    },
+    args: function(){return this.__args;}
+});
+
+var TwoArgToOperatorProcCallGenerator = ExpCallGenerator.extend({
+    init: function TwoArgToOperatorProcCallGenerator(context, id, type, operator){
+        ExpCallGenerator.prototype.init.call(this, context, id, type);
+        this.__operator = operator;
+    },
+    callExpression: function(){
+        var args = this.args();
+        return new Code.Expression(this.__operator(args[0], args[1]));
+    }
+});
+
+function setBitImpl(name, op){
+    var args = [new Arg(Type.basic.set, true),
+                new Arg(Type.basic.integer, false)];
+    function operator(x, y){
+        var value = y.constValue();
+        if (value === undefined || value < 0 || value > 31)
+            throw new Errors.Error("constant (0..31) expected as second argument of " + name);
+        var comment = "bit: " + (y.isTerm() ? value : Code.adjustPrecedence(y, precedence.shift));
+        value = 1 << value;
+        var valueCode = value + "/*" + comment + "*/";
+        return op(Code.adjustPrecedence(x, precedence.assignment), valueCode);
+    }
+    var proc = new Std(
+        name,
+        args,
+        undefined,
+        function(context, id, type){
+            return new TwoArgToOperatorProcCallGenerator(
+                context, id, type, operator);
+            });
+    var symbol = new Symbol.Symbol(name, proc);
+    return symbol;
+}
+
+function incImpl(name, unary, op){
+    var args = [new Arg(Type.basic.integer, true),
+                new Arg(Type.basic.integer, false)];
+    function operator(x, y){
+        if (!y)
+            return unary + x.code();
+
+        var value = y.constValue();
+        if (value === undefined)
+            throw new Errors.Error("constant expected as second argument of " + name);
+        var comment = y.isTerm() ? "" : "/*" + y.code() + "*/";
+        var valueCode = value + comment;
+        return op(x.code(), valueCode);
+    }
+    var CallGenerator = TwoArgToOperatorProcCallGenerator.extend({
+        init: function IncDecProcCallGenerator(context, id, type, operator){
+            TwoArgToOperatorProcCallGenerator.prototype.init.call(this, context, id, type, operator);
+        },
+        checkArgumentsCount: function(count){
+            checkVariableArgumentsCount(1, 2, count);
+        }
+    });
+    var proc = new Std(
+        name,
+        args,
+        undefined,
+        function(context, id, type){
+            return new CallGenerator(
+                context, id, type, operator);
+            });
+    var symbol = new Symbol.Symbol(name, proc);
+    return symbol;
+}
+
+function bitShiftImpl(name, op){
+    var CallGenerator = ExpCallGenerator.extend({
+        init: function ShiftProcCallGenerator(context, id, type){
+            ExpCallGenerator.prototype.init.call(this, context, id, type);
+        },
+        callExpression: function(){
+            var args = this.args();
+            return op(args[0], args[1]);
+        }
+    });
+    var args = [new Arg(Type.basic.integer, false),
+                new Arg(Type.basic.integer, false)
+               ];
+    var proc = new Std(
+        name,
+        args,
+        Type.basic.integer,
+        function(context, id, type){
+            return new CallGenerator(context, id, type);
+        });
+    var symbol = new Symbol.Symbol(name, proc);
+    return symbol;
+}
+
+function longShort(name){
+    var CallGenerator = ExpCallGenerator.extend({
+        init: function LongShortCallGenerator(context, id, type){
+            ExpCallGenerator.prototype.init.call(this, context, id, type);
+        },
+        callExpression: function(){return this.args()[0];}
+    });
+    var args = [new Arg(Type.basic.real, false)];
+    var proc = new Std(
+        name,
+        args,
+        Type.basic.real,
+        function(context, id, type){
+            return new CallGenerator(context, id, type);
+        });
+    var symbol = new Symbol.Symbol(name, proc);
+    return symbol;
+}
+
+function checkVariableArgumentsCount(min, max, actual){
+    if (actual < min)
+        throw new Errors.Error("at least " + min + " argument expected, got " + actual);
+    if (actual > max )
+        throw new Errors.Error("at most " + max + " arguments expected, got " + actual);
+}
+
+exports.predefined = [
+    function(){
+        var NewProcCallGenerator = ProcCallGenerator.extend({
+            init: function NewProcCallGenerator(context, id, type){
+                ProcCallGenerator.prototype.init.call(this, context, id, type);
+                this.__baseType = undefined;
+            },
+            prolog: function(id){return "";},
+            checkArgument: function(pos, e){
+                ProcCallGenerator.prototype.checkArgument.call(this, pos, e);
+
+                var type = e.type();
+                if (!(type instanceof Type.Pointer))
+                    throw new Errors.Error("POINTER variable expected, got '"
+                                         + type.name() + "'");
+                this.__baseType = type.baseType();
+                if (this.__baseType instanceof Type.NonExportedRecord)
+                    throw new Errors.Error("non-exported RECORD type cannot be used in NEW");
+                return new CheckArgumentResult(type, false);
+            },
+            epilog: function(){
+                return " = new " 
+                     + this.context().qualifyScope(this.__baseType.scope()) 
+                     + this.__baseType.cons()
+                     + "()";
+            }
+        });
+
+        var name = "NEW";
+        var args = [new Arg(undefined, true)];
+        var type = new Std(
+            name,
+            args,
+            undefined,
+            function(context, id, type){
+                return new NewProcCallGenerator(context, id, type);
+            });
+        var symbol = new Symbol.Symbol(name, type);
+        return symbol;
+    }(),
+    function(){
+        var LenProcCallGenerator = ProcCallGenerator.extend({
+            init: function LenProcCallGenerator(context, id, type){
+                ProcCallGenerator.prototype.init.call(this, context, id, type);
+            },
+            prolog: function(id){return "";},
+            checkArgument: function(pos, e){
+                var type = e.type();
+                if (type instanceof Type.Array || type instanceof Type.String)
+                    return new CheckArgumentResult(type, false);
+                
+                // should throw error
+                return ProcCallGenerator.prototype.checkArgument.call(this, pos, e);
+            },
+            epilog: function(){return ".length";}
+        });
+
+        var name = "LEN";
+        var args = [new Arg(new Type.Array("ARRAY OF any type"), false)];
+        var type = new Std(
+            name,
+            args,
+            Type.basic.integer,
+            function(context, id, type){
+                return new LenProcCallGenerator(context, id, type);
+            });
+        var symbol = new Symbol.Symbol(name, type);
+        return symbol;
+    }(),
+    function(){
+        var CallGenerator = ExpCallGenerator.extend({
+            init: function OddProcCallGenerator(context, id, type){
+                ExpCallGenerator.prototype.init.call(this, context, id, type);
+            },
+            callExpression: function(){
+                var e = this.args()[0];
+                var code = Code.adjustPrecedence(e, precedence.bitAnd);
+                return new Code.Expression(code + " & 1", Type.basic.bool, undefined, e.constValue(), precedence.bitAnd);
+            }
+        });
+        var name = "ODD";
+        var args = [new Arg(Type.basic.integer, false)];
+        var type = new Std(
+            "ODD",
+            args,
+            Type.basic.bool,
+            function(context, id, type){
+                return new CallGenerator(context, id, type);
+            });
+        var symbol = new Symbol.Symbol(name, type);
+        return symbol;
+    }(),
+    function(){
+        var AssertProcCallGenerator = ProcCallGenerator.extend({
+            init: function AssertProcCallGenerator(context, id, type){
+                ProcCallGenerator.prototype.init.call(this, context, id, type);
+            },
+            prolog: function(){return this.context().rtl().assertId() + "(";},
+            checkArgumentsCount: function(count){
+                checkVariableArgumentsCount(1, 2, count);
+            }
+        });
+
+        var args = [new Arg(Type.basic.bool), new Arg(Type.basic.integer)];
+        var proc = new Std(
+            "ASSERT",
+            args,
+            undefined,
+            function(context, id, type){
+                return new AssertProcCallGenerator(context, id, type);
+            });
+        var symbol = new Symbol.Symbol("ASSERT", proc);
+        return symbol;
+    }(),
+    setBitImpl("INCL", function(x, y){return x + " |= " + y;}),
+    setBitImpl("EXCL", function(x, y){return x + " &= ~(" + y + ")";}),
+    incImpl("INC", "++", function(x, y){return x + " += " + y;}),
+    incImpl("DEC", "--", function(x, y){return x + " -= " + y;}),
+    function(){
+        var CallGenerator = ProcCallGenerator.extend({
+            init: function AbsProcCallGenerator(context, id, type){
+                ProcCallGenerator.prototype.init.call(this, context, id, type);
+                this.__argType = undefined;
+            },
+            prolog: function(){return "Math.abs(";},
+            checkArgument: function(pos, e){
+                var type = e.type();
+                if (Type.numeric.indexOf(type) == -1)
+                    throw new Errors.Error("type mismatch: expected numeric type, got '" +
+                                           type.description() + "'");
+                this.__argType = type;
+                return ProcCallGenerator.prototype.checkArgument.call(this, pos, e);
+            },
+            resultType: function(){return this.__argType;}
+        });
+        var args = [new Arg(undefined, false)];
+        var proc = new Std(
+            "ABS",
+            args,
+            undefined,
+            function(context, id, type){
+                return new CallGenerator(context, id, type);
+            });
+        var symbol = new Symbol.Symbol("ABS", proc);
+        return symbol;
+    }(),
+    function(){
+        var CallGenerator = ProcCallGenerator.extend({
+            init: function FloorProcCallGenerator(context, id, type){
+                ProcCallGenerator.prototype.init.call(this, context, id, type);
+            },
+            prolog: function(){return "Math.floor(";}
+        });
+        var args = [new Arg(Type.basic.real, false)];
+        var proc = new Std(
+            "FLOOR",
+            args,
+            Type.basic.integer,
+            function(context, id, type){
+                return new CallGenerator(context, id, type);
+            });
+        var symbol = new Symbol.Symbol("FLOOR", proc);
+        return symbol;
+    }(),
+    function(){
+        var CallGenerator = ExpCallGenerator.extend({
+            init: function FltProcCallGenerator(context, id, type){
+                ExpCallGenerator.prototype.init.call(this, context, id, type);
+            },
+            callExpression: function(){
+                var e = this.args()[0];
+                return new Code.Expression(e.code(), Type.basic.real, undefined, e.constValue(), e.maxPrecedence());
+            }
+        });
+        var args = [new Arg(Type.basic.integer, false)];
+        var proc = new Std(
+            "FLT",
+            args,
+            Type.basic.real,
+            function(context, id, type){
+                return new CallGenerator(context, id, type);
+            });
+        var symbol = new Symbol.Symbol("FLT", proc);
+        return symbol;
+    }(),
+    longShort("LONG"),
+    longShort("SHORT"),
+    bitShiftImpl("LSL", op.lsl),
+    bitShiftImpl("ASR", op.asr),
+    bitShiftImpl("ROR", op.ror),
+    function(){
+        var CallGenerator = ProcCallGenerator.extend({
+            init: function OrdProcCallGenerator(context, id, type){
+                ProcCallGenerator.prototype.init.call(this, context, id, type);
+                this.__callExpression = undefined;
+            },
+            prolog: function(){return "";},
+            epilog: function(){return "";},
+            checkArgument: function(pos, e){
+                var type = e.type();
+                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.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.integer);
+                }
+                
+                if (this.__callExpression)
+                    return new CheckArgumentResult(type, false);
+
+                // should throw error
+                return ProcCallGenerator.prototype.checkArgument.call(this, pos, e);
+            },
+            callExpression: function(){return this.__callExpression;}
+        });
+        var name = "ORD";
+        var argType = new Type.Basic("CHAR or BOOLEAN or SET");
+        var args = [new Arg(argType, false)];
+        var type = new Std(
+            name,
+            args,
+            Type.basic.integer,
+            function(context, id, type){
+                return new CallGenerator(context, id, type);
+            });
+        var symbol = new Symbol.Symbol(name, type);
+        return symbol;
+    }(),
+    function(){
+        var CallGenerator = ExpCallGenerator.extend({
+            init: function ChrProcCallGenerator(context, id, type){
+                ExpCallGenerator.prototype.init.call(this, context, id, type);
+            },
+            callExpression: function(){
+                return new Code.Expression(this.args()[0].code(), Type.basic.ch);
+            }
+        });
+        var name = "CHR";
+        var type = new Std(
+            name,
+            [new Arg(Type.basic.integer, false)],
+            Type.basic.ch,
+            function(context, id, type){
+                return new CallGenerator(context, id, type);
+            });
+        var symbol = new Symbol.Symbol(name, type);
+        return symbol;
+    }(),
+    function(){
+        var CallGenerator = ExpCallGenerator.extend({
+            init: function CopyProcCallGenerator(context, id, type){
+                ExpCallGenerator.prototype.init.call(this, context, id, type);
+            },
+            callExpression: function(){
+                var args = this.args();
+                return new Code.Expression(op.assign(args[1], args[0], this.context()));
+            }
+        });
+        var name = "COPY";
+        var type = new Std(
+            name,
+            [new Arg(undefined, false),
+             new Arg(new Type.Array("ARRAY OF CHAR", undefined, Type.basic.ch), true)],
+            undefined,
+            function(context, id, type){
+                return new CallGenerator(context, id, type);
+            });
+        var symbol = new Symbol.Symbol(name, type);
+        return symbol;
+    }(),
+    function(){
+        var args = [new Arg(Type.basic.real, true),
+                    new Arg(Type.basic.integer, false)];
+        function operator(x, y){
+            return op.mulInplace(x, op.pow2(y));
+        }
+        var name = "PACK";
+        var proc = new Std(
+            name,
+            args,
+            undefined,
+            function(context, id, type){
+                return new TwoArgToOperatorProcCallGenerator(
+                    context, id, type, operator);
+                });
+        var symbol = new Symbol.Symbol(name, proc);
+        return symbol;
+    }(),
+    function(){
+        var args = [new Arg(Type.basic.real, true),
+                    new Arg(Type.basic.integer, true)];
+        function operator(x, y){
+            return op.assign(y, op.log2(x)) +
+                   "; " +
+                   op.divInplace(x, op.pow2(y));
+        }
+        var name = "UNPACK";
+        var proc = new Std(
+            name,
+            args,
+            undefined,
+            function(context, id, type){
+                return new TwoArgToOperatorProcCallGenerator(
+                    context, id, type, operator);
+                });
+        var symbol = new Symbol.Symbol(name, proc);
+        return symbol;
+    }()
+    ];
+
+exports.CallGenerator = ProcCallGenerator;
+exports.Type = DefinedProc;
+exports.Std = Std;

+ 183 - 0
snapshot/rtl.js

@@ -0,0 +1,183 @@
+"use strict";
+
+// support IE8
+if (!Array.prototype.indexOf)
+    Array.prototype.indexOf = function(x){
+        for(var i = 0; i < this.length; ++i)
+            if (this[i] === x)
+                return i;
+        return -1;
+    };
+
+function Class(){}
+Class.extend = function extend(methods){
+        function Type(){
+            for(var m in methods)
+                this[m] = methods[m];
+        }
+        Type.prototype = this.prototype;
+
+        var result = methods.init;
+        result.prototype = new Type(); // inherit this.prototype
+        result.prototype.constructor = result; // to see constructor name in diagnostic
+        
+        result.extend = extend;
+        return result;
+    };
+
+var impl = {
+    extend: Class.extend,
+    typeGuard: function(from, to){
+        if (!(from instanceof to))
+            throw new Error("typeguard assertion failed");
+        return from;
+    },
+    makeArray: function(/*dimensions, initializer*/){
+        var forward = Array.prototype.slice.call(arguments);
+        var result = new Array(forward.shift());
+        var i;
+        if (forward.length == 1){
+            var init = forward[0];
+            if (typeof init == "function")
+                for(i = 0; i < result.length; ++i)
+                    result[i] = init();
+            else
+                for(i = 0; i < result.length; ++i)
+                    result[i] = init;
+        }
+        else
+            for(i = 0; i < result.length; ++i)
+                result[i] = this.makeArray.apply(this, forward);
+        return result;
+    },
+    makeSet: function(/*...*/){
+        var result = 0;
+        
+        function checkBit(b){
+            if (b < 0 || b > 31)
+                throw new Error("integers between 0 and 31 expected, got " + b);
+        }
+
+        function setBit(b){
+            checkBit(b);
+            result |= 1 << b;
+        }
+        
+        for(var i = 0; i < arguments.length; ++i){
+            var b = arguments[i];
+            if (b instanceof Array){
+                var from = b[0];
+                var to = b[1];
+                if (from < to)
+                    throw new Error("invalid SET diapason: " + from + ".." + to);
+                for(var bi = from; bi <= to; ++bi)
+                    setBit(bi);
+            }
+            else
+                setBit(b);
+        }
+        return result;
+    },
+    makeRef: function(obj, prop){
+        return {set: function(v){ obj[prop] = v; },
+                get: function(){ return obj[prop]; }};
+    },
+    setInclL: function(l, r){return (l & r) == l;},
+    setInclR: function(l, r){return (l & r) == r;},
+    assignArrayFromString: function(a, s){
+        var i;
+        for(i = 0; i < s.length; ++i)
+            a[i] = s.charCodeAt(i);
+        for(i = s.length; i < a.length; ++i)
+            a[i] = 0;
+    },
+    strToArray: function(s){
+        var result = new Array(s.length);
+        for(var i = 0; i < s.length; ++i)
+            result[i] = s.charCodeAt(i);
+        return result;
+    },
+    copy: function(from, to){
+        for(var prop in to){
+            if (to.hasOwnProperty(prop)){
+                var v = from[prop];
+                if (v !== null && typeof v == "object")
+                    this.copy(v, to[prop]);
+                else
+                    to[prop] = v;
+            }
+        }
+    },
+    assert: function(condition, code){
+        if (!condition)
+            throw new Error("assertion failed"
+                          + ((code !== undefined) ? " with code " + code : ""));
+    }
+};
+
+var Code = Class.extend({
+    init: function RTL$Code(){
+        var names = [];
+        for(var f in impl)
+            names.push(f);
+        this.__functions = names;
+    },
+    functions: function(){return this.__functions;},
+    get: function(func){return impl[func];}
+});
+
+var defaultCode = new Code();
+
+exports.Class = Class;
+exports.RTL = Class.extend({
+    init: function RTL(code){
+        this.__entries = {};
+        this.__code = code || defaultCode;        
+        var names = this.__code.functions();
+        for(var i = 0; i < names.length; ++i){
+            var name = names[i];
+            this[name] = this.__makeOnDemand(name);
+            this[name + "Id"] = this.__makeIdOnDemand(name);
+        }
+    },
+    name: function(){return "RTL$";},
+    generate: function(){
+        var result = "var " + this.name() + " = {\n";
+        var firstEntry = true;
+        for (var name in this.__entries){
+            if (!firstEntry)
+                result += ",\n";
+            else
+                firstEntry = false;
+            result += "    " + name + ": " + this.__entries[name].toString();
+        }
+        if (!firstEntry)
+            result += "\n};\n";
+        else
+            result = undefined;
+        
+        return result;
+    },
+    __makeIdOnDemand: function(name){
+        return function(){
+            if (!this.__entries[name])
+                this.__entries[name] = this.__code.get(name);
+            return this.name() + "." + name;
+        };
+    },
+    __makeOnDemand: function(name){
+        return function(){
+            var result = this[name +"Id"]() + "(";
+            if (arguments.length){
+                result += arguments[0];
+                for(var a = 1; a < arguments.length; ++a)
+                    result += ", " + arguments[a];
+            }
+            result += ")";
+            return result;
+        };
+    }
+
+});
+
+exports.Code = Code;

+ 132 - 0
snapshot/scope.js

@@ -0,0 +1,132 @@
+"use strict";
+
+var Class = require("rtl.js").Class;
+var Errors = require("errors.js");
+var Procedure = require("procedure.js");
+var Symbol = require("symbol.js");
+var Type = require("type.js");
+
+var stdSymbols = function(){
+    var symbols = {};
+    for(var t in Type.basic){
+        var type = Type.basic[t];
+        symbols[type.name()] = new Symbol.Symbol(type.name(), new Type.TypeId(type));
+    }
+    symbols["LONGREAL"] = new Symbol.Symbol("LONGREAL", new Type.TypeId(Type.basic.real));
+    
+    var predefined = Procedure.predefined;
+    for(var i = 0; i < predefined.length; ++i){
+        var s = predefined[i];
+        symbols[s.id()] = s;
+    }
+    return symbols;
+}();
+
+var Scope = Class.extend({
+    init: function Scope(id){
+        this.__id = id;
+        this.__symbols = {};
+        for(var p in stdSymbols)
+            this.__symbols[p] = stdSymbols[p];
+        this.__unresolved = [];
+    },
+    id: function(){return this.__id;},
+    addSymbol: function(symbol){
+        var id = symbol.id();
+        if (this.findSymbol(id))
+            throw new Errors.Error( "'" + id + "' already declared");
+        this.__symbols[id] = symbol;
+    },
+    addType: function(type, id){
+        if (!id)
+            return undefined;
+
+        var symbol = new Symbol.Symbol(id.id(), type);
+        this.addSymbol(symbol, id.exported());
+        return symbol;
+    },
+    resolve: function(symbol){
+        var id = symbol.id();
+        var i = this.__unresolved.indexOf(id);
+        if (i != -1){
+            var info = symbol.info();
+            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);
+        }
+    },
+    findSymbol: function(ident){return this.__symbols[ident];},
+    addUnresolved: function(id){
+        if (this.__unresolved.indexOf(id) == -1)
+            this.__unresolved.push(id);
+    },
+    unresolved: function(){return this.__unresolved;}
+});
+
+var ProcedureScope = Scope.extend({
+    init: function ProcedureScope(){
+        Scope.prototype.init.call(this, "procedure");
+    },
+    addSymbol: function(symbol, exported){
+        if (exported)
+            throw new Errors.Error("cannot export from within procedure: " 
+                + symbol.info().idType() + " '" + symbol.id() + "'");
+        Scope.prototype.addSymbol.call(this, symbol, exported);
+    }
+});
+
+var CompiledModule = Type.Module.extend({
+    init: function Scope$CompiledModule(id){
+        Type.Module.prototype.init.call(this, id);
+        this.__exports = {};
+    },
+    defineExports: function(exports){
+        for(var id in exports){
+            var symbol = exports[id];
+            if (symbol.isVariable())
+                symbol = new Symbol.Symbol(
+                    id,
+                    new Type.ExportedVariable(symbol.info()));
+            this.__exports[id] = symbol;
+        }
+    },  
+    findSymbol: function(id){
+        var s = this.__exports[id];
+        if (!s)
+            return undefined;
+        return new Symbol.Found(s);
+    }
+});
+
+var Module = Scope.extend({
+    init: function Scope$Module(name){
+        Scope.prototype.init.call(this, "module");
+        this.__name = name;
+        this.__exports = {};
+        this.__stripTypes = [];
+        this.__symbol = new Symbol.Symbol(name, new CompiledModule(name));
+        this.addSymbol(this.__symbol);
+    },
+    module: function(){return this.__symbol;},
+    addSymbol: function(symbol, exported){
+        Scope.prototype.addSymbol.call(this, symbol, exported);
+        if (exported)
+            this.__exports[symbol.id()] = symbol;
+    },
+    addType: function(type, id){
+        var result = Scope.prototype.addType.call(this, type, id);
+        if (!id || !id.exported())
+            this.__stripTypes.push(type);
+        return result;
+    },
+    exports: function(){return this.__exports;},
+    strip: function(){
+        for(var i = 0; i < this.__stripTypes.length; ++i)
+            this.__stripTypes[i].strip();
+    }
+});
+
+exports.Procedure = ProcedureScope;
+exports.Module = Module;

+ 31 - 0
snapshot/symbol.js

@@ -0,0 +1,31 @@
+"use strict";
+
+var Class = require("rtl.js").Class;
+var Errors = require("errors.js");
+var Type = require("type.js");
+
+var Symbol = Class.extend({
+	init: function Symbol(id, info){
+		this.__id = id;
+		this.__info = info;
+	},
+	id: function(){return this.__id;},
+	info: function(){return this.__info;},
+	isModule: function(){return this.__info instanceof Type.Module;},
+	isVariable: function(){return this.__info instanceof Type.Variable;},
+	isConst: function(){return this.__info instanceof Type.Const;},
+	isType: function(){return this.__info instanceof Type.TypeId;},
+	isProcedure: function(){return this.__info instanceof Type.Procedure;}
+});
+
+var FoundSymbol = Class.extend({
+    init: function(symbol, scope){
+        this.__symbol = symbol;
+        this.__scope = scope;
+    },
+    symbol: function(){return this.__symbol;},
+    scope: function(){return this.__scope;}
+});
+
+exports.Symbol = Symbol;
+exports.Found = FoundSymbol;

+ 234 - 0
snapshot/type.js

@@ -0,0 +1,234 @@
+"use strict";
+
+var Class = require("rtl.js").Class;
+var Errors = require("errors.js");
+
+var Id = Class.extend({
+    init: function Id(){}
+});
+
+var Type = Id.extend({
+    init: function Type(){
+        Id.prototype.init.call(this);
+    }
+});
+
+var TypeId = Id.extend({
+    init: function TypeId(type){
+        Id.prototype.init.call(this);
+        this._type = type;
+    },
+    type: function(){return this._type;},
+    description: function(){return 'type ' + this._type.description();},
+    strip: function(){
+        this._type = this._type instanceof Record 
+                   ? new NonExportedRecord(this._type.cons(), this._type.scope(), this._type.baseType())
+                   : undefined;
+    }
+});
+
+var ForwardTypeId = TypeId.extend({
+    init: function Type$ForwardTypeId(resolve){
+        TypeId.prototype.init.call(this);
+        this.__resolve = resolve;
+    },
+    type: function(){
+        if (!this._type)
+            this._type = this.__resolve();
+        return this._type;
+    }
+});
+
+var LazyTypeId = TypeId.extend({
+    init: function LazyTypeId(){
+        TypeId.prototype.init.call(this);
+    },
+    define: function(type){return this._type = type;}
+});
+
+exports.String = Type.extend({
+    init: function TypeString(s){
+        Type.prototype.init.call(this);
+        this.__s = s;
+    },
+    idType: function(){return "string";},
+    description: function(){return (this.__s.length == 1 ? "single-" : "multi-") + "character string";},
+    value: function(){return this.__s;},
+    asChar: function(){return this.__s.length == 1 ? this.__s.charCodeAt(0) : undefined;},
+    length: function(){return this.__s.length;}
+});
+
+var BasicType = Type.extend({
+    init: function BasicType(name, initValue){
+        Type.prototype.init.call(this);
+        this.__name = name;
+        this.__initValue = initValue;
+    },
+    idType: function(){return "type";},
+    name: function() {return this.__name;},
+    description: function(){return this.name();},
+    initializer: function(context){return this.__initValue;}
+});
+
+exports.Basic = BasicType;
+
+function foldArrayDimensions(a){
+    var result = a.length();
+    var next = a.elementsType();
+    if (result !== undefined
+        && next instanceof ArrayType){
+        var r = foldArrayDimensions(next);
+        return [result + ", " + r[0], r[1]];
+    }
+    return [result, next.description()];
+}
+
+var ArrayType = BasicType.extend({
+    init: function ArrayType(name, initializer, elementsType, size){
+        BasicType.prototype.init.call(this, name, initializer);
+        this.__elementsType = elementsType;
+        this.__size = size;
+    },
+    elementsType: function(){return this.__elementsType;},
+    length: function(){return this.__size;},
+    description: function(){
+        if (this.__elementsType === undefined) // special arrays, see procedure "LEN"
+            return this.name();
+        var desc = foldArrayDimensions(this);
+        var sizes = (desc[0] === undefined ? "" : " " + desc[0]);
+        return "ARRAY" + sizes + " OF " + desc[1];
+    }
+});
+
+exports.Pointer = BasicType.extend({
+    init: function PointerType(name, base){
+        BasicType.prototype.init.call(this, name, "null");
+        this.__base = base;
+    },
+    description: function(){
+        return this.name() || "POINTER TO " + this.baseType().description();
+    },
+    baseType: function(){return this.__base.type();}
+});
+
+var Record = BasicType.extend({
+    init: function Type$Record(name, cons, scope){
+        BasicType.prototype.init.call(this, name);
+        this.__cons = cons;        
+        this.__scope = scope;
+        this.__fields = {};
+        this.__base = undefined;
+    },
+    initializer: function(context){
+        return "new " + context.qualifyScope(this.__scope) + this.__cons + "()";
+    },
+    scope: function(){return this.__scope;},
+    cons: function(){return this.__cons;},
+    addField: function(field, type){
+        var name = field.id();
+        if (this.__fields.hasOwnProperty(name))
+            throw new Errors.Error("duplicated field: '" + name + "'");
+        if (this.__base && this.__base.findSymbol(name))
+            throw new Errors.Error("base record already has field: '" + name + "'");
+        this.__fields[name] = type;
+    },
+    ownFields: function() {return this.__fields;},
+    findSymbol: function(field){
+        var result = this.__fields[field];
+        if ( !result && this.__base)
+            result = this.__base.findSymbol(field);
+        return result;
+    },
+    baseType: function() {return this.__base;},
+    setBaseType: function(type) {this.__base = type;},
+    description: function(){
+        return this.name() || "anonymous RECORD";
+    }
+});
+
+var NonExportedRecord = Record.extend({
+    init: function Scope$NonExportedRecord(cons, scope, base){
+        Record.prototype.init.call(this, undefined, cons, scope);
+        this.setBaseType(base);
+    }
+});
+
+var NilType = Type.extend({
+    init: function NilType(){Type.prototype.init.call(this);},
+    idType: function(){return "NIL";},
+    description: function(){return "NIL";}
+});
+
+var basic = {
+    bool: new BasicType("BOOLEAN", false),
+    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.integer, basic.real];
+
+exports.nil = new NilType();
+
+exports.Const = Id.extend({
+    init: function Const(type, value){
+        Id.prototype.init.call(this);
+        this.__type = type;
+        this.__value = value;
+    },
+    idType: function(){return "constant";},
+    type: function(){return this.__type;},
+    value: function(){return this.__value;}
+});
+
+var Variable = Id.extend({
+    init: function Variable(type, isReadOnly){
+        Id.prototype.init.call(this);
+        this.__type = type;
+        this.__isReadOnly = isReadOnly;
+    },
+    idType: function(){return this.__isReadOnly ? "read-only variable" : "variable";},
+    type: function(){return this.__type;},
+    isReadOnly: function(){return this.__isReadOnly;}
+});
+
+var VariableRef = Variable.extend({
+    init: function Type$VariableRef(type){
+        Variable.prototype.init.call(this, type, false);
+    }
+});
+
+var ExportedVariable = Variable.extend({
+    init: function ExportedVariable(variable){
+        Variable.prototype.init.call(this, variable.type(), true);
+    },
+    idType: function(){return "imported variable";}
+});
+
+exports.Procedure = BasicType.extend({
+    init: function Procedure(name){
+        BasicType.prototype.init.call(this, name, "null");
+    },
+    idType: function(){return "procedure";}
+});
+
+var Module = Id.extend({
+    init: function Type$Module(name){
+        Id.prototype.init.call(this);
+        this.__name = name;
+    },
+    name: function(){return this.__name;}
+});
+
+exports.Array = ArrayType;
+exports.Variable = Variable;
+exports.VariableRef = VariableRef;
+exports.ExportedVariable = ExportedVariable;
+exports.Module = Module;
+exports.NonExportedRecord = NonExportedRecord;
+exports.Record = Record;
+exports.Type = Type;
+exports.TypeId = TypeId;
+exports.ForwardTypeId = ForwardTypeId;
+exports.LazyTypeId = LazyTypeId;

+ 10 - 4
src/context.js

@@ -157,13 +157,19 @@ exports.Real = ChainedContext.extend({
 });
 
 function escapeString(s){
+    var escapeChars = {"\\": "\\\\",
+                       "\"": "\\\"",
+                       "\n": "\\n",
+                       "\r": "\\r",
+                       "\t": "\\t",
+                       "\b": "\\b",
+                       "\f": "\\f"
+                      };
     var result = "\"";
     for(var i = 0; i < s.length; ++i){
         var c = s[i];
-        if (c == '"')
-            result += "\\\"";
-        else
-            result += s[i];
+        var escape = escapeChars[c];
+        result += escape !== undefined ? escape : c;
     }
     return result + "\"";
 }

+ 6 - 0
test/expected/string.js

@@ -40,6 +40,12 @@ var m = function (){
 var s1 = "\"";
 var s2 = "ABC";
 var s3 = "with space";
+var s4 = "\n";
+var s5 = "\r";
+var s6 = "\b";
+var s7 = "\t";
+var s8 = "\f";
+var s9 = "\\";
 var ch1 = 0;
 var a2 = RTL$.makeArray(3, 0);
 

+ 6 - 0
test/input/string.ob

@@ -4,6 +4,12 @@ CONST
 	s1 = 22X;
 	s2 = "ABC";
 	s3 = "with space";
+	s4 = 0AX;
+	s5 = 0DX;
+	s6 = 08X;
+	s7 = 09X;
+	s8 = 0CX;
+	s9 = "\";
 VAR
     ch1: CHAR;
 	a2: ARRAY 3 OF CHAR;