Browse Source

initial commit

Vladislav Folts 12 years ago
commit
53614ee1c1
65 changed files with 4899 additions and 0 deletions
  1. 22 0
      .gitattributes
  2. 163 0
      .gitignore
  3. 19 0
      LICENSE
  4. 4 0
      assert.js
  5. 3 0
      browser/build.cmd
  6. 52 0
      browser/linkjs.py
  7. 42 0
      browser/oberonjs.html
  8. 12 0
      browser/test_linkjs.py
  9. 55 0
      cast.js
  10. 37 0
      code.js
  11. 1551 0
      context.js
  12. 6 0
      errors.js
  13. 189 0
      grammar.js
  14. 104 0
      lexer.js
  15. 33 0
      module.js
  16. 26 0
      oc.js
  17. 100 0
      parser.js
  18. 214 0
      procedure.js
  19. 144 0
      rtl.js
  20. 45 0
      stream.js
  21. 3 0
      test/build_test_unit_browser.cmd
  22. 17 0
      test/expected/arithmetic.js
  23. 53 0
      test/expected/array.js
  24. 59 0
      test/expected/case.js
  25. 45 0
      test/expected/cast.js
  26. 17 0
      test/expected/const.js
  27. 17 0
      test/expected/for.js
  28. 31 0
      test/expected/if.js
  29. 41 0
      test/expected/is.js
  30. 4 0
      test/expected/js_module.js
  31. 8 0
      test/expected/logical.js
  32. 2 0
      test/expected/module.js
  33. 35 0
      test/expected/new.js
  34. 21 0
      test/expected/nil.js
  35. 57 0
      test/expected/proc.js
  36. 13 0
      test/expected/repeat.js
  37. 68 0
      test/expected/set.js
  38. 4 0
      test/expected/string.js
  39. 87 0
      test/expected/var_parameter.js
  40. 31 0
      test/expected/while.js
  41. 22 0
      test/input/arithmetic.ob
  42. 29 0
      test/input/array.ob
  43. 42 0
      test/input/case.ob
  44. 18 0
      test/input/cast.ob
  45. 20 0
      test/input/const.ob
  46. 22 0
      test/input/for.ob
  47. 17 0
      test/input/if.ob
  48. 19 0
      test/input/is.ob
  49. 5 0
      test/input/js_module.ob
  50. 12 0
      test/input/logical.ob
  51. 2 0
      test/input/module.ob
  52. 13 0
      test/input/new.ob
  53. 7 0
      test/input/nil.ob
  54. 44 0
      test/input/proc.ob
  55. 17 0
      test/input/repeat.ob
  56. 43 0
      test/input/set.ob
  57. 7 0
      test/input/string.ob
  58. 57 0
      test/input/var_parameter.ob
  59. 22 0
      test/input/while.ob
  60. 2 0
      test/test.cmd
  61. 50 0
      test/test.js
  62. 2 0
      test/test_unit.cmd
  63. 4 0
      test/test_unit.html
  64. 817 0
      test/test_unit.js
  65. 172 0
      type.js

+ 22 - 0
.gitattributes

@@ -0,0 +1,22 @@
+# Auto detect text files and perform LF normalization
+* text=auto
+
+# Custom for Visual Studio
+*.cs     diff=csharp
+*.sln    merge=union
+*.csproj merge=union
+*.vbproj merge=union
+*.fsproj merge=union
+*.dbproj merge=union
+
+# Standard to msysgit
+*.doc	 diff=astextplain
+*.DOC	 diff=astextplain
+*.docx diff=astextplain
+*.DOCX diff=astextplain
+*.dot  diff=astextplain
+*.DOT  diff=astextplain
+*.pdf  diff=astextplain
+*.PDF	 diff=astextplain
+*.rtf	 diff=astextplain
+*.RTF	 diff=astextplain

+ 163 - 0
.gitignore

@@ -0,0 +1,163 @@
+#################
+## Eclipse
+#################
+
+*.pydevproject
+.project
+.metadata
+bin/
+tmp/
+*.tmp
+*.bak
+*.swp
+*~.nib
+local.properties
+.classpath
+.settings/
+.loadpath
+
+# External tool builders
+.externalToolBuilders/
+
+# Locally stored "Eclipse launch configurations"
+*.launch
+
+# CDT-specific
+.cproject
+
+# PDT-specific
+.buildpath
+
+
+#################
+## Visual Studio
+#################
+
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+
+# User-specific files
+*.suo
+*.user
+*.sln.docstates
+
+# Build results
+[Dd]ebug/
+[Rr]elease/
+*_i.c
+*_p.c
+*.ilk
+*.meta
+*.obj
+*.pch
+*.pdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.vspscc
+.builds
+*.dotCover
+
+## TODO: If you have NuGet Package Restore enabled, uncomment this
+#packages/
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opensdf
+*.sdf
+
+# Visual Studio profiler
+*.psess
+*.vsp
+
+# ReSharper is a .NET coding add-in
+_ReSharper*
+
+# Installshield output folder
+[Ee]xpress
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish
+
+# Others
+[Bb]in
+[Oo]bj
+sql
+TestResults
+*.Cache
+ClientBin
+stylecop.*
+~$*
+*.dbmdl
+Generated_Code #added for RIA/Silverlight projects
+
+# Backup & report files from converting an old project file to a newer
+# Visual Studio version. Backup files are not needed, because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+
+
+
+############
+## Windows
+############
+
+# Windows image file caches
+Thumbs.db
+
+# Folder config file
+Desktop.ini
+
+
+#############
+## Python
+#############
+
+*.py[co]
+
+# Packages
+*.egg
+*.egg-info
+dist
+build
+eggs
+parts
+bin
+var
+sdist
+develop-eggs
+.installed.cfg
+
+# Installer logs
+pip-log.txt
+
+# Unit test / coverage reports
+.coverage
+.tox
+
+#Translations
+*.mo
+
+#Mr Developer
+.mr.developer.cfg
+
+# Mac crap
+.DS_Store

+ 19 - 0
LICENSE

@@ -0,0 +1,19 @@
+Copyright (c) 2013 Vladislav Folts 
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of 
+this software and associated documentation files (the "Software"), to deal in 
+the Software without restriction, including without limitation the rights to 
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 
+of the Software, and to permit persons to whom the Software is furnished to do 
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all 
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 
+DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 
+OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE 
+OR OTHER DEALINGS IN THE SOFTWARE.

+ 4 - 0
assert.js

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

+ 3 - 0
browser/build.cmd

@@ -0,0 +1,3 @@
+cd ..
+browser\linkjs.py oc.js browser/oc.js
+cd browser

+ 52 - 0
browser/linkjs.py

@@ -0,0 +1,52 @@
+import os
+import re
+import sys
+
+def extract_require(s):
+	m = re.search('(//)?(.*)require\\("(.*)"\\)', s)
+	if not m or m.group(1):
+		return None
+	prefix = m.group(2)
+	if prefix and len(prefix):
+		prefix = prefix[-1]
+		if prefix.isalpha() or prefix.isdigit() or prefix == '_':
+			return None
+	return m.group(3)
+
+def resolve_require(req, out, resolved, resolving):
+	if not os.path.exists(req):
+		raise Exception('cannot resolve "%s"' % req)
+	process(req, out, resolved, resolving)
+
+def process(path, out, resolved, resolving):
+	module_name = os.path.splitext(os.path.basename(path))[0]
+	if module_name in resolving:
+		raise Exception('cyclic import detected: "%s"' % module_name)
+	result = 'imports["%s"] = {};\n' % path
+	result += '(function %s(exports){\n' % module_name
+	with open(path) as f:
+		for l in f:
+			req = extract_require(l)
+			if req and not req in resolved:
+				try:
+					resolve_require(req, out, resolved, resolving + [module_name])
+				except Exception, e:
+					print 'while resolving "%s"...' % module_name
+					raise e
+			result += l
+	result += '\n})(imports["%s"]);\n' % path
+	out.write(result)
+	resolved += [path]
+
+if __name__ == '__main__':
+	if len(sys.argv) != 3:
+		raise Exception("Usage: linkjs.py <input js> <output js>")
+
+	input_path = sys.argv[1]
+	output_path = sys.argv[2]
+	with open(output_path, "w") as out:
+		prolog = "var imports = {};\n"
+		prolog += 'function require(module){return imports[module];}\n'
+		out.write(prolog)
+		process(input_path, out, [], [])
+

+ 42 - 0
browser/oberonjs.html

@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<html>
+<body>
+<p>
+Oberon module:
+</p>
+<textarea id="source" rows="10" cols="80">
+MODULE test;
+IMPORT JS;
+BEGIN
+    JS.alert("Hello, World!")
+END test.
+</textarea>
+<p>
+<button onclick="compile()">Compile</button>
+<button onclick="compile(); run()">Compile &amp; Run</button>
+</p>
+<textarea id="result" rows="10" cols="80">
+</textarea>
+<p>
+<button onclick="run()">Run</button>
+</p>
+<script>
+function require(){}
+</script>
+
+<script src="oc.js"></script>
+
+<script>
+function compile(){
+	var src = document.getElementById("source").value;
+	var result = require("oc.js").compile(src);
+	document.getElementById("result").value = result;
+}
+
+function run(){
+	eval(document.getElementById("result").value);
+}
+</script>
+
+</body>
+</html>

+ 12 - 0
browser/test_linkjs.py

@@ -0,0 +1,12 @@
+from linkjs import extract_require
+import unittest
+
+class Test(unittest.TestCase):
+	def test_extract_require(self):
+		self.assertEqual(extract_require('require("test.js")'), 'test.js')
+		self.assertEqual(extract_require('var test = require("../test.js");'), '../test.js')
+		self.assertEqual(extract_require('//var test = require("test.js");'), None)
+		self.assertEqual(extract_require('Another_require("test.js");'), None)
+
+if __name__ == '__main__':
+    unittest.main()

+ 55 - 0
cast.js

@@ -0,0 +1,55 @@
+var Type = require("type.js");
+var ArrayType = Type.Array;
+var PointerType = Type.Pointer;
+
+function implicitCast(from, to){
+	if (from == to)
+		return true;
+
+	//if (from instanceof VarParameter)
+	//	return implicitCast(from.type(), (to instanceof VarParameter) ? to.type() : to);
+	
+	if ((to === Type.basic.char) && (from instanceof Type.String)){
+		var v = from.asChar();
+		if (v !== undefined)
+			return true;
+	}
+	else if (from instanceof ArrayType && to instanceof ArrayType)
+		return implicitCast(from.elementsType(), to.elementsType());
+	else if (from instanceof PointerType && to instanceof PointerType){
+		toR = to.baseType();
+		fromR = from.baseType();
+		while (fromR && fromR != toR)
+			fromR = fromR.baseType();
+		if (fromR)
+			return true;
+	}
+	else if (from == Type.nil 
+		&& (to instanceof PointerType || to.isProcedure()))
+		return true;
+	else if (from.isProcedure() && to.isProcedure()){
+		var fromArgs = from.arguments();
+		var toArgs = to.arguments();
+		if (fromArgs.length == toArgs.length){
+			for(var i = 0; i < fromArgs.length; ++i){
+				var fromArg = fromArgs[i];
+				var toArg = toArgs[i];
+				if (toArg.isVar != fromArg.isVar)
+					return false;
+				if ((toArg.type != to || fromArg.type != from)
+					&& !implicitCast(fromArg.type, toArg.type))
+					return false;
+			}
+
+			var fromResult = from.result();
+			var toResult = to.result();
+			if (toResult == to && fromResult == from)
+				return true;
+			if (implicitCast(fromResult, toResult))
+				return true;
+		}
+	}
+	return false;
+}
+
+exports.implicit = implicitCast;

+ 37 - 0
code.js

@@ -0,0 +1,37 @@
+var Class = require("rtl.js").Class;
+
+var NullCodeGenerator = Class.extend({
+	init: function NullCodeGenerator(){},
+	write: 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;}
+});
+
+exports.nullGenerator = new NullCodeGenerator();

+ 1551 - 0
context.js

@@ -0,0 +1,1551 @@
+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 Parser = require("parser.js");
+var Procedure = require("procedure.js");
+var ImportRTL = require("rtl.js");
+var Stream = require("stream.js").Stream;
+var Type = require("type.js");
+
+var RTL = ImportRTL.RTL;
+var Class = ImportRTL.Class;
+
+var basicTypes = Type.basic;
+var Symbol = Type.Symbol;
+
+function getSymbol(context, id){
+	var s = context.findSymbol(id);
+	if (!s)
+		throw new Errors.Error("undeclared identifier: '" + id + "'");
+	return s;
+}
+
+function checkTypeCast(from, to, msg){
+	if (from instanceof Type.Pointer)
+		from = from.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);},
+	addSymbol: function(s){this.__parent.addSymbol(s);},
+	currentScope: function(s){return this.__parent.currentScope();},
+	pushScope: function(){this.__parent.pushScope();},
+	popScope: function(){this.__parent.popScope();},
+	setType: function(type){this.__parent.setType(type);},
+	setDesignator: function(d){this.__parent.setDesignator(d);},
+	handleExpression: function(type, value, designator){this.__parent.handleExpression(type, value, designator);},
+	handleLiteral: function(s){this.__parent.handleLiteral(s);},
+	handleConst: function(type, value){this.__parent.handleConst(type, value);},
+	genTypeName: function(){return this.__parent.genTypeName();},
+	genVarName: function(id){return this.__parent.genVarName(id);},
+	rtl: function(){return this.__parent.rtl();}
+});
+
+exports.Integer = ChainedContext.extend({
+	init: function IntegerContext(context){
+		ChainedContext.prototype.init.bind(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);},
+	endParse: function(){
+		var n = this.toInt();
+		this.parent().codeGenerator().write(n.toString());
+		this.parent().handleConst(basicTypes.int, n);
+	}
+});
+
+exports.HexInteger = exports.Integer.extend({
+	init: function HexIntegerContext(context){
+		exports.Integer.prototype.init.bind(this)(context);
+	},
+	toInt: function(s){return parseInt(this.__result, 16);}
+});
+
+exports.Real = ChainedContext.extend({
+	init: function RealContext(context){
+		ChainedContext.prototype.init.bind(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().codeGenerator().write(n.toString());
+		this.parent().handleConst(basicTypes.real, n);
+	}
+});
+
+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.bind(this)(context);
+		this.__result = "";
+	},
+	handleChar: function(c){this.__result += c;},
+	toStr: function(s){return s;},
+	endParse: function(){
+		var s = this.toStr(this.__result);
+		this.parent().codeGenerator().write(escapeString(s));
+		this.parent().handleConst(new Type.String(s), s);
+		}
+});
+
+exports.Char = exports.String.extend({
+	init: function CharContext(context){
+		exports.String.prototype.init.bind(this)(context);
+		this.__result = "";
+	},
+	toStr: function(s){return String.fromCharCode(parseInt(s, 16));}
+});
+
+exports.BaseType = ChainedContext.extend({
+	init: function BaseTypeContext(context){
+		ChainedContext.prototype.init.bind(this)(context);
+	},
+	setIdent: function(id){this.parent().setBaseType(id);}
+});
+
+var DesignatorInfo = Class.extend({
+	init: function(code, refCode, type, info){
+		this.__code = code;
+		this.__refCode = refCode;
+		this.__type = type;
+		this.__info = info;
+	},
+	code: function(){return this.__code;},
+	refCode: function(){return this.__refCode(this.__code);},
+	type: function(){return this.__type;},
+	info: function(){return this.__info;}
+});
+
+exports.Designator = ChainedContext.extend({
+	init: function DesignatorContext(context){
+		ChainedContext.prototype.init.bind(this)(context);
+		this.__currentType = undefined;
+		this.__info = undefined;
+		this.__code = new Code.SimpleGenerator();
+		this.__derefCode = undefined;
+		this.__propCode = undefined;
+	},
+	setIdent: function(id){
+		var t = this.__currentType;
+		if ( t === undefined){
+			var s = getSymbol(this.parent(), id);
+			var info = s.info();
+			if (s.isType())
+				this.__currentType = info;
+			else if (s.isVariable() || s.isConst() || s.isProcedure()){
+				this.__currentType = info.type();
+			}
+			else
+				throw new Errors.Error("variable, constant or procedure name expected");
+			this.__info = info;
+		}
+		else if (t instanceof Type.Pointer){
+			this.__handleDeref();
+			this.__denote(id);
+		}
+		else if (!(t instanceof Type.Record)
+			  && !(t instanceof Module.Type)
+			  && !(t instanceof Module.AnyType))
+			throw new Errors.Error("cannot designate '" + t.description() + "'");
+		else
+			this.__denote(id);
+
+		this.__code.write(id);
+	},
+	codeGenerator: function(){return this.__code;},
+	handleExpression: function(expType, value, designator){
+		if (expType != basicTypes.int)
+			throw new Errors.Error("'INTEGER' expression expected, got '" + expType.name() + "'");
+
+		var type = this.__currentType;
+		if (!(type instanceof Type.Array))
+			throw new Errors.Error("ARRAY expected, got '" + type.name() + "'");
+		if (value !== undefined && value >= type.arraySize())
+			throw new Errors.Error("index out of bounds: maximum possible index is "
+								 + (type.arraySize() - 1)
+								 + ", got " + value );
+		this.__currentType = type.elementsType();
+		this.__info = new Type.Variable(this.__currentType, false, this.__info.isReadOnly());
+		if (designator)
+			writeDerefDesignatorCode(designator, this.__code);
+	},
+	handleLiteral: function(s){
+		if (s == "]" || s == ","){
+			var indexCode = this.__code.result();
+			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();
+	},
+	__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();
+		this.__info = new Type.Variable(this.__currentType, false, false);
+	},
+	handleTypeCast: function(type){
+		if (!(type instanceof Type.Record))
+			throw new Errors.Error(
+				"invalid type cast: RECORD type expected as an argument of type guard, got '"
+			  + type.description() + "'");
+
+		checkTypeCast(this.__currentType, type, "invalid type cast");
+
+		var code = this.rtl().genCast(this.__code.result(), type);
+		this.__code = new Code.SimpleGenerator(code);
+
+		if (this.__currentType instanceof Type.Pointer)
+			type = new Type.Pointer(this.genTypeName(), type);
+		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(".");
+		this.__currentType = fieldType;
+	},
+	endParse: function(){
+		var code = this.__code.result();
+		this.parent().setDesignator(
+			new DesignatorInfo(code, this.__makeRefCode.bind(this), this.__currentType, this.__info));
+	},
+	__makeRefCode: function(code){
+		if (this.__derefCode)
+			return this.rtl().makeRef(this.__derefCode, this.__propCode);
+		if (!(this.__currentType instanceof Type.Array)
+			&& this.__info instanceof Type.Variable && !this.__info.isVar())
+			return "{set: function($v){" + code + " = $v;}, get: function(){return " + code + ";}}";
+		return code;
+	}
+});
+
+exports.Type = ChainedContext.extend({
+	init: function TypeContext(context){ChainedContext.prototype.init.bind(this)(context);},
+	setIdent: function(id){
+		var s = this.findSymbol(id);
+		if (!s)
+			throw new Errors.Error("undeclared type: '" + id + "'");
+		if (s instanceof Type.Type)
+			throw new Errors.Error("type name expected");
+		this.setType(s.info());
+	}
+});
+
+exports.FormalType = exports.Type.extend({
+	init: function FormatlTypeContext(context){
+		exports.Type.prototype.init.bind(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.bind(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);
+	},
+	setIdent: function(id){
+		var parent = this.parent();
+		var s = getSymbol(parent, id);
+		if (!s.isType())
+			throw new Errors.Error("type name expected");
+		this.__result = s.info();
+	},
+	endParse: function(){
+		this.__type.define(this.__arguments, this.__result);
+	}
+});
+
+exports.FormalParametersProcDecl = exports.FormalParameters.extend({
+	init: function FormalParametersProcDeclContext(context){
+		exports.FormalParameters.prototype.init.bind(this)(context);
+	},
+	addArgument: function(name, arg){
+		exports.FormalParameters.prototype.addArgument.bind(this)(name, arg);
+		this.parent().addArgument(name, arg);
+	},
+	endParse: function(){
+		exports.FormalParameters.prototype.endParse.bind(this)();
+		this.parent().endParameters();
+	}
+});
+
+exports.ProcDecl = ChainedContext.extend({
+	init: function ProcDeclContext(context){
+		ChainedContext.prototype.init.bind(this)(context);
+		this.__name = undefined;
+		this.__firstArgument = true;
+		this.__type = undefined;
+		this.__returnParsed = false;
+		this.__outerScope = this.parent().currentScope();
+	},
+	setIdent: function(id){
+		var gen = this.codeGenerator();
+		if (this.__name === undefined){ // first call
+			this.__name = id;
+			gen.write("\nfunction " + id + "(");
+			this.parent().pushScope();
+		}
+		else if (this.__name === id){
+			gen.closeScope();
+			this.parent().popScope();
+		}
+		else
+			throw new Errors.Error("mismatched procedure names: '" + this.__name
+								 + "' at the begining and '" + id + "' at the end");
+	},
+	typeName: function(){return undefined;},
+	setType: function(type){
+		var procSymbol = new Symbol(this.__name, new Type.Procedure(type));
+		this.__outerScope.addSymbol(procSymbol);
+		this.__type = type;
+	},
+	addArgument: function(name, arg){
+		if (name == this.__name)
+			throw new Errors.Error("argument '" + name + "' has the same name as procedure");
+		var readOnly = !arg.isVar && (arg.type instanceof Type.Array);
+		var s = new Symbol(name, new Type.Variable(arg.type, arg.isVar, readOnly));
+		this.parent().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.bind(this)(context);
+		this.__type = undefined;
+		this.__code = new Code.SimpleGenerator();
+	},
+	codeGenerator: function(){return this.__code;},
+	handleExpression: function(type, value, designator){
+		this.__type = type;
+		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 = ChainedContext.extend({
+	init: function ProcParamsContext(context){
+		ChainedContext.prototype.init.bind(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 PointerDeclContext(context){
+		ChainedContext.prototype.init.bind(this)(context);
+		this.__base = undefined;
+		this.__name = this.parent().genTypeName();
+	},
+	setType: function(type){
+		if (!(type instanceof Type.ForwardRecord) && !(type instanceof Type.Record))
+			throw new Errors.Error(
+				"RECORD is expected as a POINTER base type, got '" + type.description() + "'");
+		this.__base = type;
+	},
+	findSymbol: function(id){
+		var existing = this.parent().findSymbol(id);
+		if (existing)
+			return existing;
+
+		var resolve = function(){return getSymbol(this.__parent, id).info();};
+		return new Symbol(id, new Type.ForwardRecord(resolve.bind(this)));
+	},
+	genTypeName: function(){
+		return this.__name + "$base";
+	},
+	endParse: function(){
+		var type = new Type.Pointer(this.__name, this.__base);
+		this.parent().setType(type);
+	}
+});
+
+exports.ArrayDecl = ChainedContext.extend({
+	init: function ArrayDeclContext(context){
+		ChainedContext.prototype.init.bind(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() + ";}"
+			: type.initializer();
+		var dimensions = "";
+		for(var i = 0; i < this.__dimensions.length; ++i){
+			var length = this.__dimensions[i];
+			dimensions += (dimensions.length ? ", " : "") + length;
+			var arrayInit = i == this.__dimensions.length - 1
+				? this.rtl().makeArray(dimensions + ", " + initializer)
+				: undefined;
+			type = new Type.Array("ARRAY OF " + type.name()
+							   , arrayInit
+							   , type
+							   , length);
+		}
+
+		this.__type = type;
+	},
+	endParse: function(){this.parent().setType(this.__type);}
+});
+
+exports.ArrayDimensions = ChainedContext.extend({
+	init: function ArrayDimensionsContext(context){
+		ChainedContext.prototype.init.bind(this)(context);
+		this.__dimensions = [];
+	},
+	codeGenerator: function(){return Code.nullGenerator;},
+	handleExpression: function(type, value){
+		if (type !== basicTypes.int)
+			throw new Errors.Error("'INTEGER' constant expression expected, got '" + type.name() + "'");
+		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);
+	}
+});
+
+exports.AddOperator = ChainedContext.extend({
+	init: function AddOperatorContext(context){
+		ChainedContext.prototype.init.bind(this)(context);
+	},
+	handleLiteral: function(s){
+		var parent = this.parent();
+		if (s == "+"){
+			if (parent.type() == basicTypes.set){
+				parent.handleBinaryOperator(function(x, y){return x | y;});
+				parent.codeGenerator().write(" | ");
+			}
+			else {
+				parent.handleBinaryOperator(function(x, y){return x + y;});
+				parent.codeGenerator().write(" + ");
+			}
+		}
+		else if (s == "-"){
+			if (parent.type() == basicTypes.set){
+				parent.handleBinaryOperator(function(x, y){return x & ~y;});
+				parent.codeGenerator().write(" & ~");
+			}
+			else {
+				parent.handleBinaryOperator(function(x, y){return x - y;});
+				parent.codeGenerator().write(" - ");
+			}
+		}
+		else if (s == "OR"){
+			var type = parent.type();
+			if (type != basicTypes.bool)
+				throw new Errors.Error("BOOLEAN expected as operand of 'OR', got '"
+									 + type.name() + "'");
+			parent.handleBinaryOperator(function(x, y){return x || y;});
+			this.codeGenerator().write(" || ");
+		}
+	}
+});
+
+exports.MulOperator = ChainedContext.extend({
+	init: function MulOperatorContext(context){
+		ChainedContext.prototype.init.bind(this)(context);
+	},
+	handleLiteral: function(s){
+		var parent = this.parent();
+		if (s == "*")
+			if (parent.type() == basicTypes.set)
+				parent.handleOperator({
+					  eval: function(x, y){return x & y;}
+					, code: function(x, y){return x + " & " + y;}
+					});
+			else
+				parent.handleOperator({
+					  eval: function(x, y){return x * y;}
+					, code: function(x, y){return x + " * " + y;}
+					});
+		else if (s == "/")
+			if (parent.type() == basicTypes.set)
+				parent.handleOperator({
+					  eval: function(x, y){return x ^ y;}
+					, code: function(x, y){return x + " ^ " + y;}
+					});
+			else
+				parent.handleOperator({
+					  eval: function(x, y){return x / y;}
+					, code: function(x, y){return x + " / " + y;}
+					});
+		else if (s == "DIV")
+			parent.handleOperator({
+				  eval: function(x, y){return (x / y) >> 0;}
+				, code: function(x, y){return "(" + x + " / " + y + ") >> 0";}
+				});
+		else if (s == "MOD")
+			parent.handleOperator({
+				  eval: function(x, y){return x % y;}
+				, code: function(x, y){return x + " % " + y;}
+				});
+		else if (s == "&"){
+			var type = parent.type();
+			if (type != basicTypes.bool)
+				throw new Errors.Error("BOOLEAN expected as operand of '&', got '"
+									 + type.name() + "'");
+			parent.handleOperator({
+				  eval: function(x, y){return x && y;}
+				, code: function(x, y){return x + " && " + y;}
+				});
+		}
+	}
+});
+
+function writeDerefDesignatorCode(designator, code){
+	var info = designator.info();
+	if (info instanceof Type.Variable && info.isVar())
+		code.write(".get()");
+}
+
+exports.Term = ChainedContext.extend({
+	init: function TermContext(context){
+		ChainedContext.prototype.init.bind(this)(context);
+		this.__operator = undefined;
+		this.__code = new Code.SimpleGenerator();
+		this.__left = undefined;
+		this.__isConst = true;
+		this.__value = undefined;
+		this.__designator = undefined;
+	},
+	codeGenerator: function(){return this.__code;},
+	type: function(){return this.parent().type();},
+	setDesignator: function(d){
+		var type = d.type();
+		this.parent().setType(type);
+
+		var info = d.info();
+		if (!(info instanceof Type.Const))
+			this.__isConst = false;
+		else
+			this.handleConst(type, info.value());
+		
+		this.__code.write(d.code());
+		this.__designator = d;
+		if (this.__operator)
+			this.__derefDesignator();
+	},
+	handleOperator: function(o){
+		this.__derefDesignator();
+		this.__left = this.__operator
+			? this.__operator.code(this.__left, this.__code.result())
+			: this.__code.result();
+		this.__operator = o;
+		this.__code = new Code.SimpleGenerator();
+	},
+	handleConst: function(type, value){
+		this.parent().setType(type);
+		if (value === undefined)
+			this.__isConst = false;
+		else if (this.__isConst)
+			this.__value = this.__operator ? this.__operator.eval(this.__value, value)
+										   : value;
+	},
+	procCalled: function(type){this.parent().procCalled(type);},
+	endParse: function(){
+		var code = this.__operator ? this.__operator.code(this.__left, this.__code.result())
+								   : this.__code.result();
+		this.parent().handleTerm(
+			  code
+			, this.__isConst ? this.__value : undefined
+			, this.__operator ? undefined : this.__designator);
+	},
+	__derefDesignator: function(){
+		var designator = this.__designator;
+		if (!designator)
+			return;
+
+		writeDerefDesignatorCode(designator, this.__code);
+		this.__designator = undefined;
+	}
+});
+
+exports.Factor = ChainedContext.extend({
+	init: function FactorContext(context){
+		ChainedContext.prototype.init.bind(this)(context);
+	},
+	type: function(){return this.parent().type();},
+	handleLiteral: function(s){
+		var parent = this.parent();
+		if (s == "NIL"){
+			parent.handleConst(Type.nil, undefined);
+			this.codeGenerator().write("null");
+		}
+		else if (s == "TRUE"){
+			parent.handleConst(basicTypes.bool, true);
+			this.codeGenerator().write("true");
+		}
+		else if (s == "FALSE"){
+			parent.handleConst(basicTypes.bool, false);
+			this.codeGenerator().write("false");
+		}
+		else if (s == "~"){
+			parent.setType(basicTypes.bool);
+			parent.handleOperator({
+				  eval: function(x, y){return !y;}
+				, code: function(x, y){return "!" + y;}
+				});
+		}
+	},
+	procCalled: function(type){this.parent().procCalled(type);}
+});
+
+exports.Set = ChainedContext.extend({
+	init: function SetContext(context){
+		ChainedContext.prototype.init.bind(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 gen = this.codeGenerator();
+		if (!this.__expr.length){
+			gen.write(this.__value.toString());
+			this.parent().handleConst(basicTypes.set, this.__value);
+		}
+		else{
+			this.parent().setType(basicTypes.set);
+			gen.write(this.rtl().makeSet(this.__expr));
+			if (this.__value)
+				gen.write(" | " + this.__value);
+		}
+	}
+});
+
+exports.SetElement = ChainedContext.extend({
+	init: function SetElementContext(context){
+		ChainedContext.prototype.init.bind(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(type, value){
+		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.bind(this)(context);
+		//this.__type = undefined;
+		this.__binaryOperator = undefined;
+		this.__unaryMinus = false;
+		this.__unaryPlus = false;
+		this.__isConst = true;
+		this.__constValue = undefined;
+		this.__designator = undefined;
+		this.__code = new Code.SimpleGenerator();
+	},
+	codeGenerator: function(){return this.__code;},
+	handleTerm: function(code, value, designator){
+		if (value !== undefined)
+			this.__handleConst(value);
+		else
+			this.__isConst = false;
+		this.codeGenerator().write(code);
+		this.__designator = designator;
+		this.__derefDesignator();
+	},
+	handleLiteral: function(s){
+		if (s == "-")
+			this.__unaryMinus = true;
+		else if (s == "+")
+			this.__unaryPlus = true;
+	},
+	type: function(){return this.parent().type();},
+	constValue: function(){return this.__isConst ? this.__constValue : undefined;},
+	handleBinaryOperator: function(o){
+		this.__binaryOperator = o;
+		this.__derefDesignator();
+	},
+	handleUnaryOperator: function(o){
+		this.__unary_operator = o;
+	},
+	procCalled: function(type){this.parent().procCalled(type);},
+	endParse: function(){
+		var parent = this.parent();
+		var code = parent.codeGenerator();
+		if (this.__unaryMinus)
+			if (this.type() == basicTypes.set){
+				if (this.__isConst)
+					this.__constValue = ~this.__constValue;
+				else
+					code.write('~');
+			}
+			else {
+				if (this.__isConst)
+					this.__constValue = -this.__constValue;
+				else
+					code.write('-');
+			}
+
+		code.write(this.__isConst ? constValueCode(this.__constValue) : this.__code.result());
+		parent.handleSimpleExpression(this.constValue(), this.__designator);
+	},
+	__handleConst: function(value){
+		if (!this.__isConst)
+			return;
+
+		if (this.__unary_operator){
+			value = this.__unary_operator(value);
+			this.__unary_operator = undefined;
+		}
+
+		if (!this.__binaryOperator)
+			this.__constValue = value;
+		else
+			this.__constValue = this.__binaryOperator(this.__constValue, value);
+	},
+	__derefDesignator: function(){
+		if (!this.__designator)
+			return;
+		if (!this.__binaryOperator && !this.__unary_operator
+			&& !this.__unaryMinus && !this.__unaryPlus)
+			return;
+
+		writeDerefDesignatorCode(this.__designator, this.__code);
+		this.__designator = undefined;
+	}
+});
+
+exports.Expression = ChainedContext.extend({
+	init: function ExpressionContext(context){
+		ChainedContext.prototype.init.bind(this)(context);
+		this.__leftParsed = false;
+		this.__type = undefined;
+		this.__relation = undefined;
+		this.__value = undefined;
+		this.__designator = undefined;
+		this.__code = new Code.SimpleGenerator();
+	},
+	setType: function(type){
+		if (this.__relation == "IS"){
+			if (!(type instanceof Type.Record))
+				throw new Errors.Error("RECORD type expected after 'IS'");
+			
+			checkTypeCast(this.__type, type, "invalid type test");
+		}
+		else if (type === undefined || this.__type === undefined)
+			this.__type = type;
+		else if (type !== this.__type)
+			throw new Errors.Error("type mismatch: expected '" + this.__type.name()
+								 + "', got '" + type.name() + "'");
+	},
+	type: function(){return this.__type;},
+	codeGenerator: function(){return this.__code;},
+	handleSimpleExpression: function(value, designator){
+		if (!this.__leftParsed){
+			this.__leftParsed = true;
+			this.__value = value;
+			this.__designator = designator;
+		}
+		else {
+			if (this.__relation == "IS"){
+				if (!designator || !(designator.info() instanceof Type.Type))
+					throw new Errors.Error("type name expected");
+			}
+			this.__type = basicTypes.bool;
+			this.__value = undefined;
+			this.__designator = undefined;
+		}
+	},
+	procCalled: function(type){
+		if (!type)
+			throw new Errors.Error("procedure returning no result cannot be used in an expression");
+		this.__type = type;
+		this.__designator = undefined;
+	},
+	handleLiteral: function(relation){
+		if (relation == "IS")
+			if (!(this.__type instanceof Type.Pointer))
+				throw new Errors.Error("POINTER to type expected before 'IS'");
+			else
+				this.codeGenerator().write(" instanceof ");
+		else if (relation == "IN"){
+			if (this.__type != basicTypes.int)
+				throw new Errors.Error("'INTEGER' expected as an element of SET, got '" + this.__type.name() + "'");
+			this.__type = basicTypes.set;
+			this.__code = new Code.SimpleGenerator("1 << " + this.__code.result() + " & ");
+		}
+		else if (relation == "=")
+			this.__code = new Code.SimpleGenerator(this.__code.result() + " == ");
+		else if (relation == "#")
+			this.__code = new Code.SimpleGenerator(this.__code.result() + " != ");
+		else if (relation == "<=" || relation == ">=")
+			this.__code.write(", ");
+
+		this.__relation = relation;
+	},
+	endParse: function(){
+		var parent = this.parent();
+		var code = parent.codeGenerator();
+		if (this.__relation == "<=")
+			code.write(this.rtl().setInclL(this.__code.result()));
+		else if (this.__relation == ">=")
+			code.write(this.rtl().setInclR(this.__code.result()));
+		else
+			code.write(this.__code.result());
+		parent.handleExpression(this.__type, this.__value, this.__designator);
+	}
+});
+
+function handleIfExpression(type){
+	if (type !== basicTypes.bool)
+		throw new Errors.Error("'BOOLEAN' expression expected, got '" + type.name() + "'");
+}
+
+function endIfParse(){
+	var gen = this.codeGenerator();
+	gen.write(")");
+	gen.openScope();
+}
+
+exports.If = ChainedContext.extend({
+	init: function IfContext(context){
+		ChainedContext.prototype.init.bind(this)(context);
+		this.codeGenerator().write("if (");
+	},
+	handleExpression: handleIfExpression,
+	endParse: endIfParse
+});
+
+exports.ElseIf = ChainedContext.extend({
+	init: function ElseIfContext(context){
+		ChainedContext.prototype.init.bind(this)(context);
+		var gen = this.codeGenerator();
+		gen.closeScope();
+		gen.write("else if (");
+	},
+	handleExpression: handleIfExpression,
+	endParse: endIfParse
+});
+
+exports.Else = ChainedContext.extend({
+	init: function ElseContext(context){
+		ChainedContext.prototype.init.bind(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.bind(this)(context);
+		this.__type = undefined;
+		this.__firstCase = true;
+		this.genVarName("$c");
+		this.codeGenerator().write("$c = ");
+	},
+	handleExpression: function(type){
+		var gen = this.codeGenerator();
+		if (type instanceof Type.String){
+			var v = type.asChar();
+			if (v !== undefined){
+				gen.write(v);
+				type = basicTypes.char;
+			}
+		}
+		if (type != basicTypes.int && type != basicTypes.char)
+			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.bind(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.bind(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.bind(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.char;
+		}
+		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.bind(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.bind(this)(context);
+		var gen = context.codeGenerator();
+		gen.write("do ");
+		gen.openScope();
+	}
+});
+
+exports.Until = ChainedContext.extend({
+	init: function UntilContext(context){
+		ChainedContext.prototype.init.bind(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.bind(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.int)
+			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(type, value){
+		if (type !== basicTypes.int)
+			throw new Errors.Error(
+				!this.__initExprParsed
+					? "'INTEGER' expression expected to assign '" + this.__var
+					  + "', got '" + type.name() + "'"
+					: !this.__toParsed
+					? "'INTEGER' expression expected as 'TO' parameter, got '" + type.name() + "'"
+					: "'INTEGER' expression expected as 'BY' parameter, got '" + type.name() + "'"
+					);
+		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.Assignment = ChainedContext.extend({
+	init: function AssignmentContext(context){
+		ChainedContext.prototype.init.bind(this)(context);
+		this.__designator = undefined;
+		this.__leftOp = undefined;
+		this.__type = undefined;
+	},
+	setDesignator: function(d){
+		this.__designator = d;
+	},
+	handleLiteral: function(){
+		var d = this.__designator;
+		var d_info = d.info();
+		if (!(d_info instanceof Type.Variable) || d_info.isReadOnly())
+			throw new Errors.Error("cannot assign to " + d_info.idType());
+		this.__leftOp = d.code();
+		this.__type = d.type();
+		this.codeGenerator().write(this.__leftOp + (d_info.isVar() ? ".set(" : " = "));
+	},
+	handleExpression: function(type, value, designator){
+		if (!Cast.implicit(type, this.__type))
+			throw new Errors.Error("type mismatch: '" + this.__leftOp
+								 + "' is '" + this.__type.description()
+								 + "' and cannot be assigned to '" + type.description() + "' expression");
+		if (designator)
+			writeDerefDesignatorCode(designator, this.codeGenerator());
+	},
+	endParse: function(){
+		if (this.__designator.info().isVar())
+			this.codeGenerator().write(")");
+	}
+});
+
+exports.ConstDecl = ChainedContext.extend({
+	init: function ConstDeclContext(context){
+		ChainedContext.prototype.init.bind(this)(context);
+		this.__id = undefined;
+		this.__type = undefined;
+		this.__value = undefined;
+	},
+	setIdent: function(id){
+		this.__id = id;
+		this.codeGenerator().write("var " + id + " = ");
+	},
+	handleExpression: function(type, value){
+		if (value === undefined)
+			throw new Errors.Error("constant expression expected");
+		this.__type = type;
+		this.__value = value;
+	},
+	endParse: function(){
+		var c = new Type.Const(this.__type, this.__value);
+		this.addSymbol(new Symbol(this.__id, c));
+		this.codeGenerator().write(";\n");
+	}
+});
+
+exports.VariableDeclaration = ChainedContext.extend({
+	init: function VariableDeclarationContext(context){
+		ChainedContext.prototype.init.bind(this)(context);
+		this.__idents = [];
+		this.__type = undefined;
+	},
+	setIdent: function(id) {this.__idents.push(id);},
+	setType: function(type) {this.__type = type;},
+	typeName: function(){return undefined;},
+	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 varName = idents[i];
+			this.addSymbol(new Symbol(varName, v));
+			var t = v.type();
+			gen.write("var " + varName + " = " + t.initializer() + ";");
+		}
+
+		gen.write("\n");
+	}
+});
+
+exports.FieldListDeclaration = ChainedContext.extend({
+	init: function FieldListDeclarationContext(context){
+		ChainedContext.prototype.init.bind(this)(context);
+		this.__idents = [];
+		this.__type = undefined;
+	},
+	setIdent: function(id) {this.__idents.push(id);},
+	setType: function(type) {this.__type = type;},
+	endParse: function(){
+		var idents = this.__idents;
+		for(var i = 0; i < idents.length; ++i){
+			var fieldName = idents[i];
+			var fieldType = this.__type;
+			this.parent().addField(fieldName, fieldType);
+		}
+	}
+});
+
+function assertProcType(type){
+	if (!(type instanceof Procedure.Type) && !(type instanceof Module.AnyType))
+		throw new Errors.Error("PROCEDURE expected, got '" + type.name() + "'");
+}
+
+exports.ActualParameters = ChainedContext.extend({
+	init: function ActualParametersContext(context){
+		ChainedContext.prototype.init.bind(this)(context);
+		this.parent().hasActualParameters();
+	},
+});
+
+exports.ProcedureCall = ChainedContext.extend({
+	init: function ProcedureCallContext(context){
+		ChainedContext.prototype.init.bind(this)(context);
+		this.__type = undefined;
+		this.__procCall = undefined;
+		this.__code = undefined;
+	},
+	setDesignator: function(d){
+		var type = d.type();
+		assertProcType(type);
+		this.__type = type;
+		this.__procCall = type.callGenerator(new Code.SimpleGenerator(), d.code());
+		this.__code = new Code.SimpleGenerator();
+	},
+	codeGenerator: function(){
+		return this.__code ? this.__code : this.parent().codeGenerator();
+	},
+	type: function(){return this.__type;},
+	setType: function(){},
+	hasActualParameters: function(){},
+	handleExpression: function(type, value, designator){
+		var code = this.__code.result();
+		this.__code = new Code.SimpleGenerator();
+		this.__procCall.handleArgument(type, designator, code);
+	},
+	endParse: function(){this.parent().codeGenerator().write(this.__procCall.end());}
+});
+
+exports.ExpressionProcedureCall = exports.ProcedureCall.extend({
+	init: function ExpressionProcedureCallContext(context){
+		exports.ProcedureCall.prototype.init.bind(this)(context);
+		this.__designator = undefined;
+		this.__hasActualParameters = false;
+	},
+	setDesignator: function(d){
+		this.__designator = d;
+	},
+	hasActualParameters: function(){
+		exports.ProcedureCall.prototype.setDesignator.bind(this)(this.__designator);
+		this.__hasActualParameters = true;
+	},
+	endParse: function(){
+		if (this.__hasActualParameters){
+			exports.ProcedureCall.prototype.endParse.bind(this)();
+			this.parent().procCalled(this.__type.result());
+		}
+		else
+			this.parent().setDesignator(this.__designator);
+	}
+});
+
+exports.RecordDecl = ChainedContext.extend({
+	init: function RecordDeclContext(context){
+		ChainedContext.prototype.init.bind(this)(context);
+		var id = this.genTypeName();
+		this.__type = new Type.Record(id);
+		var gen = this.codeGenerator();
+		gen.write("var " + id + " = ");
+	},
+	addField: function(name, type) {this.__type.addField(name, type);},
+	setBaseType: function(id){
+		var s = getSymbol(this.parent(), id);
+		if (!s.isType())
+			throw new Errors.Error("type name expected");
+		this.__type.setBaseType(s.info());
+	},
+	endParse: function(){
+		var type = this.__type;
+		var baseType = type.baseType();
+		var gen = this.codeGenerator();
+		gen.write((baseType ? baseType.name() : this.rtl().baseClass()) + ".extend(");
+		gen.openScope();
+		gen.write("init: function " + type.name() + "()");
+		gen.openScope();
+		if (baseType)
+			gen.write(baseType.name() + ".prototype.init.bind(this)();\n");
+		var ownFields = type.ownFields();
+		for(var f in ownFields)
+			gen.write("this." + f + " = " + ownFields[f].initializer() + ";\n");
+
+		this.parent().setType(type);
+		gen.closeScope();
+		gen.closeScope(");\n");
+	}
+});
+
+exports.TypeDeclaration = ChainedContext.extend({
+	init: function TypeDeclarationContext(context){
+		ChainedContext.prototype.init.bind(this)(context);
+		this.__id = undefined;
+	},
+	setIdent: function(id){this.__ident = id;},
+	setType: function(type){
+		this.addSymbol(new Symbol(this.__ident, type));
+	},
+	typeName: function(){return this.__ident;},
+	genTypeName: function(){return this.__ident;},
+	type: function(){return this.parent().type();}
+});
+
+exports.TypeCast = ChainedContext.extend({
+	init: function TypeCastContext(context){
+		ChainedContext.prototype.init.bind(this)(context);
+		this.__type = undefined;
+	},
+	setIdent: function(id){
+		var s = getSymbol(this.parent(), id);
+		if (!s.isType())
+			return; // this is not a type cast, may be procedure call
+		this.__type = s.info();
+	},
+	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.bind(this)(context);
+		this.__name = undefined;
+	},
+	setIdent: function(id){
+		var gen = this.codeGenerator();
+		if (this.__name === undefined )	{
+			this.__name = id;
+			this.addSymbol(new Symbol(id, Type.module));
+			gen.write("var " + id + " = function " + "(){\n");
+		}
+		else if (id === this.__name)
+			gen.write("}();");
+		else
+			throw new Errors.Error("original module name '" + this.__name + "' expected, got '" + id + "'" );
+	}
+});
+
+var ModuleImport = ChainedContext.extend({
+	init: function ModuleImport(context){
+		ChainedContext.prototype.init.bind(this)(context);
+	},
+	setIdent: function(id){
+		if (id == "JS"){
+			this.rtl().supportJS();
+			this.addSymbol(new Symbol("JS", new Module.JS()));
+		}
+	}
+});
+exports.ModuleImport = ModuleImport;
+
+var Scope = Class.extend({
+	init: function Scope(){
+		var symbols = {};
+		for(var t in basicTypes){
+			var type = basicTypes[t];
+			symbols[type.name()] = new Symbol(type.name(), type);
+		}
+		symbols["LONGREAL"] = new Symbol("LONGREAL", basicTypes.real);
+		
+		var predefined = Procedure.predefined;
+		for(var i = 0; i < predefined.length; ++i){
+			var s = predefined[i];
+			symbols[s.id()] = s;
+		}
+			
+		this.__symbols = symbols;
+	},
+	addSymbol: function(symbol){
+		var id = symbol.id();
+		if (this.findSymbol(id))
+			throw new Errors.Error( "'" + id + "' already declared");
+		this.__symbols[id] = symbol;
+	},
+	findSymbol: function(ident){return this.__symbols[ident];}
+});
+
+exports.Context = Class.extend({
+	init: function Context(){
+		this.__code = new Code.Generator();
+		this.__designator = undefined;
+		this.__type = undefined;
+		this.__scopes = [new Scope()];
+		this.__gen = 0;
+		this.__vars = [];
+		this.__rtl = new RTL();
+	},
+	setDesignator: function(d){this.__designator = d;},
+	//designator: function(id){return this.__designator;},
+	type: function(){return this.__type;},
+	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);
+		}
+	},
+	addSymbol: function(symbol){this.currentScope().addSymbol(symbol);},
+	findSymbol: function(ident){
+		for(var i = this.__scopes.length; i--;){
+			var s = this.__scopes[i].findSymbol(ident);
+			if (s)
+				return s;
+		}
+		return undefined;
+	},
+	currentScope: function(){return this.__scopes[this.__scopes.length - 1];},
+	pushScope: function(){this.__scopes.push(new Scope());},
+	popScope: function(){this.__scopes.pop();},
+	handleExpression: function(){},
+	handleLiteral: function(){},
+	getResult: function(){
+		return this.__rtl.generate() + this.__code.getResult();
+	},
+	codeGenerator: function(){return this.__code;},
+	rtl: function(){
+		return this.__rtl;
+	}
+});

+ 6 - 0
errors.js

@@ -0,0 +1,6 @@
+var Class = require("rtl.js").Class;
+
+exports.Error = Class.extend({
+	init: function CompileError(msg) {this.__msg = msg;},
+	toString: function(){return this.__msg;}
+});

+ 189 - 0
grammar.js

@@ -0,0 +1,189 @@
+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 selector = or(and(point, ident)
+				// break recursive declaration of expList
+			    , and("[", function(stream, context){return expList(stream, context);}, "]")
+				, "^"
+				, context(and("(", ident, ")"), Context.TypeCast)
+			    );
+var designator = context(and(ident, repeat(selector)), Context.Designator);
+var type = or(function(stream, context){return strucType(stream, context);} // break recursive declaration of strucType
+			, context(ident, Context.Type));
+var identList = and(ident, repeat(and(",", ident)));
+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(and("\"", repeat(character), required("\"", "unexpected end of 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 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.ProcedureCall);
+
+var assignment = context(and(designator, ":=", required(expression, "expression expected"))
+					   , Context.Assignment);
+
+var statement = 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, "END")
+						 , 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(ident, 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")), ident), 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))
+		, ")"
+		, optional(and(":", ident)));
+
+var procedureType = and("PROCEDURE"
+					  , context(optional(formalParameters), Context.FormalParameters)
+					    );
+var strucType = or(arrayType, recordType, pointerType, procedureType);
+var typeDeclaration = context(and(ident, "=", strucType), Context.TypeDeclaration);
+
+var procedureHeading = and("PROCEDURE"
+						 , ident
+						 , 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(ident, "=", constExpression), Context.ConstDecl);
+
+var declarationSequence = and(optional(and("CONST", repeat(and(constantDeclaration, ";"))))
+							, optional(and("TYPE", repeat(and(typeDeclaration, ";"))))
+							, optional(and("VAR", repeat(and(variableDeclaration, ";"))))
+							, repeat(and(procedureDeclaration, ";")));
+var procedureBody = and(declarationSequence
+					  , optional(and("BEGIN", statementSequence))
+					  , optional(context(and("RETURN", expression), Context.Return))
+					  , "END");
+
+var imprt = and(ident, optional(and(":=", ident)));
+var importList = context(and("IMPORT", imprt, repeat(and(",", imprt))),
+						 Context.ModuleImport);
+var module = context(and("MODULE", ident, ";",
+						 optional(and(importList, ";")),
+						 declarationSequence,
+						 optional(and("BEGIN", statementSequence)),
+						 "END", ident, point),
+					 Context.ModuleDeclaration);
+
+exports.declarationSequence = declarationSequence;
+exports.expression = expression;
+exports.ident = ident;
+exports.module = module;
+exports.procedureBody = procedureBody;
+exports.procedureDeclaration = procedureDeclaration;
+exports.procedureHeading = procedureHeading;
+exports.statement = statement;
+exports.typeDeclaration = typeDeclaration;
+exports.variableDeclaration = variableDeclaration;

+ 104 - 0
lexer.js

@@ -0,0 +1,104 @@
+var Errors = require("errors.js");
+
+function isDigit(c) {return c >= '0' && c <= '9';}
+
+exports.digit = function(stream, context){
+	var c = stream.char();
+	if (!isDigit(c))
+		return false;
+	context.handleChar(c);
+	return true;
+}
+
+exports.hexDigit = function(stream, context){
+	var c = stream.char();
+	if (!isDigit(c) && (c < 'A' || c > 'F'))
+		return false;
+	context.handleChar(c);
+	return true;
+}
+
+exports.point = function(stream, context){
+	if (stream.char() != '.' 
+		|| stream.peekChar() == '.') // not a diapason ".."
+		return false;
+	context.handleLiteral(".");
+	return true;
+}
+
+exports.character = function(stream, context){
+	var c = stream.char();
+	if (c == '"')
+		return false;
+	context.handleChar(c);
+	return true;
+}
+
+function isLetter(c) { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');}
+
+exports.ident = function(stream, context){
+	if (!isLetter(stream.peekChar()))
+		return false;
+	
+	var savePos = stream.pos();
+	var result = "";
+	stream.read(function(c){
+		if (!isLetter(c) && !isDigit(c) /*&& c != '_'*/)
+			return false;
+		result += c;
+		return true;
+	});
+
+	var keywords = ["ARRAY", "END", "VAR", "TYPE", "IF", "CASE", "WHILE", "REPEAT", "FOR", "NIL", "TRUE", "FALSE", "IS"];
+	if (keywords.indexOf(result) != -1){
+		stream.setPos(savePos);
+		return false;
+	}
+	context.setIdent(result);
+	return true;
+}
+
+function skipComment(stream){
+	if (stream.peekStr(2) != "(*")
+		return false;
+
+	stream.next(2);
+	while (stream.peekStr(2) != "*)"){
+		if (stream.eof())
+			throw new Errors.Error("comment was not closed");
+		if (!skipComment(stream))
+			stream.next(1);
+		}
+	stream.next(2);
+	return true;
+}
+
+exports.skipSpaces = function(stream, context){
+	if (context.isLexem && context.isLexem())
+		return;
+
+	stream.read(function(c){return ' \t\n\r'.indexOf(c) != -1;});
+	skipComment(stream);
+}
+
+exports.separator = function(stream, context){
+	return !isLetter(stream.peekChar());
+}
+
+exports.literal = function(s){
+	return function(stream, context){
+		if (stream.str(s.length) != s)
+			return false;
+		stream.next(s.length);
+		
+		if ((!context.isLexem || !context.isLexem()) && isLetter(s[s.length - 1])){
+			var next = stream.peekChar();
+			if (isLetter(next) || isDigit(next))
+				return false;
+		}
+		
+		context.handleLiteral(s);
+		return true;
+	}
+}	
+

+ 33 - 0
module.js

@@ -0,0 +1,33 @@
+var Procedure = require("procedure.js");
+var Type = require("type.js");
+
+var AnyType = Type.Basic.extend({
+	init: function AnyType(){
+		Type.Basic.prototype.init.call(this, "ANY");
+	},
+	findSymbol: function(){return this;},
+	callGenerator: function(codegenerator, id){
+		return new Procedure.CallGenerator(codegenerator, id);
+	}
+});
+
+var any = new AnyType();
+
+var Module = Type.Basic.extend({
+	init: function(){
+		Type.Basic.prototype.init.call(this, "MODULE");
+	}
+});
+
+var JSModule = Module.extend({
+	init: function(){
+		Module.prototype.init.call(this);
+	},
+	findSymbol: function(id){
+		return any;
+	}
+});
+
+exports.AnyType = AnyType;
+exports.JS = JSModule;
+exports.Type = Module;

+ 26 - 0
oc.js

@@ -0,0 +1,26 @@
+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 Stream = require("stream.js").Stream;
+
+exports.compile = function(text){
+	var stream = new Stream(text);
+	var context = new Context.Context();
+	try {
+		if (!Grammar.module(stream, context))
+			throw new Errors.Error("syntax error, position: " + stream.pos());
+	}
+	catch (x) {
+		if (x instanceof Errors.Error) {
+			console.log(context.getResult());
+			console.error(stream.describePosition());
+		}
+		throw x;
+	}
+	Lexer.skipSpaces(stream, context);	
+	if (!stream.eof())
+		throw new Errors.Error("text beyond module end");
+	return context.getResult();
+}

+ 100 - 0
parser.js

@@ -0,0 +1,100 @@
+var assert = require("assert.js").ok;
+var Errors = require("errors.js");
+var Lexer = require("lexer.js");
+
+exports.and = function(/*...*/){
+	var args = arguments;
+	assert(args.length >= 2);
+
+	return function(stream, context){
+		var savePos = stream.pos();
+		for(var i = 0; i < args.length; ++i){
+			if (i != 0)
+				Lexer.skipSpaces(stream, context);
+			
+			var p = args[i];
+			if (typeof p == "string")
+				p = Lexer.literal(p);
+			
+			if (!p(stream, context)){
+				stream.setPos(savePos);
+				return false;
+			}
+		}
+	 	return true;
+	}
+}
+
+exports.or = function(/*...*/){
+	var args = arguments;
+	assert(args.length >= 2);
+
+	return function(stream, context){
+		for(var i = 0; i < args.length; ++i){
+			var p = args[i];
+			if (typeof p == "string")
+				p = Lexer.literal(p);
+			
+			var savePos = stream.pos();
+			if (p(stream, context))
+				return true;
+			stream.setPos(savePos);
+		}
+		return false;
+	}
+}
+
+exports.repeat = function(p){
+	return function(stream, context){
+			var savePos = stream.pos();
+			while (!stream.eof() && p(stream, context)){
+				Lexer.skipSpaces(stream, context);
+				savePos = stream.pos();
+			}
+			stream.setPos(savePos);
+			return true;
+		}
+}
+
+exports.optional = function(p){
+	assert(arguments.length == 1);
+	return function(stream, context){
+		var savePos = stream.pos();
+		if ( !p(stream, context))
+			stream.setPos(savePos);
+		return true;
+		}
+}
+
+exports.required = function(parser, error){
+	if (typeof(parser) === "string")
+		parser = Lexer.literal(parser);
+	return function(stream, context){
+		if (!parser(stream, context))
+			throw new Errors.Error(error);
+		return true;
+	}
+}
+
+exports.context = function(parser, contextFactory){
+	return function(stream, context){
+		var context = new contextFactory(context);
+		if (!parser(stream, context))
+			return false;
+		if (context.endParse)
+			return context.endParse() !== false;
+		return true;
+	}
+}
+
+exports.emit = function(parser, action){
+	assert(action);
+	if (typeof(parser) === "string")
+		parser = Lexer.literal(parser);
+	return function(stream, context){
+		if (!parser(stream, context))
+			return false;
+		action(context);
+		return true;
+	}
+}

+ 214 - 0
procedure.js

@@ -0,0 +1,214 @@
+//var assert = require("assert").ok;
+var Cast = require("cast.js");
+var Class = require("rtl.js").Class;
+var Code = require("code.js");
+var Errors = require("errors.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 ProcCallGenerator = Class.extend({
+	init: function ProcCallGenerator(codeGenerator, id, type){
+		this.__codeGenerator = codeGenerator;
+		this.__id = id;
+		this.__type = type;
+		this.__argumentsCount = 0;
+		this.writeCode(this.prolog());
+	},
+	id: function(){return this.__id;},
+	handleArgument: function(type, designator, code){
+		var pos = this.__argumentsCount++;
+		var isVarArg = false;
+		if (this.__type){
+			var expectedArguments = this.__type.arguments();
+			if (pos >= expectedArguments.length )
+				// ignore, handle error after parsing all arguments
+				return;
+			
+			var arg = this.checkArgument(pos, type, designator);
+			isVarArg = arg.isVar;
+		}
+		if (designator){
+			var info = designator.info();
+			if (info instanceof Type.Variable)
+				if (info.isVar() && !isVarArg)
+					code += ".get()";
+				else if (!info.isVar() && isVarArg)
+					code = designator.refCode();
+			}
+		var prefix = pos ? ", " : "";
+		this.writeCode(prefix + code);
+	},
+	end: function(){
+		if (this.__type){
+			var procArgs = this.__type.arguments();
+			if (this.__argumentsCount != procArgs.length)
+				throw new Errors.Error(procArgs.length + " argument(s) expected, got "
+									 + this.__argumentsCount);
+		}
+		this.writeCode(this.epilog());
+		return this.__codeGenerator.result();
+	},
+	prolog: function(){return this.__id + "(";},
+	checkArgument: function(pos, type, designator){
+		var arg = this.__type.arguments()[pos];
+		var expectType = arg.type; // can be undefined for predefined functions (like NEW), dont check it in this case
+		if (expectType && !Cast.implicit(type, expectType))
+			throw new Errors.Error("expect '" + expectType.name() + "' type for argument "
+								 + pos + ", got '" + type.name() + "'");
+		if (arg.isVar){
+			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("read-only variable cannot be used as VAR parameter");
+		}
+		return arg;
+	},
+	epilog: function(){return ")";},
+	writeCode: function(s){this.__codeGenerator.write(s);}
+});
+
+var ProcType = Type.Basic.extend({
+	init: function ProcType(name, args, result, callGeneratorFactory){
+		Type.Basic.prototype.init.bind(this)(name, "null");
+		this.__arguments = args;
+		this.__result = result;
+		this.__callGeneratorFactory = callGeneratorFactory
+			? callGeneratorFactory
+			: function(codeGenerator, id, type){
+				return new ProcCallGenerator(codeGenerator, id, type);
+			};
+	},
+	isProcedure: function(){return true;},
+	define: function(args, result){
+		this.__arguments = args;
+		this.__result = result;
+	},
+	arguments: 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(codeGenerator, id){
+		return this.__callGeneratorFactory(codeGenerator, 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 TwoArgToOperatorProcCallGenerator = ProcCallGenerator.extend({
+	init: function TwoArgToOperatorProcCallGenerator(codeGenerator, id, type, operator){
+		ProcCallGenerator.prototype.init.bind(this)(Code.nullGenerator, id, type);
+		this.__code = codeGenerator;
+		this.__operator = operator;
+		this.__firstArgumentCode = undefined;
+		this.__secondArgumentCode = undefined;
+	},
+	prolog: function(id){return "";},
+	handleArgument: function(type, designator, code){
+		if (!this.__firstArgumentCode)
+			this.__firstArgumentCode = code;
+		else
+			this.__secondArgumentCode = code;
+		ProcCallGenerator.prototype.handleArgument.bind(this)(type, designator, code);
+	},
+	epilog: function(type){return "";},
+	end: function(){
+		return this.__operator(this.__firstArgumentCode, this.__secondArgumentCode);
+	}
+});
+
+exports.predefined = [
+	function(){
+		var NewProcCallGenerator = ProcCallGenerator.extend({
+			init: function NewProcCallGenerator(codeGenerator, id, type){
+				ProcCallGenerator.prototype.init.bind(this)(codeGenerator, id, type);
+				this.__baseType = undefined;
+			},
+			prolog: function(id){return "";},
+			checkArgument: function(pos, type, designator){
+				ProcCallGenerator.prototype.checkArgument.bind(this)(pos, type, designator);
+				if (!(type instanceof Type.Pointer))
+					throw new Errors.Error("POINTER variable expected, got '"
+										 + type.name() + "'");
+				this.__baseType = type.baseType();
+				return new Arg(type, false);
+			},
+			epilog: function(){return " = new " + this.__baseType.name() + "()";}
+		});
+
+		var name = "NEW";
+		var args = [new Arg(undefined, true)];
+		var type = new Type.Procedure(new ProcType(
+			"predefined procedure NEW",
+			args,
+			undefined,
+			function(codeGenerator, id, type){
+				return new NewProcCallGenerator(codeGenerator, id, type);
+			}));
+		var symbol = new Type.Symbol(name, type);
+		return symbol;
+	}(),
+	function(){
+		var args = [new Arg(Type.basic.set, true),
+					new Arg(Type.basic.int, false)];
+		function operator(x, y){return x + " |= 1 << " + y;}
+		var proc = new ProcType(
+			"predefined procedure INCL",
+			args,
+			undefined,
+			function(codeGenerator, id, type){
+				return new TwoArgToOperatorProcCallGenerator(
+					codeGenerator, id, type, operator);
+				});
+		var type = new Type.Procedure(proc);
+		var symbol = new Type.Symbol("INCL", type);
+		return symbol;
+	}(),
+	function(){
+		var args = [new Arg(Type.basic.set, true),
+					new Arg(Type.basic.int, false)];
+		function operator(x, y){return x + " &= ~(1 << " + y + ")";}
+		var proc = new ProcType(
+			"predefined procedure EXCL",
+			args,
+			undefined,
+			function(codeGenerator, id, type){
+				return new TwoArgToOperatorProcCallGenerator(
+					codeGenerator, id, type, operator);
+				});
+		var type = new Type.Procedure(proc);
+		var symbol = new Type.Symbol("EXCL", type);
+		return symbol;
+	}()
+	];
+
+exports.CallGenerator = ProcCallGenerator;
+exports.Type = ProcType;

+ 144 - 0
rtl.js

@@ -0,0 +1,144 @@
+function Class(){}
+Class.extend = function extend(methods){
+	methods.__proto__ = this.prototype; // make instanceof work
+
+	// to see constructor name in diagnostic
+	var result = methods.init;
+	methods.constructor = result.prototype.constructor;
+
+	result.prototype = methods;
+	result.extend = extend;
+	return result;
+};
+
+function RTLTypeGuard(from, to){
+	if (!(from instanceof to))
+		throw new Error("typeguard assertion failed");
+	return from;
+}
+
+function RTLMakeArray(/*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] = RTLMakeArray.apply(this, forward);
+	return result;
+	}
+}
+
+function RTLMakeSet(/*...*/){
+	var result = 0;
+	
+	function checkBit(b){
+		if (b < 0 || b > 31)
+			throw new Error("integes 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;
+}
+
+function RTLMakeRef(obj, prop){
+    return {set: function(v){ obj[prop] = v; },
+            get: function(){ return obj[prop]; }};
+}
+
+function RTLSetInclL(l, r){
+	return l & r == l;
+}
+
+function RTLSetInclR(l, r){
+	return l & r == r;
+}
+
+exports.Class = Class;
+exports.RTL = Class.extend({
+	init: function RTL(){
+		this.__entries = {};
+		this.__supportJS = false;
+	},
+	supportJS: function(){this.__supportJS = true;},
+	baseClass: function(){
+		if (!this.__entries["extend"])
+			this.__entries.extend = Class.extend;
+		return "RTL$";
+	},
+	genCast: function(obj, type){
+		if (!this.__entries["typeGuard"])
+			this.__entries.typeGuard = RTLTypeGuard;
+
+		return "RTL$.typeGuard(" + obj + ", " + type.name() + ")";
+	},
+	makeRef: function(obj, prop){
+		if (!this.__entries["makeRef"])
+			this.__entries.makeRef = RTLMakeRef;
+		return "RTL$.makeRef(" + obj + ", " + prop + ")";
+	},
+	makeArray: function(args){
+		if (!this.__entries.makeArray)
+			this.__entries.makeArray = RTLMakeArray;
+		return "RTL$.makeArray(" + args + ")";
+	},
+	makeSet: function(args){
+		if (!this.__entries["makeSet"])
+			this.__entries.makeSet = RTLMakeSet;
+		return "RTL$.makeSet(" + args + ")";
+	},
+	setInclL: function(args){
+		if (!this.__entries.setInclL)
+			this.__entries.setInclL = RTLSetInclL;
+		return "RTL$.setInclL(" + args + ")";
+	},
+	setInclR: function(args){
+		if (!this.__entries.setInclR)
+			this.__entries.setInclR = RTLSetInclR;
+		return "RTL$.setInclR(" + args + ")";
+	},
+	generate: function(){
+		var result = "var RTL$ = {\n";
+		var firstEntry = true;
+		for (var name in this.__entries){
+			if (!firstEntry)
+				result += ",\n";
+			else
+				firstEntry = false;
+			result += "\t" + name + ": " + this.__entries[name].toString().replace(/\n/g, "\n\t");
+		}
+		if (!firstEntry)
+			result += "\n};\n";
+		else
+			result = "";
+		
+		if (this.__supportJS)
+			result += "var JS = function(){return this;}();\n";
+		return result;
+	}
+});

+ 45 - 0
stream.js

@@ -0,0 +1,45 @@
+var assert = require("assert.js").ok;
+var Class = require("rtl.js").Class;
+
+exports.Stream = Class.extend({
+	init: function Stream(s){
+		this.__s = s;
+		this.__pos = 0;
+	},
+	str: function(n){return this.__s.substr(this.__pos, n);},
+	char: function(){
+		if (this.eof())
+			return undefined;
+		return this.__s.charAt(this.__pos++);
+	},
+	read: function(f){
+		while (!this.eof() && f(this.peekChar()))
+			this.next(1);
+		return !this.eof();
+	},
+	peekChar: function(){
+		if (this.eof())
+			return undefined;
+		return this.__s.charAt(this.__pos);
+	},
+	peekStr: function(size){
+		var max = this.__s.length - this.__pos;
+		if (size > max)
+			size = max;
+		return this.__s.substr(this.__pos, size);
+	},
+	next: function(n){
+		assert(this.__pos + n <= this.__s.length);
+		this.__pos += n;
+	},
+	pos: function(){return this.__pos;},
+	setPos: function(pos){
+		assert(pos <= this.__s.length);
+		return this.__pos = pos;
+	},
+	eof: function(){return this.__pos == this.__s.length;},
+	describePosition: function(){
+		var line = this.__s.substr(0, this.__pos).split("\n").length;
+		return "line " + line;
+	}
+});

+ 3 - 0
test/build_test_unit_browser.cmd

@@ -0,0 +1,3 @@
+cd ..
+browser\linkjs.py test/test_unit.js test/test_unit_browser.js
+cd test

+ 17 - 0
test/expected/arithmetic.js

@@ -0,0 +1,17 @@
+var m = function (){
+var i1 = 0;var i2 = 0;
+var r1 = 0;var r2 = 0;
+i1 = 1;
+i2 = 2;
+i1 = i1 + i2;
+i1 = i1 - i2;
+i1 = i1 * i2;
+i1 = (i1 / i2) >> 0;
+i1 = i1 % i2;
+r1 = 1;
+r2 = 2;
+r1 = r1 + r2;
+r1 = r1 - r2;
+r1 = r1 * r2;
+r1 = r1 / r2;
+}();

+ 53 - 0
test/expected/array.js

@@ -0,0 +1,53 @@
+var RTL$ = {
+	makeArray: function RTLMakeArray(/*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] = RTLMakeArray.apply(this, forward);
+		return result;
+		}
+	},
+	extend: function extend(methods){
+		methods.__proto__ = this.prototype; // make instanceof work
+	
+		// to see constructor name in diagnostic
+		var result = methods.init;
+		methods.constructor = result.prototype.constructor;
+	
+		result.prototype = methods;
+		result.extend = extend;
+		return result;
+	}
+};
+var m = function (){
+var arraySize = 10;
+var a1 = RTL$.makeArray(10, 0);
+var a2 = RTL$.makeArray(5, function(){return RTL$.makeArray(10, 0);});
+var a3 = RTL$.makeArray(5, false);
+var a4 = RTL$.makeArray(3, 4, false);
+var anonymous$1 = RTL$.extend({
+	init: function anonymous$1(){
+	}
+});
+var a5 = RTL$.makeArray(3, function(){return new anonymous$1();});
+
+function p(){
+	var a3 = RTL$.makeArray(1, 0);
+	a3[0] = 1;
+}
+a1[0] = 1;
+a3[1] = true;
+a4[1][2] = true;
+a4[1][2] = true;
+}();

+ 59 - 0
test/expected/case.js

@@ -0,0 +1,59 @@
+var m = function (){
+var ch1 = "a";
+var constI = 12;
+var i = 0;
+var b1 = false;
+var i1 = 0;
+var c = 0;
+var $c;
+$c = i1;
+$c = 123;
+if ($c === 1){
+	b1 = true;
+}
+$c = i1;
+if ($c === 1){
+	i = 2;
+}
+else if ($c === 2){
+	i = 3;
+	b1 = false;
+}
+$c = i1;
+if ($c === 1){
+	i = 2;
+}
+else if ($c === 2){
+	i = 3;
+	b1 = false;
+}
+$c = i1;
+if ($c === 1 || $c === 2 || $c === 3){
+	i = 4;
+}
+else if ($c === 12){
+	i = 12;
+}
+else if (($c >= 4 && $c <= 5)){
+	i = 5;
+}
+else if ($c === 6 || ($c >= 7 && $c <= 10)){
+	b1 = true;
+}
+$c = c;
+if ($c === 65){
+	i = 1;
+}
+else if ($c === 97){
+	i = 2;
+}
+else if ($c === 66 || $c === 67){
+	i = 2;
+}
+else if (($c >= 68 && $c <= 70) || $c === 73 || $c === 74){
+	i = 3;
+}
+else if (($c >= 75 && $c <= 90)){
+	b1 = true;
+}
+}();

+ 45 - 0
test/expected/cast.js

@@ -0,0 +1,45 @@
+var RTL$ = {
+	extend: function extend(methods){
+		methods.__proto__ = this.prototype; // make instanceof work
+	
+		// to see constructor name in diagnostic
+		var result = methods.init;
+		methods.constructor = result.prototype.constructor;
+	
+		result.prototype = methods;
+		result.extend = extend;
+		return result;
+	},
+	typeGuard: function RTLTypeGuard(from, to){
+		if (!(from instanceof to))
+			throw new Error("typeguard assertion failed");
+		return from;
+	}
+};
+var m = function (){
+var Base = RTL$.extend({
+	init: function Base(){
+	}
+});
+var Derived1 = Base.extend({
+	init: function Derived1(){
+		Base.prototype.init.bind(this)();
+		this.field1 = 0;
+	}
+});
+var Derived2 = Derived1.extend({
+	init: function Derived2(){
+		Derived1.prototype.init.bind(this)();
+		this.field2 = 0;
+	}
+});
+var pb = null;
+var pd1 = null;
+var pd2 = null;
+pd2 = new Derived2();
+pb = pd2;
+pd1 = pd2;
+RTL$.typeGuard(pb, Derived1).field1 = 0;
+RTL$.typeGuard(pb, Derived2).field2 = 1;
+RTL$.typeGuard(pd1, Derived2).field2 = 2;
+}();

+ 17 - 0
test/expected/const.js

@@ -0,0 +1,17 @@
+var m = function (){
+var i1 = 1;
+var b1 = true;
+var i2 = 3;
+var i3 = 31;
+var i4 = 10;
+var i5 = 3;
+var i6 = 256;
+var r1 = 456700000;
+var r2 = 1.23;
+var r3 = 0.12345;
+var r4 = 12345600000;
+var lr1 = 23456700000000;
+var lr2 = 23.4567;
+var lr3 = 0.0000234567;
+var b2 = true;
+}();

+ 17 - 0
test/expected/for.js

@@ -0,0 +1,17 @@
+var m = function (){
+var i = 0;
+var b1 = false;
+var i1 = 0;
+for (i = 0; i <= 10; ++i){
+	i1 = i1 + 1;
+}
+for (i = 0; i <= 10; i += 5){
+	b1 = true;
+}
+for (i = 15; i >= 0; i -= 3){
+	for (i1 = 1; i1 >= 3; i1 -= 1){
+		b1 = true;
+	}
+	i1 = -2;
+}
+}();

+ 31 - 0
test/expected/if.js

@@ -0,0 +1,31 @@
+var m = function (){
+var b1 = false;
+var i1 = 0;
+if (true){
+	b1 = true;
+}
+if (b1){
+	i1 = 0;
+}
+else {
+	i1 = 1;
+}
+if (b1){
+	i1 = 0;
+}
+else if (false){
+	i1 = 1;
+}
+else {
+	i1 = 2;
+}
+if (b1){
+	if (b1){
+		i1 = 0;
+		b1 = false;
+	}
+}
+else {
+	b1 = true;
+}
+}();

+ 41 - 0
test/expected/is.js

@@ -0,0 +1,41 @@
+var RTL$ = {
+	extend: function extend(methods){
+		methods.__proto__ = this.prototype; // make instanceof work
+	
+		// to see constructor name in diagnostic
+		var result = methods.init;
+		methods.constructor = result.prototype.constructor;
+	
+		result.prototype = methods;
+		result.extend = extend;
+		return result;
+	}
+};
+var m = function (){
+var Base = RTL$.extend({
+	init: function Base(){
+	}
+});
+var Derived1 = Base.extend({
+	init: function Derived1(){
+		Base.prototype.init.bind(this)();
+		this.field1 = 0;
+	}
+});
+var Derived2 = Derived1.extend({
+	init: function Derived2(){
+		Derived1.prototype.init.bind(this)();
+		this.field2 = 0;
+	}
+});
+var pb = null;
+var pd1 = null;
+var pd2 = null;
+var b = false;
+pd2 = new Derived2();
+pb = pd2;
+pd1 = pd2;
+b = pb instanceof Derived1;
+b = pb instanceof Derived2;
+b = pd1 instanceof Derived2;
+}();

+ 4 - 0
test/expected/js_module.js

@@ -0,0 +1,4 @@
+var JS = function(){return this;}();
+var m = function (){
+JS.console.info("test");
+}();

+ 8 - 0
test/expected/logical.js

@@ -0,0 +1,8 @@
+var m = function (){
+var b1 = false;var b2 = false;
+b1 = true;
+b2 = b1 || b1;
+b1 = b1 && b2;
+b1 = !b2;
+b1 = b1 && b2 || !b1;
+}();

+ 2 - 0
test/expected/module.js

@@ -0,0 +1,2 @@
+var m = function (){
+}();

+ 35 - 0
test/expected/new.js

@@ -0,0 +1,35 @@
+var RTL$ = {
+	extend: function extend(methods){
+		methods.__proto__ = this.prototype; // make instanceof work
+	
+		// to see constructor name in diagnostic
+		var result = methods.init;
+		methods.constructor = result.prototype.constructor;
+	
+		result.prototype = methods;
+		result.extend = extend;
+		return result;
+	}
+};
+var m = function (){
+var T1 = RTL$.extend({
+	init: function T1(){
+		this.field1 = 0;
+	}
+});
+var anonymous$1$base = RTL$.extend({
+	init: function anonymous$1$base(){
+	}
+});
+var p = null;
+var p1 = null;
+var anonymous$3 = RTL$.extend({
+	init: function anonymous$3(){
+		this.p = null;
+	}
+});
+var r = new anonymous$3();
+p = new anonymous$1$base();
+p1 = new T1();
+r.p = new T1();
+}();

+ 21 - 0
test/expected/nil.js

@@ -0,0 +1,21 @@
+var RTL$ = {
+	extend: function extend(methods){
+		methods.__proto__ = this.prototype; // make instanceof work
+	
+		// to see constructor name in diagnostic
+		var result = methods.init;
+		methods.constructor = result.prototype.constructor;
+	
+		result.prototype = methods;
+		result.extend = extend;
+		return result;
+	}
+};
+var m = function (){
+var anonymous$1$base = RTL$.extend({
+	init: function anonymous$1$base(){
+	}
+});
+var p = null;
+p = null;
+}();

+ 57 - 0
test/expected/proc.js

@@ -0,0 +1,57 @@
+var RTL$ = {
+	extend: function extend(methods){
+		methods.__proto__ = this.prototype; // make instanceof work
+	
+		// to see constructor name in diagnostic
+		var result = methods.init;
+		methods.constructor = result.prototype.constructor;
+	
+		result.prototype = methods;
+		result.extend = extend;
+		return result;
+	}
+};
+var m = function (){
+
+function p1(arg1/*INTEGER*/){
+	var T1 = RTL$.extend({
+		init: function T1(){
+			this.field1 = 0;
+		}
+	});
+	var T2 = T1.extend({
+		init: function T2(){
+			T1.prototype.init.bind(this)();
+			this.field2 = false;
+		}
+	});
+	var i = 0;var j = 0;
+	var b = false;
+	var t1 = new T1();
+	var t2 = new T2();
+	i = arg1 + 1;
+	t1.field1 = i;
+	t2.field1 = t1.field1;
+	b = true;
+	t2.field2 = b;
+}
+
+function p2(){
+	p1(123);
+}
+
+function p3(i/*INTEGER*/){
+	p1(123);
+	p2();
+	p2();
+	return 123;
+}
+
+function p4(){
+	return p3(123) + p3(p3(123));
+}
+
+function p5(){
+	return p5;
+}
+}();

+ 13 - 0
test/expected/repeat.js

@@ -0,0 +1,13 @@
+var m = function (){
+var b1 = false;var b2 = false;
+var i1 = 0;
+b1 = true;
+b2 = false;
+do {
+	i1 = 0;
+} while (b1);
+do {
+	i1 = 1;
+	b2 = false;
+} while (b1);
+}();

+ 68 - 0
test/expected/set.js

@@ -0,0 +1,68 @@
+var RTL$ = {
+	makeSet: function RTLMakeSet(/*...*/){
+		var result = 0;
+		
+		function checkBit(b){
+			if (b < 0 || b > 31)
+				throw new Error("integes 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;
+	},
+	setInclL: function RTLSetInclL(l, r){
+		return l & r == l;
+	},
+	setInclR: function RTLSetInclR(l, r){
+		return l & r == r;
+	}
+};
+var m = function (){
+var ci = 3;
+var cs1 = 6;
+var cs2 = 12;
+var cs3 = 2;
+var cs4 = 28;
+var cs5 = -3;
+var s1 = 0;var s2 = 0;
+var i1 = 0;
+var b = false;
+s1 = 0;
+s1 = 61;
+s1 = 8;
+s1 = 64;
+i1 = 3;
+s2 = RTL$.makeSet(i1, i1 + 2, [10 - i1, 15]);
+s2 = RTL$.makeSet(i1) | 4;
+b = 1 << i1 & s1;
+b = RTL$.setInclL(s1, s2);
+b = RTL$.setInclR(s1, s2);
+b = s1 == s2;
+b = s1 != s2;
+s1 = s1 | s2;
+s1 = s1 & ~s2;
+s1 = s1 & s2;
+s1 = s1 ^ s2;
+s1 = ~s2;
+s2 |= 1 << 3;
+s1 |= 1 << 9;
+s1 |= 1 << ci * 2 - i1 + 3;
+s2 &= ~(1 << 3);
+}();

+ 4 - 0
test/expected/string.js

@@ -0,0 +1,4 @@
+var m = function (){
+var s1 = "\"";
+var s2 = "ABC";
+}();

+ 87 - 0
test/expected/var_parameter.js

@@ -0,0 +1,87 @@
+var RTL$ = {
+	extend: function extend(methods){
+		methods.__proto__ = this.prototype; // make instanceof work
+	
+		// to see constructor name in diagnostic
+		var result = methods.init;
+		methods.constructor = result.prototype.constructor;
+	
+		result.prototype = methods;
+		result.extend = extend;
+		return result;
+	},
+	makeArray: function RTLMakeArray(/*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] = RTLMakeArray.apply(this, forward);
+		return result;
+		}
+	},
+	makeRef: function RTLMakeRef(obj, prop){
+	    return {set: function(v){ obj[prop] = v; },
+	            get: function(){ return obj[prop]; }};
+	}
+};
+var m = function (){
+var R = RTL$.extend({
+	init: function R(){
+		this.i = 0;
+		this.p = null;
+	}
+});
+var i = 0;
+var b = false;
+var a = RTL$.makeArray(5, 0);
+
+function p1(i1/*VAR INTEGER*/, i2/*VAR INTEGER*/){
+	i1.set(1);
+	i2.set(2);
+}
+
+function p2(i/*INTEGER*/, b/*BOOLEAN*/){
+}
+
+function index(i/*VAR INTEGER*/){
+	return i.get();
+}
+
+function array(a/*VAR ARRAY OF INTEGER*/){
+	return a[0];
+}
+
+function p3(i/*VAR INTEGER*/, b/*VAR BOOLEAN*/){
+	var j = 0;
+	var r = new R();
+	var ar = RTL$.makeArray(5, function(){return new R();});
+	var ai = RTL$.makeArray(5, 0);
+	j = i.get() + 1;
+	j = 2 * i.get();
+	j = i.get() / 2;
+	j = -i.get();
+	b.set(!b.get());
+	a[i.get()] = i.get();
+	p1({set: function($v){j = $v;}, get: function(){return j;}}, i);
+	p1(i, {set: function($v){j = $v;}, get: function(){return j;}});
+	p1(i, RTL$.makeRef(a, index(i)));
+	p2(i.get(), b.get());
+	p1(RTL$.makeRef(r, "i"), RTL$.makeRef(ar[index(RTL$.makeRef(r, "i"))], "i"));
+	r.p = new R();
+	ar[j].p = new R();
+	p1(RTL$.makeRef(r.p, "i"), RTL$.makeRef(ar[j].p, "i"));
+	p2(ar[j].p.i, r.p.i == ar[j].p.i);
+	j = array(ai);
+}
+p3({set: function($v){i = $v;}, get: function(){return i;}}, {set: function($v){b = $v;}, get: function(){return b;}});
+}();

+ 31 - 0
test/expected/while.js

@@ -0,0 +1,31 @@
+var m = function (){
+var b1 = false;var b2 = false;
+var i1 = 0;
+b1 = true;
+b2 = false;
+while (true){
+	if (b1){
+		i1 = 0;
+	} else break;
+}
+while (true){
+	if (b1){
+		i1 = 0;
+	}
+	else if (b2){
+		i1 = 1;
+	} else break;
+}
+while (true){
+	if (b1){
+		while (true){
+			if (b2){
+				i1 = 1;
+			}
+			else if (b1){
+				i1 = 2;
+			} else break;
+		}
+	} else break;
+}
+}();

+ 22 - 0
test/input/arithmetic.ob

@@ -0,0 +1,22 @@
+MODULE m;
+
+VAR 
+	i1, i2: INTEGER;
+	r1, r2: REAL;
+
+BEGIN
+	i1 := 1;
+	i2 := 2;
+	i1 := i1 + i2;
+	i1 := i1 - i2;
+	i1 := i1 * i2;
+	i1 := i1 DIV i2;
+	i1 := i1 MOD i2;
+
+	r1 := 1.0;
+	r2 := 2.0;
+	r1 := r1 + r2;
+	r1 := r1 - r2;
+	r1 := r1 * r2;
+	r1 := r1 / r2
+END m.

+ 29 - 0
test/input/array.ob

@@ -0,0 +1,29 @@
+MODULE m;
+
+CONST arraySize = 10;
+
+TYPE
+    T1 = ARRAY arraySize OF INTEGER;
+    T2 = ARRAY 5 OF T1;
+    T3 = ARRAY 3 OF ARRAY 7 OF BOOLEAN;
+    T4 = ARRAY 3, 4 OF BOOLEAN;
+
+VAR
+    a1: T1;
+    a2: T2;
+    a3: ARRAY 5 OF BOOLEAN;
+	a4: T4;
+    a5: ARRAY 3 OF RECORD END;
+
+PROCEDURE p; 
+VAR a3: ARRAY 1 OF INTEGER;
+BEGIN 
+    a3[0] := 1 
+END p;
+
+BEGIN
+    a1[0] := 1;
+    a3[1] := TRUE;
+	a4[1][2] := TRUE;
+	a4[1, 2] := TRUE
+END m.

+ 42 - 0
test/input/case.ob

@@ -0,0 +1,42 @@
+MODULE m;
+
+CONST
+	ch1 = "a";
+	constI = 12;
+
+VAR i: INTEGER;
+	b1: BOOLEAN;
+	i1: INTEGER;
+	c: CHAR;
+
+BEGIN    
+	CASE i1 OF END;
+
+	CASE 123 OF 1: b1 := TRUE END;
+
+	CASE i1 OF 
+		  1: i := 2
+		| 2: i := 3; b1 := FALSE
+	END;
+
+	CASE i1 OF 
+		  1: i := 2
+		| 2: i := 3; b1 := FALSE
+	END;
+
+	CASE i1 OF
+		  1, 2, 3: i := 4
+		| constI: i := constI
+		| 4..5: i := 5
+		| 6, 7..10: b1 := TRUE
+	END;
+
+	CASE c OF
+		  "A": i := 1
+		| ch1: i := 2
+		| "B", "C": i := 2
+		| "D".."F", "I", "J": i:=3
+		| "K".."Z": b1:= TRUE
+	END
+
+END m.

+ 18 - 0
test/input/cast.ob

@@ -0,0 +1,18 @@
+MODULE m;
+
+TYPE
+	Base = RECORD END;
+	Derived1 = RECORD(Base) field1: INTEGER END;
+	Derived2 = RECORD(Derived1) field2: INTEGER END;
+VAR
+	pb: POINTER TO Base;
+	pd1: POINTER TO Derived1;
+	pd2: POINTER TO Derived2;
+BEGIN 
+	NEW(pd2);
+	pb := pd2;
+	pd1 := pd2;
+    pb(Derived1).field1 := 0;
+    pb(Derived2).field2 := 1;
+    pd1(Derived2).field2 := 2
+END m.

+ 20 - 0
test/input/const.ob

@@ -0,0 +1,20 @@
+MODULE m;
+
+CONST
+	i1 = 1;
+	b1 = TRUE;
+	i2 = i1 + 2;
+	i3 = 1FH;
+	i4 = 0AH;
+	i5 = 03H;
+	i6 = 100H;
+	r1 = 4.567E8;
+	r2 = 1.23;
+	r3 = 1.2345E-1;
+	r4 = 1.23456E+10;
+	lr1 = 2.34567D13;
+	lr2 = 2.34567D+1;
+	lr3 = 2.34567D-5;
+	b2 = b1;
+
+END m.

+ 22 - 0
test/input/for.ob

@@ -0,0 +1,22 @@
+MODULE m;
+
+VAR i: INTEGER;
+	b1: BOOLEAN;
+	i1: INTEGER;
+
+BEGIN    
+	FOR i := 0 TO 10 DO 
+		i1 := i1 + 1 
+	END;
+	
+	FOR i := 0 TO 10 BY 5 DO
+		b1 := TRUE
+	END;
+
+	FOR i := 15 TO 0 BY -3 DO
+		FOR i1 := 1 TO 3 BY -1 DO
+			b1 := TRUE
+		END;
+		i1 := -2
+	END
+END m.

+ 17 - 0
test/input/if.ob

@@ -0,0 +1,17 @@
+MODULE m;
+
+VAR b1: BOOLEAN;
+	i1: INTEGER;
+
+BEGIN    
+	IF TRUE THEN b1 := TRUE END;
+	IF b1 THEN i1 := 0 ELSE i1 := 1 END;
+	IF b1 THEN i1 := 0 ELSIF FALSE THEN i1 := 1 ELSE i1 := 2 END;
+
+	IF b1 THEN
+		IF b1 THEN i1 := 0; b1 := FALSE END
+	ELSE
+		b1 := TRUE
+	END
+	
+END m.

+ 19 - 0
test/input/is.ob

@@ -0,0 +1,19 @@
+MODULE m;
+
+TYPE
+	Base = RECORD END;
+	Derived1 = RECORD(Base) field1: INTEGER END;
+	Derived2 = RECORD(Derived1) field2: INTEGER END;
+VAR
+	pb: POINTER TO Base;
+	pd1: POINTER TO Derived1;
+	pd2: POINTER TO Derived2;
+	b: BOOLEAN;
+BEGIN 
+    NEW(pd2);
+    pb := pd2;
+    pd1 := pd2;
+    b := pb IS Derived1;
+    b := pb IS Derived2;
+    b := pd1 IS Derived2
+END m.

+ 5 - 0
test/input/js_module.ob

@@ -0,0 +1,5 @@
+MODULE m;
+IMPORT JS;
+BEGIN
+	JS.console.info("test")
+END m.

+ 12 - 0
test/input/logical.ob

@@ -0,0 +1,12 @@
+MODULE m;
+
+VAR 
+	b1, b2: BOOLEAN;
+
+BEGIN
+	b1 := TRUE;
+	b2 := b1 OR b1;
+	b1 := b1 & b2;
+	b1 := ~b2;
+	b1 := b1 & b2 OR ~b1 
+END m.

+ 2 - 0
test/input/module.ob

@@ -0,0 +1,2 @@
+MODULE m;
+END m.

+ 13 - 0
test/input/new.ob

@@ -0,0 +1,13 @@
+MODULE m;
+                
+TYPE T1 = RECORD field1: INTEGER END;
+
+VAR p: POINTER TO RECORD END;
+	p1: POINTER TO T1;
+	r: RECORD p: POINTER TO T1 END;
+
+BEGIN
+	NEW(p);
+	NEW(p1);
+	NEW(r.p)
+END m.

+ 7 - 0
test/input/nil.ob

@@ -0,0 +1,7 @@
+MODULE m;
+
+VAR p: POINTER TO RECORD END;
+
+BEGIN
+	p := NIL
+END m.

+ 44 - 0
test/input/proc.ob

@@ -0,0 +1,44 @@
+MODULE m;
+
+TYPE
+	P1 = PROCEDURE(a1: INTEGER);
+	P2 = PROCEDURE(): P2;
+
+PROCEDURE p1(arg1: INTEGER);
+TYPE
+    T1 = RECORD 
+        field1: INTEGER END;
+    T2 = RECORD (T1) 
+        field2: BOOLEAN END;
+VAR 
+    i, j: INTEGER; 
+    b: BOOLEAN;
+    t1: T1;
+    t2: T2;
+BEGIN
+    i := arg1 + 1;
+    t1.field1 := i;
+    t2.field1 := t1.field1;
+    b := TRUE;
+    t2.field2 := b
+END p1;
+
+PROCEDURE p2; BEGIN p1(123) END p2;
+
+PROCEDURE p3(i: INTEGER): INTEGER; 
+BEGIN
+	p1(123);
+	p2;
+	p2()
+	RETURN 123
+END p3;
+
+PROCEDURE p4(): INTEGER; 
+	RETURN p3(123) + p3(p3(123))
+END p4;
+
+PROCEDURE p5(): P2; 
+	RETURN p5
+END p5;
+
+END m.

+ 17 - 0
test/input/repeat.ob

@@ -0,0 +1,17 @@
+MODULE m;
+
+VAR b1, b2: BOOLEAN;
+	i1: INTEGER;
+
+BEGIN    
+	b1 := TRUE;
+	b2 := FALSE;
+
+	REPEAT i1 := 0 UNTIL b1;
+
+	REPEAT 
+		i1 := 1;
+		b2 := FALSE 
+	UNTIL b1
+	
+END m.

+ 43 - 0
test/input/set.ob

@@ -0,0 +1,43 @@
+MODULE m;
+
+CONST
+	ci = 3;
+	
+	cs1 = {1} + {2};
+	cs2 = {1, 2, 3} - {1, 4};
+	cs3 = {1, 2, 3} * {1, 4};
+	cs4 = {1, 2, 3} / {1, 4};
+	cs5 = -{1};
+VAR 
+	s1, s2: SET;
+	i1: INTEGER;
+	b: BOOLEAN;
+
+BEGIN
+	s1 := {};
+	s1 := {0, 2..5};
+	s1 := {ci};
+	s1 := {ci * 2};
+
+	i1 := 3;
+	s2 := {i1, i1 + 2, 10 - i1..15};
+	s2 := {i1, 2};
+
+	b := i1 IN s1;
+	b := s1 <= s2;
+	b := s1 >= s2;
+	b := s1 = s2;
+	b := s1 # s2;
+
+	s1 := s1 + s2;
+	s1 := s1 - s2;
+	s1 := s1 * s2;
+	s1 := s1 / s2;
+	s1 := -s2;
+
+	INCL(s2, 3);
+	INCL(s1, ci * 2 + 3);
+	INCL(s1, ci * 2 - i1 + 3);
+
+	EXCL(s2, 3)
+END m.

+ 7 - 0
test/input/string.ob

@@ -0,0 +1,7 @@
+MODULE m;
+
+CONST
+	s1 = 22X;
+	s2 = "ABC";
+
+END m.

+ 57 - 0
test/input/var_parameter.ob

@@ -0,0 +1,57 @@
+MODULE m;
+
+TYPE R = RECORD i: INTEGER; p: POINTER TO R END;
+
+VAR 
+	i: INTEGER;
+	b: BOOLEAN;
+	a: ARRAY 5 OF INTEGER;
+
+PROCEDURE p1(VAR i1, i2: INTEGER);
+BEGIN
+    i1 := 1;
+	i2 := 2
+END p1;
+
+PROCEDURE p2(i: INTEGER; b: BOOLEAN);
+END p2;
+
+PROCEDURE index(VAR i: INTEGER): INTEGER;
+	RETURN i
+END index;
+
+PROCEDURE array(VAR a: ARRAY OF INTEGER): INTEGER;
+	RETURN a[0]
+END array;
+
+PROCEDURE p3(VAR i: INTEGER; VAR b: BOOLEAN);
+VAR j: INTEGER;
+	r: R;
+	ar: ARRAY 5 OF R;
+	ai: ARRAY 5 OF INTEGER;
+BEGIN
+	j := i + 1;
+	j := 2 * i;
+	j := i / 2;
+	j := -i;
+	b := ~b;
+	a[i] := i;
+    p1(j, i);
+	p1(i, j);
+	p1(i, a[index(i)]);
+	p2(i, b);
+
+	p1(r.i, ar[index(r.i)].i);
+
+	NEW(r.p);
+	NEW(ar[j].p);
+	p1(r.p.i, ar[j].p.i);
+	p2(ar[j].p.i, r.p.i = ar[j].p.i);
+
+	j := array(ai)
+END p3;
+
+BEGIN
+    p3(i, b)
+
+END m.

+ 22 - 0
test/input/while.ob

@@ -0,0 +1,22 @@
+MODULE m;
+
+VAR b1, b2: BOOLEAN;
+	i1: INTEGER;
+
+BEGIN    
+	b1 := TRUE;
+	b2 := FALSE;
+
+	WHILE b1 DO i1 := 0 END;
+
+	WHILE b1 DO i1 := 0 
+	ELSIF b2 DO i1 := 1
+	END;
+
+	WHILE b1 DO
+		WHILE b2 DO i1 := 1
+		ELSIF b1 DO i1 := 2
+		END
+	END
+	
+END m.

+ 2 - 0
test/test.cmd

@@ -0,0 +1,2 @@
+SET NODE_PATH=%~dp0..
+"C:\Program Files\nodejs\node.exe" test.js

+ 50 - 0
test/test.js

@@ -0,0 +1,50 @@
+var oc = require('oc');
+var fs = require('fs');
+
+function normalizeLineEndings(text)
+{
+    return text.replace(/\r\n/g, '\n');
+}
+
+function run()
+{
+    var inputDir = "input";
+    var outputDir = "output";
+    if (!fs.existsSync(outputDir))
+        fs.mkdirSync(outputDir);
+    var sources = fs.readdirSync(inputDir);
+    var failCount = 0;
+
+    var start = Date.now();
+
+    for(var i = 0; i < sources.length; ++i)
+    {
+        var source = sources[i];
+        console.log(source + ":\t");
+        var text = fs.readFileSync(inputDir + "/" + source, "utf8");
+        var result = oc.compile(text);
+        var resultName = source.replace(".ob", ".js");
+        fs.writeFileSync(outputDir + "/" + resultName, result);
+        var success = (normalizeLineEndings(result) == normalizeLineEndings(fs.readFileSync("expected/" + resultName, "utf8")));
+        console.log(success ? "OK" : "Failed");
+        if (!success)
+            ++failCount;
+    }
+    console.log(sources.length + " tests" + (failCount ? ", " + failCount + " failed." : ""));
+
+    var stop = Date.now();
+    console.log("elapsed: " + (stop - start) / 1000 + " s" );
+
+    if (!failCount)
+        console.log("All OK!");
+}
+
+//try
+{
+    run();
+}
+//catch (x)
+//{
+ //   console.error(x);
+//    console.error(x.stack);
+//}

+ 2 - 0
test/test_unit.cmd

@@ -0,0 +1,2 @@
+SET NODE_PATH=%~dp0..
+"C:\Program Files\nodejs\node.exe" test_unit.js %*

+ 4 - 0
test/test_unit.html

@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<html>
+    <script src="test_unit_browser.js"></script>
+</html>

+ 817 - 0
test/test_unit.js

@@ -0,0 +1,817 @@
+var assert = require("assert.js").ok;
+var Context = require("context.js");
+var Errors = require("errors.js");
+var Grammar = require("grammar.js");
+var oc = require("oc.js");
+var Class = require("rtl.js").Class;
+var Stream = require("stream.js").Stream;
+
+function TestError(s) {this.__s = s;}
+TestError.prototype.toString = function(){return this.__s;};
+
+function parseInContext(grammar, s, context){
+	var stream = new Stream(s);
+	if (!grammar(stream, context) || !stream.eof())
+		throw new Errors.Error("not parsed");
+}
+
+function parseUsingGrammar(grammar, s, cxFactory, handlerError){
+	var baseContext = new Context.Context();
+	var context = cxFactory ? cxFactory(baseContext) : baseContext;
+	try {
+		parseInContext(grammar, s, context);
+	}
+	catch (x){
+		if (!(x instanceof Errors.Error))
+			throw x;//console.log(x.stack);
+		
+		if (handlerError)
+			handlerError(x);
+		//else
+		//	throw x;
+		//	console.log(s + ": " + x);
+		return false;
+	}
+	return true;
+}
+
+function setup(parser, contextFactory){
+	function parseImpl(s, handleError){
+		return parseUsingGrammar(parser, s, contextFactory, handleError);
+	}
+
+	return {
+		parse: function(s){
+			function handleError(e){throw new TestError(s + "\n\t" + e);}
+
+			if (!parseImpl(s, handleError))
+				throw new TestError(s + ": not parsed");
+		},
+		expectError: function(s, error){
+			function handleError(actualError){
+				var sErr = actualError.toString();
+				if (sErr != error)
+					throw new TestError(s + "\n\texpected error: " + error + "\n\tgot: " + sErr );
+			}
+
+			if (parseImpl(s, handleError))
+				throw new TestError(s + ": should not be parsed, expect error: " + error);
+		}
+	};
+}
+
+function setupWithContext(grammar, source){
+	function makeContext(){
+		var context = new Context.Context();
+		try {
+			parseInContext(Grammar.declarationSequence, source, context);
+		}
+		catch (x) {
+			if (x instanceof Errors.Error)
+				throw new TestError("setup error: " + x + "\n" + source);
+			throw x;
+		}
+		return context;
+	}
+
+	return setup(grammar, makeContext);
+}
+
+var testSuite = {
+comment: function(){
+	var test = setup(Grammar.expression);
+	test.parse("(**)123");
+	test.parse("(*abc*)123");
+	test.parse("(*a(*b*)c*)123");
+	test.expectError("(*123", "comment was not closed");
+},
+"spaces are required to separate keywords and integers": function(){
+	var test = setup(Grammar.typeDeclaration);
+
+	test.expectError("T = ARRAY10OFARRAY5OFINTEGER", "not parsed");
+	test.expectError("T = ARRAY10 OF ARRAY 5 OF INTEGER", "not parsed");
+	test.expectError("T = ARRAY 10OF ARRAY 5 OF INTEGER", "not parsed");
+	test.expectError("T = ARRAY 10 OFARRAY 5 OF INTEGER", "not parsed");
+	test.expectError("T = ARRAY 10 OF ARRAY5 OF INTEGER", "undeclared type: 'ARRAY5'");
+	test.expectError("T = ARRAY 10 OF ARRAY 5OF INTEGER", "not parsed");
+	test.expectError("T = ARRAY 10 OF ARRAY 5 OFINTEGER", "not parsed");
+},
+expression: function(){
+	var test = setupWithContext(
+		  Grammar.expression
+		, "TYPE ProcType = PROCEDURE(): INTEGER;"
+		+ "PROCEDURE p1(): INTEGER; RETURN 1 END p1;"
+		+ "PROCEDURE p2(): ProcType; RETURN p1 END p2;"
+		+ "PROCEDURE noResult(); END noResult;");
+
+	test.expectError("", "not parsed");
+	test.parse("123");
+	test.expectError("12a", "not parsed");
+
+	test.parse("1+2");
+	test.parse("1 + 2");
+	test.parse("1 + 2 + 3");
+
+	test.parse("-1");
+	test.parse("+1");
+
+	test.parse("p1() + p1()");
+	test.parse("p2()");
+	test.expectError("p2()()", "not parsed");
+	test.expectError("noResult()", "procedure returning no result cannot be used in an expression");
+},
+"string expression": function(){
+	var test = setup(Grammar.expression);
+
+	test.parse("\"\"");
+	test.parse("\"a\"");
+	test.parse("\"abc\"");
+	test.parse("0FFX");
+	test.parse("0AX");
+	test.parse("22X");
+	test.parse("0X");
+	test.expectError("\"", "unexpected end of string");
+	test.expectError("FFX", "undeclared identifier: 'FFX'");
+	//assert(!parse("1 + \"a\""));
+	//assert(parse("\"a\" + \"b\""));
+}
+,identifier: function(){
+	var IdentDeclarationContext = Class.extend({
+		init: function(){this.__ident = undefined;},
+		setIdent: function(id){this.__ident = id;},
+		ident: function() {return this.__ident;},
+		getResult: function() {return this.__ident;}
+	});
+	function makeContext() {return new IdentDeclarationContext();}
+	var parse = function(s) { return parseUsingGrammar(Grammar.ident, s, makeContext); };
+	assert(!parse(""));
+	assert(parse("i"));
+	assert(!parse("1"));
+	assert(parse("abc1"));
+},
+"variable declaration": function(){
+	var parse = function(s) { return parseUsingGrammar(Grammar.variableDeclaration, s); };
+	assert(parse("i: INTEGER"));
+	assert(parse("i, j: INTEGER"));
+	assert(!parse("i: T"));
+},
+"procedure VAR section": function(){
+	var parse = function(s) { return parseUsingGrammar(Grammar.declarationSequence, s); };
+	assert(parse("VAR"));
+	assert(parse("VAR i: INTEGER;"));
+	assert(parse("VAR i, j: INTEGER;"));
+	assert(parse("VAR i, j: INTEGER; b: BOOLEAN;"));
+},
+"const declaration": function(){
+	var test = setupWithContext(
+		  Grammar.declarationSequence
+		, "CONST ci = 1; VAR v1: INTEGER;");
+	test.parse("CONST i = 10;");
+	test.parse("CONST i = 1 + 2;");
+	test.parse("CONST i = ci + 2;");
+	test.parse("CONST i = ci * 2;");
+	test.parse("CONST b = TRUE;");
+	test.parse("CONST c = \"a\";");
+	test.parse("CONST s = \"abc\";");
+	test.parse("CONST s0 = \"\";");
+	test.parse("CONST set = {};");
+	test.parse("CONST set = {1 + 2};");
+	test.parse("CONST set = {0..32 - 1};");
+	test.parse("CONST set = {ci};");
+	test.parse("CONST i1 = 1; b1 = TRUE;");
+	test.parse("CONST i1 = 1; i2 = i1 + 1;");
+	test.parse("CONST i1 = 1; i2 = i1 + 1; i3 = i2 + 2;");
+	test.expectError("CONST i1 = v1;", "constant expression expected");
+	test.expectError("CONST i1 = v1 * 2;", "constant expression expected");
+	test.expectError("CONST i1 = v1 - 10;", "constant expression expected");
+	test.expectError("CONST i1 = 10 - v1;", "constant expression expected");
+	test.expectError("CONST s = {v1};", "constant expression expected");
+	test.expectError("CONST s = {1, v1};", "constant expression expected");
+	test.expectError("CONST s = {1..v1};", "constant expression expected");
+	test.expectError("CONST s = {10 - v1..15};", "constant expression expected");
+},
+"record declaration": function(){
+	var parse = function(s) { return parseUsingGrammar(Grammar.typeDeclaration, s); };
+	assert(parse("t = RECORD END"));
+	assert(parse("t = RECORD i: INTEGER END"));
+	assert(parse("t = RECORD i, j: INTEGER END"));
+	assert(!parse("t = RECORD i, j, i: INTEGER END"));
+	assert(parse("t = RECORD i, j: INTEGER; b: BOOLEAN END"));
+},
+"array declaration": function(){
+	var test = setupWithContext(
+		  Grammar.typeDeclaration
+		, "CONST c1 = 5; VAR v1: INTEGER;");
+	test.parse("T = ARRAY 10 OF INTEGER");
+	test.parse("T = ARRAY 10 OF BOOLEAN");
+	test.expectError("T = ARRAY 0 OF INTEGER", "array size must be greater than 0, got 0");
+	test.expectError("T = ARRAY TRUE OF INTEGER"
+				   , "'INTEGER' constant expression expected, got 'BOOLEAN'");
+	test.parse("T = ARRAY 1 + 2 OF INTEGER");
+	test.parse("T = ARRAY c1 OF INTEGER");
+	test.expectError("T = ARRAY v1 OF INTEGER", "constant expression expected as ARRAY size");
+	test.expectError("T = ARRAY c1 - 10 OF INTEGER", "array size must be greater than 0, got -5");
+},
+"multi-dimensional array declaration": function(){
+	var test = setup(Grammar.typeDeclaration);
+
+	test.parse("T = ARRAY 10 OF ARRAY 5 OF INTEGER");
+	test.parse("T = ARRAY 10, 5 OF INTEGER");
+},
+"PROCEDURE type declaration": function(){
+	var test = setup(Grammar.typeDeclaration);
+
+	test.parse("T = PROCEDURE");
+	test.parse("T = PROCEDURE()");
+	test.parse("T = PROCEDURE(a: INTEGER)");
+	test.parse("T = PROCEDURE(a: INTEGER; b: BOOLEAN)");
+	test.parse("T = PROCEDURE(): T");
+},
+"POINTER declaration": function(){
+	var test = setup(Grammar.typeDeclaration);
+
+	test.parse("T = POINTER TO RECORD END");
+	test.parse("T = POINTER TO NotDeclaredYet");
+	test.parse("T = POINTER TO RECORD p: POINTER TO T END");
+	test.expectError("T = POINTER TO INTEGER"
+				   , "RECORD is expected as a POINTER base type, got 'INTEGER'");
+	test.expectError("T = POINTER TO POINTER TO RECORD END"
+				   , "RECORD is expected as a POINTER base type, got 'POINTER TO anonymous RECORD'");
+},
+"POINTER dereference": function(){
+	var test = setupWithContext(
+		  Grammar.statement
+		, "VAR p: POINTER TO RECORD field: INTEGER END; i: INTEGER; r: RECORD END;");
+
+	test.parse("p^.field := 1");
+	test.parse("p.field := 0");
+	test.expectError("i^", "POINTER TO type expected, got 'INTEGER'");
+	test.expectError("r^", "POINTER TO type expected, got 'anonymous RECORD'");
+},
+"POINTER assignment": function(){
+	var test = setupWithContext(
+		  Grammar.statement
+		, "TYPE Base = RECORD END; Derived = RECORD (Base) END;"
+		+ "VAR p1, p2: POINTER TO RECORD END; pBase: POINTER TO Base; pDerived: POINTER TO Derived;");
+
+	test.parse("p1 := NIL");
+	test.parse("p1 := p2");
+	test.parse("pBase := pDerived");
+	test.expectError("p1 := pBase"
+				   , "type mismatch: 'p1' is 'POINTER TO anonymous RECORD' and cannot be assigned to 'POINTER TO Base' expression");
+	test.expectError("pDerived := pBase"
+				   , "type mismatch: 'pDerived' is 'POINTER TO Derived' and cannot be assigned to 'POINTER TO Base' expression");
+	test.expectError("NIL := p1", "not parsed");
+},
+"POINTER cast": function(){
+	var test = setupWithContext(
+		  Grammar.expression
+		, "TYPE Base = RECORD END; Derived = RECORD (Base) END; PDerived = POINTER TO Derived;"
+		+ "VAR p1, p2: POINTER TO RECORD END; pBase: POINTER TO Base; pDerived: POINTER TO Derived; i: INTEGER;");
+
+	test.parse("pBase(Derived)");
+	test.expectError("pDerived(Derived)"
+				   , "invalid type cast: 'Derived' is not an extension of 'Derived'");
+	test.expectError("p1(Base)"
+				   , "invalid type cast: 'Base' is not an extension of 'anonymous RECORD'");
+	test.expectError("p1(INTEGER)"
+				   , "invalid type cast: RECORD type expected as an argument of type guard, got 'INTEGER'");
+	test.expectError("p1(PDerived)"
+				   , "invalid type cast: RECORD type expected as an argument of type guard, got 'PDerived'");
+	test.expectError("i(Derived)"
+				   , "invalid type cast: 'Derived' is not an extension of 'INTEGER'");
+},
+"IS expression": function(){
+	var test = setupWithContext(
+		  Grammar.expression
+		, "TYPE Base = RECORD END; Derived = RECORD (Base) END; PDerived = POINTER TO Derived;"
+		+ "VAR p: POINTER TO RECORD END; pBase: POINTER TO Base; pDerived: POINTER TO Derived; vDerived: Derived; i: INTEGER;");
+
+	test.parse("pBase IS Derived");
+	test.expectError("pBase IS pDerived", "RECORD type expected after 'IS'");
+	test.expectError("pBase IS TRUE", "RECORD type expected after 'IS'");
+	test.expectError("pBase IS vDerived", "type name expected");
+	test.expectError("Derived IS Derived", "POINTER to type expected before 'IS'");
+	test.expectError("i IS Derived", "POINTER to type expected before 'IS'");
+	test.expectError("p IS Derived"
+				   , "invalid type test: 'Derived' is not an extension of 'anonymous RECORD'");
+	test.expectError("pDerived IS Derived"
+				   , "invalid type test: 'Derived' is not an extension of 'Derived'");
+	test.expectError("pDerived IS Base"
+				   , "invalid type test: 'Base' is not an extension of 'Derived'");
+	test.expectError("pDerived IS INTEGER", "RECORD type expected after 'IS'");
+},
+"NEW": function(){
+	var test = setupWithContext(
+		  Grammar.statement
+		, "TYPE P = POINTER TO RECORD END;"
+		+ "VAR p: P; i: INTEGER;"
+		+ "PROCEDURE proc(): P; RETURN NIL END proc;"
+		);
+
+	test.parse("NEW(p)");
+	test.expectError("NEW.NEW(p)", "cannot designate 'predefined procedure NEW'");
+	test.expectError("NEW(i)", "POINTER variable expected, got 'INTEGER'");
+	test.expectError("NEW()", "1 argument(s) expected, got 0");
+	test.expectError("NEW(p, p)", "1 argument(s) expected, got 2");
+	test.expectError("NEW(proc())", "expression cannot be used as VAR parameter");
+},
+"NEW for read only array element fails": function(){
+	var test = setupWithContext(Grammar.procedureDeclaration
+							  , "TYPE P = POINTER TO RECORD END;");
+	test.expectError("PROCEDURE readOnlyPointers(a: ARRAY OF P); BEGIN NEW(a[0]) END readOnlyPointers",
+					 "read-only variable cannot be used as VAR parameter");
+},
+"assignment statement": function(){
+	var test = setupWithContext(
+		  Grammar.statement
+		, "CONST c = 15;"
+		+ "VAR ch: CHAR; i, n: INTEGER; b: BOOLEAN;"
+		    + "proc1: PROCEDURE; proc2: PROCEDURE(): INTEGER;"
+		    + "a: ARRAY 5 OF INTEGER;"
+		+ "PROCEDURE p(): INTEGER; RETURN 1 END p;"
+		+ "PROCEDURE noResult(); END noResult;");
+	test.parse("i := 0");
+	test.parse("i := n");
+	test.parse("i := c");
+	test.parse("b := TRUE");
+	test.parse("ch := \"A\"");
+	test.parse("i := p()");
+	test.parse("proc1 := proc1");
+	test.parse("proc2 := NIL");
+	test.parse("a[1] := 2");
+	test.expectError("i := b", "type mismatch: 'i' is 'INTEGER' and cannot be assigned to 'BOOLEAN' expression");
+	test.expectError("c := i", "cannot assign to constant");
+	test.expectError("ch := \"AB\""
+				   , "type mismatch: 'ch' is 'CHAR' and cannot be assigned to 'multi-character string' expression");
+	test.expectError("i := .1", "expression expected");
+	test.expectError("proc1 := proc2"
+				   , "type mismatch: 'proc1' is 'PROCEDURE' and cannot be assigned to 'PROCEDURE(): INTEGER' expression");
+	test.expectError("i := noResult()", "procedure returning no result cannot be used in an expression");
+	},
+"array expression": function(){
+	var test = setup(Grammar.procedureBody);
+	test.parse("VAR a: ARRAY 10 OF INTEGER; BEGIN a[0] := 1 END");
+	test.parse("VAR a: ARRAY 10 OF INTEGER; BEGIN a[0] := 1; a[1] := a[0] END");
+	test.expectError("VAR a: ARRAY 10 OF INTEGER; BEGIN a[0] := TRUE END"
+				   , "type mismatch: 'a[0]' is 'INTEGER' and cannot be assigned to 'BOOLEAN' expression");
+	test.expectError("VAR a: ARRAY 10 OF INTEGER; BEGIN a[TRUE] := 1 END"
+				   , "'INTEGER' expression expected, got 'BOOLEAN'");
+	test.expectError("VAR i: INTEGER; BEGIN i[0] := 1 END"
+				   , "ARRAY expected, got 'INTEGER'");
+	test.expectError("VAR a: ARRAY 10 OF INTEGER; BEGIN a[0][0] := 1 END"
+				   , "ARRAY expected, got 'INTEGER'");
+	test.expectError("VAR a: ARRAY 10 OF BOOLEAN; BEGIN a[0,0] := TRUE END"
+				   , "ARRAY expected, got 'BOOLEAN'");
+	test.expectError("VAR a: ARRAY 10, 20 OF BOOLEAN; BEGIN a[0] := TRUE END"
+				   , "type mismatch: 'a[0]' is 'ARRAY OF BOOLEAN' and cannot be assigned to 'BOOLEAN' expression");
+	test.expectError("VAR a: ARRAY 10 OF INTEGER; BEGIN a[10] := 0 END"
+				   , "index out of bounds: maximum possible index is 9, got 10");
+	test.expectError("CONST c1 = 5; VAR a: ARRAY 10 OF INTEGER; BEGIN a[10 + c1] := 0 END"
+				   , "index out of bounds: maximum possible index is 9, got 15");
+},
+"multi-dimensional array expression": function(){
+	var test = setup(Grammar.procedureBody);
+	test.parse("VAR a: ARRAY 10 OF ARRAY 5 OF INTEGER; BEGIN a[0][0] := 1 END");
+	test.parse("VAR a: ARRAY 10, 5 OF BOOLEAN; BEGIN a[0][0] := TRUE END");
+	test.parse("VAR a: ARRAY 10, 5 OF BOOLEAN; BEGIN a[0, 0] := TRUE END");
+},
+"INTEGER number": function(){
+	var test = setup(Grammar.expression);
+	test.parse("0");
+	test.parse("123");
+	test.parse("1H");
+	test.parse("1FH");
+	test.parse("0FFH");
+	test.parse("0H");
+	test.expectError("FFH", "undeclared identifier: 'FFH'");
+	test.expectError("FF", "undeclared identifier: 'FF'");
+	test.expectError("1HH", "not parsed");
+	test.expectError("1H0", "not parsed");
+	test.expectError("1 23", "not parsed");
+	test.expectError("1F FH", "not parsed");
+},
+"SET statement": function(){
+	var test = setupWithContext(Grammar.statement, "VAR s: SET;");
+	test.parse("s := {}");
+	test.parse("s := {0}");
+	test.parse("s := {0, 1}");
+	test.parse("s := {1 + 2, 5..10}");
+	//test.expectError("s := {32}", "0..31");
+},
+"REAL number": function(){
+	var test = setup(Grammar.expression);
+	test.parse("1.2345");
+	test.parse("1.");
+	test.parse("1.2345E6");
+	test.parse("1.2345E+6");
+	test.parse("1.2345E-12");
+	test.expectError("1. 2345E-12", "not parsed");
+	test.expectError("1.23 45E-12", "not parsed");
+	test.expectError("1.2345 E-12", "not parsed");
+	test.expectError("1.2345E-1 2", "not parsed");
+},
+"LONGREAL number": function(){
+	var test = setup(Grammar.expression);
+	test.parse("1.2345D6");
+	test.parse("1.2345D+6");
+	test.parse("1.2345D-6");
+},
+"IF statement": function(){
+	var test = setupWithContext(
+		  Grammar.statement
+		, "VAR b1: BOOLEAN; i1: INTEGER;");
+	test.parse("IF b1 THEN i1 := 0 END");
+	test.parse("IF FALSE THEN i1 := 0 ELSE i1 := 1 END");
+	test.parse("IF TRUE THEN i1 := 0 ELSIF FALSE THEN i1 := 1 ELSE i1 := 2 END");
+	test.expectError("IF i1 THEN i1 := 0 END", "'BOOLEAN' expression expected, got 'INTEGER'");
+	test.expectError("IF b1 THEN i1 := 0 ELSIF i1 THEN i1 := 2 END"
+				   , "'BOOLEAN' expression expected, got 'INTEGER'");
+},
+"CASE statement": function(){
+	var test = setupWithContext(
+		  Grammar.statement
+		, "CONST ci = 15; cc = \"A\";	VAR	c1: CHAR; b1: BOOLEAN; i1, i2: INTEGER;");
+	test.parse("CASE i1 OF END");
+	test.parse("CASE i1 OF 0: b1 := TRUE END");
+	test.parse("CASE c1 OF \"A\": b1 := TRUE END");
+	test.parse("CASE i1 OF 0: b1 := TRUE | 1: b1 := FALSE END");
+	test.parse("CASE i1 OF 0, 1: b1 := TRUE END");
+	test.parse("CASE c1 OF \"A\", \"B\": b1 := TRUE END");
+	test.parse("CASE i1 OF 0..2: b1 := TRUE END");
+	test.parse("CASE i1 OF ci..2: b1 := TRUE END");
+	test.parse("CASE c1 OF cc..\"Z\": b1 := TRUE END");
+	test.parse("CASE i1 OF 1, 2, 3: b1 := TRUE | 4..10: b1 := FALSE | 11: c1 := \"A\" END");
+	test.parse("CASE i1 OF 1, 2, 5..9: b1 := TRUE END");
+	test.expectError("CASE i1 OF undefined: b1 := TRUE END"
+				   , "undeclared identifier: 'undefined'");
+	test.expectError("CASE i1 OF i2: b1 := TRUE END"
+				   , "'i2' is not a constant");
+	test.expectError("CASE b1 OF END", "'INTEGER' or 'CHAR' expected as CASE expression");
+	test.expectError("CASE i1 OF \"A\": b1 := TRUE END"
+				   , "label must be 'INTEGER' (the same as case expression), got 'CHAR'");
+	test.expectError("CASE c1 OF \"A\", 1: b1 := TRUE END"
+				   , "label must be 'CHAR' (the same as case expression), got 'INTEGER'");
+	test.expectError("CASE c1 OF \"A\"..1: b1 := TRUE END"
+				   , "label must be 'CHAR' (the same as case expression), got 'INTEGER'");
+},
+"WHILE statement": function(){
+	var test = setupWithContext(
+		  Grammar.statement
+		, "VAR b1: BOOLEAN; i1: INTEGER;");
+	test.parse("WHILE TRUE DO i1 := 0 END");
+	test.parse("WHILE b1 DO i1 := 0 ELSIF FALSE DO i1 := 1 END");
+	test.expectError("WHILE i1 DO i1 := 0 END", "'BOOLEAN' expression expected, got 'INTEGER'");
+	test.expectError("WHILE b1 DO i1 := 0 ELSIF i1 DO i1 := 1 END", "'BOOLEAN' expression expected, got 'INTEGER'");
+},
+"REPEAT statement": function(){
+	var test = setupWithContext(
+		  Grammar.statement
+		, "VAR b1: BOOLEAN; i1: INTEGER;");
+	test.parse("REPEAT i1 := 0 UNTIL TRUE");
+	test.parse("REPEAT i1 := 0 UNTIL b1");
+	test.expectError("REPEAT i1 := 0 UNTIL i1", "'BOOLEAN' expression expected, got 'INTEGER'");
+},
+"FOR statement": function(){
+	var test = setupWithContext(
+		  Grammar.statement
+		, "CONST c = 15; VAR b: BOOLEAN; i, n: INTEGER;");
+	test.parse("FOR i := 0 TO 10 DO n := 1 END");
+	test.parse("FOR i := 0 TO 10 BY 5 DO b := TRUE END");
+	test.parse("FOR i := 0 TO n DO b := TRUE END");
+	test.parse("FOR i := 0 TO n BY c DO n := 1; b := FALSE END");
+	test.expectError("FOR undefined := 0 TO 10 DO n := 1 END"
+				   , "undeclared identifier: 'undefined'");
+	test.expectError("FOR b := TRUE TO 10 DO n := 1 END"
+				   , "'b' is a 'BOOLEAN' variable, 'FOR' control variable must be 'INTEGER'");
+	test.expectError("FOR c := 0 TO 10 DO END", "'c' is not a variable");
+	test.expectError("FOR i := TRUE TO 10 DO n := 1 END"
+				   , "'INTEGER' expression expected to assign 'i', got 'BOOLEAN'");
+	test.expectError("FOR i := 0 TO TRUE DO END"
+				   , "'INTEGER' expression expected as 'TO' parameter, got 'BOOLEAN'");
+	test.expectError("FOR i := 0 TO 10 BY n DO END"
+				   , "constant expression expected as 'BY' parameter");
+	test.expectError("FOR i := 0 TO 10 BY TRUE DO END"
+				   , "'INTEGER' expression expected as 'BY' parameter, got 'BOOLEAN'");
+},
+"logical operators": function(){
+	var test = setupWithContext(
+		  Grammar.statement, "VAR b1, b2: BOOLEAN; i1: INTEGER;");
+
+	test.parse("b1 := b1 OR b2");
+	test.parse("b1 := b1 & b2");
+	test.parse("b1 := ~b2");
+	test.expectError("b1 := i1 OR b2", "BOOLEAN expected as operand of 'OR', got 'INTEGER'");
+	test.expectError("b1 := b1 OR i1", "type mismatch: expected 'BOOLEAN', got 'INTEGER'");
+	test.expectError("b1 := i1 & b2", "BOOLEAN expected as operand of '&', got 'INTEGER'");
+	test.expectError("b1 := b1 & i1", "type mismatch: expected 'BOOLEAN', got 'INTEGER'");
+	test.expectError("b1 := ~i1", "type mismatch: expected 'BOOLEAN', got 'INTEGER'");
+},
+"arithmetic operators": function(){
+	var test = setupWithContext(
+		  Grammar.statement, "VAR b1: BOOLEAN; i1, i2: INTEGER; r1, r2: REAL;");
+
+	test.parse("i1 := i1 + i2");
+	test.parse("i1 := i1 - i2");
+	test.parse("i1 := i1 * i2");
+	test.parse("i1 := i1 DIV i2");
+	test.parse("i1 := i1 MOD i2");
+	test.parse("r1 := r1 + r2");
+	test.parse("r1 := r1 - r2");
+	test.parse("r1 := r1 * r2");
+	test.parse("r1 := r1 / r2");
+},
+"relations are BOOLEAN": function(){
+	var test = setupWithContext(
+		  Grammar.statement
+		, "TYPE Base = RECORD END; Derived = RECORD (Base) END;"
+		+ "VAR pBase: POINTER TO Base; proc1, proc2: PROCEDURE;"
+			+ "set1, set2: SET;"
+			+ "b: BOOLEAN; i1, i2: INTEGER; r1, r2: REAL; c1, c2: CHAR; ca1, ca2: ARRAY 10 OF CHAR;");
+
+	test.parse("b := pBase IS Derived");
+	test.parse("b := pBase = pBase");
+	test.parse("b := proc1 # proc2");
+	test.parse("b := set1 <= set2");
+	test.parse("b := i1 IN set2");
+	test.parse("b := i1 < i2");
+	test.parse("b := c1 > c2");
+	test.parse("b := ca1 <= ca2");
+	test.parse("b := r1 >= r2");
+},
+"SET relations": function(){
+	var test = setupWithContext(
+		  Grammar.expression
+		, "VAR set1, set2: SET; b: BOOLEAN; i: INTEGER;");
+
+	test.parse("set1 <= set2");
+	test.parse("set1 >= set2");
+	test.parse("set1 = set2");
+	test.parse("set1 # set2");
+	test.parse("i IN set1");
+
+	test.expectError("set1 <= i", "type mismatch: expected 'SET', got 'INTEGER'");
+	test.expectError("b IN set1", "'INTEGER' expected as an element of SET, got 'BOOLEAN'");
+	test.expectError("i IN b", "type mismatch: expected 'SET', got 'BOOLEAN'");
+},
+"SET operators": function(){
+	var test = setupWithContext(
+		  Grammar.expression
+		, "VAR set1, set2: SET; b: BOOLEAN; i: INTEGER;");
+
+	test.parse("set1 + set2");
+	test.parse("set1 - set2");
+	test.parse("set1 * set2");
+	test.parse("set1 / set2");
+	test.parse("-set1");
+
+	test.expectError("set1 + i", "type mismatch: expected 'SET', got 'INTEGER'");
+	test.expectError("set1 - b", "type mismatch: expected 'SET', got 'BOOLEAN'");
+	test.expectError("set1 * b", "type mismatch: expected 'SET', got 'BOOLEAN'");
+	test.expectError("set1 / b", "type mismatch: expected 'SET', got 'BOOLEAN'");
+},
+"SET functions": function(){
+	var test = setupWithContext(
+		  Grammar.statement
+		, "VAR set1, set2: SET; b: BOOLEAN; i: INTEGER;");
+
+	test.parse("INCL(set1, i)");
+	test.parse("EXCL(set1, i)");
+	test.expectError("INCL({}, i)", "expression cannot be used as VAR parameter");
+},
+"procedure body": function(){
+	var test = setup(Grammar.procedureBody);
+	test.parse("END");
+	test.parse("VAR END");
+	test.parse("VAR i: INTEGER; END");
+	test.parse("VAR a: ARRAY 10 OF INTEGER; END");
+	test.expectError("VAR i: INTEGER;", "not parsed");
+	test.parse("VAR i: INTEGER; BEGIN i := 1 END");
+	test.parse("VAR b: BOOLEAN; BEGIN b := TRUE END");
+	test.expectError("VAR i: INTEGER; BEGIN j := 1 END", "undeclared identifier: 'j'");
+	test.expectError("VAR i: INTEGER; BEGIN i.field := 1 END",
+					 "cannot designate 'INTEGER'");
+	test.expectError("VAR i: INTEGER; BEGIN i := j END", "undeclared identifier: 'j'");
+	test.parse("VAR i, j: INTEGER; BEGIN i := 1; j := 2; i := 1 + i + j - 2 END");
+	test.expectError("TYPE T = RECORD field: INTEGER END; VAR v: T; BEGIN v := 1 END"
+				   , "type mismatch: 'v' is 'T' and cannot be assigned to 'INTEGER' expression");
+	test.expectError("TYPE T = RECORD field: INTEGER END; VAR v: T; BEGIN v.unknown := 1 END"
+				   , "Type 'T' has no 'unknown' field");
+	test.parse("TYPE T = RECORD field: INTEGER END; VAR v: T; BEGIN v.field := 1 END");
+	test.parse("TYPE T1 = RECORD field: INTEGER END; T2 = RECORD field: T1 END; VAR v1: T1; v2: T2; BEGIN v1.field := v2.field.field END");
+	test.parse("TYPE T1 = RECORD field1: INTEGER END; T2 = RECORD (T1) field2: INTEGER END; VAR v: T2; BEGIN v.field2 := v.field1 END");
+	test.expectError("TYPE T1 = RECORD field1: INTEGER END; T2 = RECORD (T1) field1: INTEGER END; END"
+			       , "base record already has field: 'field1'");
+},
+"procedure heading": function(){
+	function makeContext(cx){return new Context.ProcDecl(new Context.Context(cx));}
+	var test = setup(Grammar.procedureHeading, makeContext);
+
+	test.parse("PROCEDURE p");
+	test.parse("PROCEDURE p(a1: INTEGER)");
+	test.parse("PROCEDURE p(a1, a2: INTEGER; b1: BOOLEAN)");
+	test.expectError("PROCEDURE p(a1: INTEGER; a1: BOOLEAN)", "'a1' already declared");
+	test.expectError("PROCEDURE p(p: INTEGER)", "argument 'p' has the same name as procedure");
+},
+procedure: function(){
+	var test = setupWithContext(Grammar.procedureDeclaration
+							  , "TYPE ProcType = PROCEDURE(): ProcType;");
+	test.parse("PROCEDURE p; END p");
+	test.expectError("PROCEDURE p; END", "not parsed");
+	test.expectError("PROCEDURE p1; END p2"
+				   , "mismatched procedure names: 'p1' at the begining and 'p2' at the end");
+	test.parse("PROCEDURE p; VAR i: INTEGER; BEGIN i := i + 1 END p");
+	test.parse("PROCEDURE p(a: INTEGER); BEGIN a := a + 1 END p");
+	test.expectError("PROCEDURE p(a: INTEGER); VAR a: INTEGER END p", "'a' already declared");
+	test.parse("PROCEDURE p; BEGIN p() END p");
+	test.expectError("PROCEDURE p(a: INTEGER); BEGIN p() END p", "1 argument(s) expected, got 0");
+	test.expectError("PROCEDURE p(a: INTEGER); BEGIN p(1, 2) END p", "1 argument(s) expected, got 2");
+	test.parse("PROCEDURE p(a: INTEGER); BEGIN p(a) END p");
+	test.parse("PROCEDURE p(a: INTEGER; b: BOOLEAN); BEGIN p(a, b) END p");
+	test.expectError("PROCEDURE p(a: INTEGER; b: BOOLEAN); BEGIN p(b, a) END p"
+				  , "expect 'INTEGER' type for argument 0, got 'BOOLEAN'");
+	test.expectError("PROCEDURE p; BEGIN p1() END p", "undeclared identifier: 'p1'");
+
+	test.parse("PROCEDURE p(): ProcType; RETURN p END p");
+},
+"procedure RETURN": function(){
+	var test = setupWithContext(Grammar.procedureDeclaration
+							  , "VAR i: INTEGER; PROCEDURE int(): INTEGER; RETURN 1 END int;");
+	test.parse("PROCEDURE p(): BOOLEAN; RETURN TRUE END p");
+	test.parse("PROCEDURE p(): BOOLEAN; RETURN int() = 1 END p");
+	test.expectError("PROCEDURE p; RETURN TRUE END p"
+				   , "unexpected RETURN in PROCEDURE declared with no result type");
+	test.expectError("PROCEDURE p(): BOOLEAN; END p", "RETURN expected at the end of PROCEDURE declared with 'BOOLEAN' result type");
+	test.expectError("PROCEDURE p(): undeclared; END p", "undeclared identifier: 'undeclared'");
+	test.expectError("PROCEDURE p(): i; END p", "type name expected");
+	test.expectError("PROCEDURE p(): INTEGER; RETURN TRUE END p"
+				   , "RETURN 'INTEGER' expected, got 'BOOLEAN'");
+},
+"pass VAR argument as VAR parameter": function(){
+	var test = setupWithContext(Grammar.procedureDeclaration,
+								"PROCEDURE p1(VAR i: INTEGER); END p1;"
+								+ "PROCEDURE p2(VAR b: BOOLEAN); END p2;"
+								);
+	test.parse("PROCEDURE p(VAR i1: INTEGER); BEGIN p1(i1) END p");
+	test.expectError("PROCEDURE p(VAR b: BOOLEAN); BEGIN p2(~b) END p", "expression cannot be used as VAR parameter");
+},
+"VAR parameter": function(){
+	var test = setupWithContext(Grammar.statement
+							  , "CONST c = 123;"
+							  + "VAR i1: INTEGER; b1: BOOLEAN; a1: ARRAY 5 OF INTEGER;"
+							    + "r1: RECORD f1: INTEGER END;"
+							  + "PROCEDURE p1(VAR i: INTEGER); END p1;"
+							  + "PROCEDURE p2(VAR b: BOOLEAN); END p2;"
+							  );
+	test.parse("p1(i1)");
+	test.parse("p1(a1[0])");
+	test.parse("p1(r1.f1)");
+	test.expectError("p1(c)", "constant cannot be used as VAR parameter");
+	test.expectError("p1(123)", "expression cannot be used as VAR parameter");
+	test.expectError("p2(TRUE)", "expression cannot be used as VAR parameter");
+	test.expectError("p1(i1 + i1)", "expression cannot be used as VAR parameter");
+	test.expectError("p1(i1 * i1)", "expression cannot be used as VAR parameter");
+	test.expectError("p1(+i1)", "expression cannot be used as VAR parameter");
+	test.expectError("p1(-i1)", "expression cannot be used as VAR parameter");
+	test.expectError("p2(~b1)", "expression cannot be used as VAR parameter");
+},
+"ARRAY parameter": function(){
+	var test = setupWithContext(Grammar.procedureDeclaration
+							  , "TYPE T = RECORD i: INTEGER; p: POINTER TO T END;"
+							  + "PROCEDURE p1(i: INTEGER); END p1;"
+							  + "PROCEDURE varInteger(VAR i: INTEGER); END varInteger;"
+							  + "PROCEDURE p2(a: ARRAY OF INTEGER); END p2;"
+							  + "PROCEDURE p3(VAR a: ARRAY OF INTEGER); END p3;"
+							  );
+	test.parse("PROCEDURE p(a: ARRAY OF INTEGER); END p");
+	test.parse("PROCEDURE p(a: ARRAY OF ARRAY OF INTEGER); END p");
+	test.parse("PROCEDURE p(a: ARRAY OF ARRAY OF INTEGER); BEGIN p1(a[0][0]) END p");
+	test.parse("PROCEDURE p(a: ARRAY OF INTEGER); BEGIN p2(a) END p");
+	test.parse("PROCEDURE p(a: ARRAY OF T); BEGIN varInteger(a[0].p.i) END p");
+	test.expectError("PROCEDURE p(a: ARRAY OF INTEGER); BEGIN a[0] := 0 END p",
+					 "cannot assign to read-only variable");
+	test.expectError("PROCEDURE p(a: ARRAY OF INTEGER); BEGIN p3(a) END p",
+					 "read-only variable cannot be used as VAR parameter");
+	test.expectError("PROCEDURE p(a: ARRAY OF T); BEGIN a[0].i := 0 END p",
+					 "cannot assign to read-only variable");
+	test.expectError("PROCEDURE p(a: ARRAY OF T); BEGIN varInteger(a[0].i) END p",
+					 "read-only variable cannot be used as VAR parameter");
+},
+"procedure call": function(){
+	var test = setupWithContext(Grammar.statement
+							  , "TYPE ProcType = PROCEDURE;"
+							  + "VAR notProcedure: INTEGER;"
+							  + "PROCEDURE p; END p;"
+							  + "PROCEDURE p1(i: INTEGER); END p1;"
+							  + "PROCEDURE p2(i: INTEGER; b: BOOLEAN); END p2;"
+							  + "PROCEDURE p3(): ProcType; RETURN p END p3;"
+							   );
+	test.parse("p");
+	test.parse("p()");
+
+	test.parse("p1(1)");
+	test.parse("p1(1 + 2)");
+
+	test.parse("p2(1, TRUE)");
+	test.expectError("notProcedure", "PROCEDURE expected, got 'INTEGER'");
+	test.expectError("p2(TRUE, 1)", "expect 'INTEGER' type for argument 0, got 'BOOLEAN'");
+	test.expectError("p2(1, 1)", "expect 'BOOLEAN' type for argument 1, got 'INTEGER'");
+	test.expectError("p3()()", "not parsed");
+},
+"procedure assignment": function(){
+	var test = setupWithContext(
+		  Grammar.statement
+		, "TYPE ProcType1 = PROCEDURE(): ProcType1;"
+		      + "ProcType2 = PROCEDURE(): ProcType2;"
+		      + "ProcType3 = PROCEDURE(p: ProcType3): ProcType3;"
+		      + "ProcType4 = PROCEDURE(p: ProcType4): ProcType4;"
+		      + "ProcType4VAR = PROCEDURE(VAR p: ProcType4VAR): ProcType4VAR;"
+		      + "ProcType5 = PROCEDURE(p: ProcType3): ProcType4;"
+		      + "ProcType6 = PROCEDURE(p: INTEGER);"
+		      + "ProcType7 = PROCEDURE(VAR p: INTEGER);"
+		+ "VAR v1: ProcType1; v2: ProcType2;"
+		    + "v3: PROCEDURE(i: INTEGER): ProcType1; v4: PROCEDURE(b: BOOLEAN): ProcType1;"
+		    + "v5: PROCEDURE(p: ProcType1); v6: PROCEDURE(p: ProcType2);"
+		    + "v7: ProcType3; v8: ProcType4; v8VAR: ProcType4VAR; v9: ProcType5; v10: ProcType6; v11: ProcType7;"
+		+ "PROCEDURE p1(): ProcType1; RETURN p1 END p1;"
+		);
+	test.parse("v1 := v2");
+	test.parse("v5 := v6");
+	test.parse("v7 := v8");
+	test.parse("v7 := v9");
+	test.parse("v8 := v9");
+	test.parse("v1 := p1");
+	test.expectError("p1 := v1", "cannot assign to procedure" );
+	test.expectError(
+		  "v3 := v1"
+		, "type mismatch: 'v3' is 'PROCEDURE(INTEGER): ProcType1' and cannot be assigned to 'ProcType1' expression");
+	test.expectError(
+		  "v3 := v4"
+		, "type mismatch: 'v3' is 'PROCEDURE(INTEGER): ProcType1' and cannot be assigned to 'PROCEDURE(BOOLEAN): ProcType1' expression");
+	test.expectError(
+		  "v10 := NEW"
+		, "type mismatch: 'v10' is 'ProcType6' and cannot be assigned to 'predefined procedure NEW' expression");
+	test.expectError("v10 := v11", "type mismatch: 'v10' is 'ProcType6' and cannot be assigned to 'ProcType7' expression" );
+	test.expectError("v8 := v8VAR", "type mismatch: 'v8' is 'ProcType4' and cannot be assigned to 'ProcType4VAR' expression" );
+},
+"scope": function(){
+	var test = setup(Grammar.declarationSequence);
+	test.parse("PROCEDURE p1(a1: INTEGER); END p1; PROCEDURE p2(a1: BOOLEAN); END p2;");
+},
+module: function(){
+	var test = setup(Grammar.module);
+	test.parse("MODULE m; END m.");
+	test.expectError("MODULE m; END undeclared.",
+					 "original module name 'm' expected, got 'undeclared'");
+},
+IMPORT: function(){
+	var test = setup(Grammar.module);
+	test.parse("MODULE m; IMPORT JS; END m.");
+	test.parse("MODULE m; IMPORT JS; BEGIN JS.alert(\"test\") END m.");
+	test.parse("MODULE m; IMPORT JS; BEGIN JS.console.info(123) END m.");
+}};
+
+function runTest(t){
+	var result = false;
+	var padding = "                           ";
+	var log = t;
+	if (log.length < padding.length)
+		log = t + padding.substring(log.length);
+	else
+		log += " ";
+
+	try {
+		testSuite[t]();
+		log += "OK";
+		result = true;
+	}
+	catch (x){
+		if (x instanceof TestError)
+			log += "Failed\n\t" + x;
+		else
+			log += "Failed\n" + (x.stack ? x.stack : '\t' + x);
+	}
+	console.log(log);
+	return result;
+}
+
+var failCount = 0;
+var start = Date.now();
+
+if (typeof process != "undefined" && process.argv.length > 2)
+	runTest(process.argv[2]);
+else {
+	console.log("Running " + Object.keys(testSuite).length + " tests...");
+
+	for(var t in testSuite)
+		if (!runTest(t))
+			++failCount;
+}
+
+var stop = Date.now();
+console.log("elapsed: " + (stop - start) / 1000 + " s" );
+
+if (!failCount)
+	console.log("All OK!");
+else
+	console.log(failCount + " test(s) failed");

+ 172 - 0
type.js

@@ -0,0 +1,172 @@
+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.bind(this)();
+	},
+	idType: function(){return "type";},
+	isProcedure: function(){return false;}
+});
+
+exports.Type = Type;
+
+exports.String = Type.extend({
+	init: function TypeString(s){
+		Type.prototype.init.bind(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;}
+});
+
+var BasicType = Type.extend({
+	init: function BasicType(name, initValue){
+		Type.prototype.init.bind(this)();
+		this.__name = name;
+		this.__initValue = initValue;
+	},
+	name: function() {return this.__name;},
+	description: function(){return this.name();},
+	initializer: function() {return this.__initValue;}
+});
+
+exports.Basic = BasicType;
+
+exports.Array = BasicType.extend({
+	init: function ArrayType(name, initializer, elementsType, size){
+		BasicType.prototype.init.bind(this)(name, initializer);
+		this.__elementsType = elementsType;
+		this.__size = size;
+	},
+	elementsType: function(){return this.__elementsType;},
+	arraySize: function(){return this.__size;}
+});
+
+exports.Pointer = BasicType.extend({
+	init: function PointerType(name, base){
+		BasicType.prototype.init.bind(this)(name, "null");
+		this.__base = base;
+	},
+	description: function(){
+		var name = this.name();
+		if (name.indexOf("$") != -1)
+			return "POINTER TO " + this.baseType().description();
+		return name;
+	},
+	baseType: function(){
+		if (this.__base instanceof exports.ForwardRecord)
+			this.__base = this.__base.resolve();
+		return this.__base;
+	}
+});
+
+exports.ForwardRecord = Type.extend({
+	init: function(resolve){
+		Type.prototype.init.bind(this)();
+		this.__resolve = resolve;
+	},
+	resolve: function(){return this.__resolve();}
+});
+
+exports.Record = BasicType.extend({
+	init: function RecordType(name){
+		BasicType.prototype.init.bind(this)(name, "new " + name + "()");
+		this.__fields = {};
+		this.__base = undefined;
+		this.__finalized = false;
+	},
+	addField: function(field, type){
+		if (this.__fields.hasOwnProperty(field))
+			throw new Errors.Error("duplicated field: '" + field + "'");
+		if (this.__base && this.__base.findSymbol(field))
+			throw new Errors.Error("base record already has field: '" + field + "'");
+		this.__fields[field] = 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;},
+	finalize: function(){this.__finalized = true;},
+	description: function(){
+		var name = this.name();
+		if (name.indexOf("$") != -1)
+			return "anonymous RECORD";
+		return name;
+	}
+});
+
+var NilType = Type.extend({
+	init: function NilType(){Type.prototype.init.bind(this)();},
+	idType: function(){return "NIL";},
+	description: function(){return "NIL";}
+});
+
+exports.basic = {
+	bool: new BasicType("BOOLEAN", false),
+	char: new BasicType("CHAR", 0),
+	int: new BasicType("INTEGER", 0),
+	real: new BasicType("REAL", 0),
+	set: new BasicType("SET", 0)
+};
+
+exports.nil = new NilType();
+
+exports.Const = Id.extend({
+	init: function Const(type, value){
+		Id.prototype.init.bind(this)();
+		this.__type = type;
+		this.__value = value;
+	},
+	idType: function(){return "constant";},
+	type: function(){return this.__type;},
+	value: function(){return this.__value;}
+});
+
+exports.Variable = Id.extend({
+	init: function Variable(type, isVar, isReadOnly){
+		Id.prototype.init.bind(this)();
+		this.__type = type;
+		this.__isVar = isVar;
+		this.__isReadOnly = isReadOnly;
+	},
+	idType: function(){return this.__isReadOnly ? "read-only variable" : "variable";},
+	type: function(){return this.__type;},
+	isVar: function(){return this.__isVar;},
+	isReadOnly: function(){return this.__isReadOnly;}
+});
+
+exports.Procedure = Id.extend({
+	init: function Procedure(type){
+		Id.prototype.init.bind(this)();
+		this.__type = type;
+	},
+	idType: function(){return "procedure";},
+	type: function(){return this.__type;}
+});
+
+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;},
+	isVariable: function(){return this.__info instanceof exports.Variable;},
+	isConst: function(){return this.__info instanceof exports.Const;},
+	isType: function(){return this.__info instanceof Type;},
+	isProcedure: function(){return this.__info instanceof exports.Procedure;},
+});
+
+exports.Symbol = Symbol;