Sfoglia il codice sorgente

support multiple modules

Vladislav Folts 12 anni fa
parent
commit
b1c0e47427
12 ha cambiato i file con 345 aggiunte e 221 eliminazioni
  1. 4 2
      src/code.js
  2. 71 44
      src/context.js
  3. 6 6
      src/lexer.js
  4. 1 1
      src/module.js
  5. 58 23
      src/oc.js
  6. 25 7
      src/scope.js
  7. 3 1
      src/stream.js
  8. 1 1
      src/symbol.js
  9. 120 120
      src/type.js
  10. 11 0
      test/expected/modules.js
  11. 12 0
      test/input/modules.ob
  12. 33 16
      test/test_unit.js

+ 4 - 2
src/code.js

@@ -5,7 +5,9 @@ var Type = require("type.js");
 
 var NullCodeGenerator = Class.extend({
 	init: function NullCodeGenerator(){},
-	write: function(){}
+	write: function(){},
+    openScope: function(){},
+    closeScope: function(){}
 });
 
 exports.Generator = Class.extend({
@@ -50,7 +52,7 @@ var Expression = Class.extend({
     designator: function(){return this.__designator;},
     constValue: function(){return this.__constValue;},
     maxPrecedence: function(){return this.__maxPrecedence;},
-    isTerm: function(){return !this.__designator && this.__maxPrecedence == undefined;},
+    isTerm: function(){return !this.__designator && this.__maxPrecedence === undefined;},
     deref: function(){
         if (!this.__designator)
             return this;

+ 71 - 44
src/context.js

@@ -8,15 +8,12 @@ var Module = require("module.js");
 var op = require("operator.js");
 var Parser = require("parser.js");
 var Procedure = require("procedure.js");
-var ImportRTL = require("rtl.js");
+var Class = require("rtl.js").Class;
 var Scope = require("scope.js");
 var Stream = require("stream.js").Stream;
 var Symbol = require("symbol.js");
 var Type = require("type.js");
 
-var RTL = ImportRTL.RTL;
-var Class = ImportRTL.Class;
-
 var basicTypes = Type.basic;
 
 function getSymbolAndScope(context, id){
@@ -213,6 +210,7 @@ exports.QualifiedIdentificator = ChainedContext.extend({
         ChainedContext.prototype.init.bind(this)(context);
         this.__module = undefined;
         this.__id = undefined;
+        this.__code = "";
     },
     setIdent: function(id){
         this.__id = id;
@@ -222,10 +220,12 @@ exports.QualifiedIdentificator = ChainedContext.extend({
         if (!s.isModule())
             return false; // stop parsing
         this.__module = s.info();
+        this.__code = this.__id + ".";
+        return undefined;
     },
     endParse: function(){
         var s = getSymbolAndScope(this.__module ? this.__module : this, this.__id);
-        this.parent().handleSymbol(s);
+        this.parent().handleSymbol(s, this.__code + this.__id);
     }
 });
 
@@ -262,7 +262,7 @@ exports.Designator = ChainedContext.extend({
         this.__derefCode = undefined;
         this.__propCode = undefined;
     },
-    handleSymbol: function(found){
+    handleSymbol: function(found, code){
         this.__scope = found.scope();
         var s = found.symbol();
         var info = s.info();
@@ -271,7 +271,7 @@ exports.Designator = ChainedContext.extend({
         else if (s.isVariable() || s.isConst())
             this.__currentType = info.type();
         this.__info = info;
-        this.__code.write(s.id());
+        this.__code.write(code);
     },
     setIdent: function(id){
         var t = this.__currentType;
@@ -867,12 +867,13 @@ exports.Set = ChainedContext.extend({
         this.__expr = "";
     },
     handleElement: function(from, fromValue, to, toValue){
-        if (fromValue !== undefined && (!to || toValue !== undefined))
+        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 += ", ";
@@ -1610,17 +1611,16 @@ exports.TypeCast = ChainedContext.extend({
 });
 
 function genExports(exports, gen){
-    if (!exports.length)
-        return;
-    gen.write("return {\n");
-    for(var i = 0; i < exports.length; ++i){
-        var e = exports[i];
-        var access = e.id();
+    var result = "";
+    for(var access in exports){
+        var e = exports[access];
         if (e.isVariable() && !(e.info().type() instanceof Type.Pointer))
             access = "function(){return " + access + ";}";
-        gen.write("\t" + e.id() + ": " + access + "\n");
+        result += "\t" + e.id() + ": " + access + "\n";
     }
-    gen.write("}\n");
+    if (!result.length)
+        return;
+    gen.write("return {\n" + result + "}\n");
 }
 
 exports.ModuleDeclaration = ChainedContext.extend({
@@ -1632,56 +1632,80 @@ exports.ModuleDeclaration = ChainedContext.extend({
         var gen = this.codeGenerator();
         if (this.__name === undefined ) {
             this.__name = id;
-            this.currentScope().addSymbol(new Symbol.Symbol(id, Type.module));
+            this.parent().pushScope(new Scope.Module(id));
             gen.write("var " + id + " = function " + "(){\n");
         }
         else if (id === this.__name){
-            var exports = this.parent().currentScope().exports();
+            var scope = this.parent().currentScope();
+            var exports = scope.exports();
+            scope.module().info().defineExports(exports);
             genExports(exports, gen);
-            gen.write("}();");
+            gen.write("}();\n");
         }
         else
             throw new Errors.Error("original module name '" + this.__name + "' expected, got '" + id + "'" );
-    }
+    },
+    findModule: function(name){return this.parent().findModule(name);}
 });
 
 var ModuleImport = ChainedContext.extend({
     init: function ModuleImport(context){
         ChainedContext.prototype.init.bind(this)(context);
-        this.__import = [];
+        this.__import = {};
+        this.__currentModule = undefined;
+        this.__currentAlias = undefined;
     },
     setIdent: function(id){
-        if (id != "JS")
-            this.__import.push(id);
-        else {
-            this.rtl().supportJS();
-            this.currentScope().addSymbol(new Symbol.Symbol("JS", new Module.JS()));
-        }
+        this.__currentModule = id;
     },
     handleLiteral: function(s){
         if (s == ":=")
-            this.__import.pop();
+            this.__currentAlias = this.__currentModule;
+        else if (s == ",")
+            this.__handleImport();
     },
     endParse: function(){
-        if (this.__import.length)
-            throw new Errors.Error("module(s) not found: " + this.__import.join(", "));
+        this.__handleImport();
+
+        var unresolved  = [];
+        for(var alias in this.__import){
+            var moduleName = this.__import[alias];
+            var module = this.parent().findModule(moduleName);
+            if (!module)
+                unresolved.push(moduleName);
+            else
+                this.currentScope().addSymbol(new Symbol.Symbol(alias, module));
+        }
+        if (unresolved.length)
+            throw new Errors.Error("module(s) not found: " + unresolved.join(", "));
+    },
+    __handleImport: function(){
+        var alias = this.__currentAlias;
+        if (!alias)
+            alias = this.__currentModule;
+        else
+            this.__currentAlias = undefined;
+        
+        for(var a in this.__import){
+            if (a == alias)
+                throw new Errors.Error("duplicated alias: '" + alias +"'");
+            if (this.__import[a] == this.__currentModule)
+                throw new Errors.Error("module already imported: '" + this.__currentModule +"'");
+        }
+        this.__import[alias] = this.__currentModule;
     }
 });
 exports.ModuleImport = ModuleImport;
 
 exports.Context = Class.extend({
-    init: function Context(){
-        this.__code = new Code.Generator();
-        this.__designator = undefined;
-        this.__type = undefined;
-        this.__scopes = [new Scope.Module()];
+    init: function Context(code, rtl, moduleResolver){
+        this.__code = code;
+        this.__scopes = [];
         this.__gen = 0;
         this.__vars = [];
-        this.__rtl = new RTL();
+        this.__rtl = rtl;
+        this.__moduleResolver = moduleResolver;
     },
-    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;
@@ -1696,6 +1720,7 @@ exports.Context = Class.extend({
         for(var i = this.__scopes.length; i--;){
             var scope = this.__scopes[i];
             var s = scope.findSymbol(ident);
+
             if (s)
                 return new Symbol.Found(s, scope);
         }
@@ -1706,11 +1731,13 @@ exports.Context = Class.extend({
     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;
+    rtl: function(){return this.__rtl;},
+    findModule: function(name){
+        if (name == "JS"){
+            this.rtl().supportJS();
+            return new Module.JS();
+        }
+        return this.__moduleResolver ? this.__moduleResolver(name) : undefined;
     }
 });

+ 6 - 6
src/lexer.js

@@ -5,7 +5,7 @@ var Errors = require("errors.js");
 function isDigit(c) {return c >= '0' && c <= '9';}
 
 exports.digit = function(stream, context){
-    var c = stream.char();
+    var c = stream.getChar();
     if (!isDigit(c))
         return false;
     context.handleChar(c);
@@ -13,7 +13,7 @@ exports.digit = function(stream, context){
 };
 
 exports.hexDigit = function(stream, context){
-    var c = stream.char();
+    var c = stream.getChar();
     if (!isDigit(c) && (c < 'A' || c > 'F'))
         return false;
     context.handleChar(c);
@@ -21,7 +21,7 @@ exports.hexDigit = function(stream, context){
 };
 
 exports.point = function(stream, context){
-    if (stream.char() != '.'
+    if (stream.getChar() != '.'
         || stream.peekChar() == '.') // not a diapason ".."
         return false;
     context.handleLiteral(".");
@@ -29,7 +29,7 @@ exports.point = function(stream, context){
 };
 
 exports.character = function(stream, context){
-    var c = stream.char();
+    var c = stream.getChar();
     if (c == '"')
         return false;
     context.handleChar(c);
@@ -37,7 +37,7 @@ exports.character = function(stream, context){
 };
 
 function string(stream, context){
-    var c = stream.char();
+    var c = stream.getChar();
     if (c != '"')
         return false;
 
@@ -107,7 +107,7 @@ function skipComment(stream){
 function readSpaces(c){return ' \t\n\r'.indexOf(c) != -1;}
 
 exports.skipSpaces = function(stream, context){
-    if (context.isLexem && context.isLexem())
+    if (context && context.isLexem && context.isLexem())
         return;
 
     do {

+ 1 - 1
src/module.js

@@ -21,7 +21,7 @@ var JSModule = Type.Module.extend({
 		Type.Module.prototype.init.call(this);
 	},
 	findSymbol: function(id){
-		return new Symbol.Found(new Symbol.Symbol("JS." + id, any));
+		return new Symbol.Found(new Symbol.Symbol(id, any));
 	}
 });
 

+ 58 - 23
src/oc.js

@@ -1,30 +1,65 @@
+"use strict";
+
 var Code = require("code.js");
-var Context = require("context.js")
+var Context = require("context.js");
 var Errors = require("errors.js");
 var Grammar = require("grammar.js");
 var Lexer = require("lexer.js");
+var ImportRTL = require("rtl.js");
 var Stream = require("stream.js").Stream;
 
-exports.compile = function(text, handleErrors){
-	var stream = new Stream(text);
-	var context = new Context.Context();
-	Lexer.skipSpaces(stream, context);	
-	try {
-		if (!Grammar.module(stream, context))
-			throw new Errors.Error("syntax error");
-	}
-	catch (x) {
-		if (x instanceof Errors.Error) {
-			//console.log(context.getResult());
-			if (handleErrors){
-				handleErrors(stream.describePosition() + ": " + x);
-				return undefined;
-			}
-		}
-		throw x;
-	}
-	Lexer.skipSpaces(stream, context);	
-	if (!stream.eof())
-		throw new Errors.Error("text beyond module end");
-	return context.getResult();
+var RTL = ImportRTL.RTL;
+var Class = ImportRTL.Class;
+
+var CompiledModule = Class.extend({
+    init: function CompiledModule(symbol, code, exports){
+        this.__symbol = symbol;
+        this.__code = code;
+        this.__exports = exports;
+    },
+    symbol: function(){return this.__symbol;},
+    code: function(){return this.__code;},
+    exports: function(){return this.__exports;}
+});
+
+function compileModule(stream, rtl, moduleResolver, handleErrors){
+    var code = new Code.Generator();
+    var context = new Context.Context(code, rtl, moduleResolver);
+    Lexer.skipSpaces(stream, context);  
+    try {
+        if (!Grammar.module(stream, context))
+            throw new Errors.Error("syntax error");
+    }
+    catch (x) {
+        if (x instanceof Errors.Error) {
+            //console.log(context.getResult());
+            if (handleErrors){
+                handleErrors(stream.describePosition() + ": " + x);
+                return undefined;
+            }
+        }
+        throw x;
+    }
+    var scope = context.currentScope();
+    return new CompiledModule(scope.module(), code.getResult(), scope.exports());
 }
+
+exports.compile = function(text, handleErrors){
+    var stream = new Stream(text);
+    var rtl = new RTL();
+    var code = "";
+    var modules = {};
+    function resolveModule(name){return modules[name];}
+
+    do {
+        var module = compileModule(stream, rtl, resolveModule, handleErrors);
+        if (!module)
+            return undefined;
+        var symbol = module.symbol();
+        modules[symbol.id()] = symbol.info();
+        code += module.code();
+        Lexer.skipSpaces(stream);
+    } 
+    while (!stream.eof());
+    return rtl.generate() + code;
+};

+ 25 - 7
src/scope.js

@@ -66,22 +66,40 @@ var ProcedureScope = Scope.extend({
     }
 });
 
-var Module = Scope.extend({
+var CompiledModule = Type.Module.extend({
     init: function Module(){
+        Type.Module.prototype.init.call(this);
+        this.__exports = undefined;
+    },
+    defineExports: function(exports){
+        this.__exports = exports;
+    },  
+    findSymbol: function(id){
+        return new Symbol.Found(this.__exports[id]);
+    }
+});
+
+var Module = Scope.extend({
+    init: function Module(name){
         Scope.prototype.init.call(this, "module");
-        this.__exports = [];
+        this.__name = name;
+        this.__exports = {};
+        this.__symbol = new Symbol.Symbol(name, new CompiledModule());
+        this.addSymbol(this.__symbol);
     },
+    module: function(){return this.__symbol;},
     addSymbol: function(symbol, exported){
-        if (exported)
-            this.__exports.push(symbol);
         Scope.prototype.addSymbol.call(this, symbol, exported);
+        if (exported)
+            this.__exports[symbol.id()] = symbol;
     },
     resolve: function(symbol){
-        var i = this.__exports.indexOf(symbol);
-        if (i != -1)
+        var id = symbol.id();
+        var exported = this.__exports[id];
+        if (exported)
             // remove non-record types from generated exports
             if (symbol.isType() && !(symbol.info().type() instanceof Type.Record))
-                this.__exports.splice(i, 1);
+                delete this.__exports[id];
         Scope.prototype.resolve.call(this, symbol);
     },
     exports: function(){return this.__exports;}

+ 3 - 1
src/stream.js

@@ -1,3 +1,5 @@
+"use strict";
+
 var assert = require("assert.js").ok;
 var Class = require("rtl.js").Class;
 
@@ -7,7 +9,7 @@ exports.Stream = Class.extend({
 		this.__pos = 0;
 	},
 	str: function(n){return this.__s.substr(this.__pos, n);},
-	char: function(){
+	getChar: function(){
 		if (this.eof())
 			return undefined;
 		return this.__s.charAt(this.__pos++);

+ 1 - 1
src/symbol.js

@@ -15,7 +15,7 @@ var Symbol = Class.extend({
 	isVariable: function(){return this.__info instanceof Type.Variable;},
 	isConst: function(){return this.__info instanceof Type.Const;},
 	isType: function(){return this.__info instanceof Type.TypeId;},
-	isProcedure: function(){return this.__info instanceof Type.Procedure;},
+	isProcedure: function(){return this.__info instanceof Type.Procedure;}
 });
 
 var FoundSymbol = Class.extend({

+ 120 - 120
src/type.js

@@ -4,136 +4,136 @@ var Class = require("rtl.js").Class;
 var Errors = require("errors.js");
 
 var Id = Class.extend({
-	init: function Id(){}
+    init: function Id(){}
 });
 
 var Type = Id.extend({
-	init: function Type(){
-		Id.prototype.init.call(this);
-	}
+    init: function Type(){
+        Id.prototype.init.call(this);
+    }
 });
 
 var TypeId = Id.extend({
-	init: function TypeId(type){
-		Id.prototype.init.call(this);
-		this._type = type;
-	},
-	type: function(){return this._type;},
-	description: function(){return 'type ' + this._type.description();}
+    init: function TypeId(type){
+        Id.prototype.init.call(this);
+        this._type = type;
+    },
+    type: function(){return this._type;},
+    description: function(){return 'type ' + this._type.description();}
 });
 
 var LazyTypeId = TypeId.extend({
-	init: function LazyTypeId(){
-		TypeId.prototype.init.call(this);
-	},
-	define: function(type){return this._type = type;}
+    init: function LazyTypeId(){
+        TypeId.prototype.init.call(this);
+    },
+    define: function(type){return this._type = type;}
 });
 
 exports.String = Type.extend({
-	init: function TypeString(s){
-		Type.prototype.init.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;},
-	length: function(){return this.__s.length;}
+    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;},
+    length: function(){return this.__s.length;}
 });
 
 var BasicType = Type.extend({
-	init: function BasicType(name, initValue){
-		Type.prototype.init.call(this);
-		this.__name = name;
-		this.__initValue = initValue;
-	},
-	idType: function(){return "type";},
-	name: function() {return this.__name;},
-	description: function(){return this.name();},
-	initializer: function() {return this.__initValue;}
+    init: function BasicType(name, initValue){
+        Type.prototype.init.call(this);
+        this.__name = name;
+        this.__initValue = initValue;
+    },
+    idType: function(){return "type";},
+    name: function() {return this.__name;},
+    description: function(){return this.name();},
+    initializer: function() {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;},
-	length: function(){return this.__size;}
+    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;},
+    length: 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;
-	}
+    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 ForwardRecord(resolve){
-		Type.prototype.init.bind(this)();
-		this.__resolve = resolve;
-	},
-	resolve: function(){return this.__resolve();}
+    init: function ForwardRecord(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;
-	},
-	addField: function(field, type){
-		var name = field.id();
-		if (this.__fields.hasOwnProperty(name))
-			throw new Errors.Error("duplicated field: '" + name + "'");
-		if (this.__base && this.__base.findSymbol(name))
-			throw new Errors.Error("base record already has field: '" + name + "'");
-		this.__fields[name] = type;
-	},
-	ownFields: function() {return this.__fields;},
-	findSymbol: function(field){
-		var result = this.__fields[field];
-		if ( !result && this.__base)
-			result = this.__base.findSymbol(field);
-		return result;
-	},
-	baseType: function() {return this.__base;},
-	setBaseType: function(type) {this.__base = type;},
-	description: function(){
-		var name = this.name();
-		if (name.indexOf("$") != -1)
-			return "anonymous RECORD";
-		return name;
-	}
+    init: function RecordType(name){
+        BasicType.prototype.init.bind(this)(name, "new " + name + "()");
+        this.__fields = {};
+        this.__base = undefined;
+    },
+    addField: function(field, type){
+        var name = field.id();
+        if (this.__fields.hasOwnProperty(name))
+            throw new Errors.Error("duplicated field: '" + name + "'");
+        if (this.__base && this.__base.findSymbol(name))
+            throw new Errors.Error("base record already has field: '" + name + "'");
+        this.__fields[name] = type;
+    },
+    ownFields: function() {return this.__fields;},
+    findSymbol: function(field){
+        var result = this.__fields[field];
+        if ( !result && this.__base)
+            result = this.__base.findSymbol(field);
+        return result;
+    },
+    baseType: function() {return this.__base;},
+    setBaseType: function(type) {this.__base = type;},
+    description: function(){
+        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";}
+    init: function NilType(){Type.prototype.init.bind(this)();},
+    idType: function(){return "NIL";},
+    description: function(){return "NIL";}
 });
 
 var basic = {
-	bool: new BasicType("BOOLEAN", false),
-	ch: new BasicType("CHAR", 0),
-	integer: new BasicType("INTEGER", 0),
-	real: new BasicType("REAL", 0),
-	set: new BasicType("SET", 0)
+    bool: new BasicType("BOOLEAN", false),
+    ch: new BasicType("CHAR", 0),
+    integer: new BasicType("INTEGER", 0),
+    real: new BasicType("REAL", 0),
+    set: new BasicType("SET", 0)
 };
 exports.basic = basic;
 exports.numeric = [basic.integer, basic.real];
@@ -141,40 +141,40 @@ exports.numeric = [basic.integer, basic.real];
 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;}
+    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;}
+    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 = BasicType.extend({
-	init: function Procedure(name){
-		BasicType.prototype.init.call(this, name, "null");
-	},
-	idType: function(){return "procedure";}
+    init: function Procedure(name){
+        BasicType.prototype.init.call(this, name, "null");
+    },
+    idType: function(){return "procedure";}
 });
 
 var Module = Id.extend({
-	init: function Module(){
-		Id.prototype.init.call(this);
-	}
+    init: function Module(){
+        Id.prototype.init.call(this);
+    }
 });
 
 exports.Module = Module;

+ 11 - 0
test/expected/modules.js

@@ -0,0 +1,11 @@
+var m1 = function (){
+
+function p(){
+}
+return {
+	p: p
+}
+}();
+var m2 = function (){
+m1.p();
+}();

+ 12 - 0
test/input/modules.ob

@@ -0,0 +1,12 @@
+MODULE m1;
+
+PROCEDURE p*();
+END p;
+
+END m1.
+
+MODULE m2;
+IMPORT m1;
+BEGIN
+	m1.p();
+END m2.

+ 33 - 16
test/test_unit.js

@@ -1,15 +1,19 @@
 "use strict";
 
 var assert = require("assert.js").ok;
+var Code = require("code.js");
 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 ImportRTL = require("rtl.js");
+var Scope = require("scope.js");
 var Stream = require("stream.js").Stream;
 var Test = require("test.js");
 
 var TestError = Test.TestError;
+var RTL = ImportRTL.RTL;
+var Class = ImportRTL.Class;
 
 function parseInContext(grammar, s, context){
     var stream = new Stream(s);
@@ -17,8 +21,14 @@ function parseInContext(grammar, s, context){
         throw new Errors.Error("not parsed");
 }
 
+function makeContext(){
+    var result = new Context.Context(Code.nullGenerator, new RTL());
+    result.pushScope(new Scope.Module("test"));
+    return result;
+}
+
 function parseUsingGrammar(grammar, s, cxFactory, handlerError){
-    var baseContext = new Context.Context();
+    var baseContext = makeContext();
     var context = cxFactory ? cxFactory(baseContext) : baseContext;
     try {
         parseInContext(grammar, s, context);
@@ -63,8 +73,8 @@ function setup(parser, contextFactory){
 }
 
 function setupWithContext(grammar, source){
-    function makeContext(){
-        var context = new Context.Context();
+    function innerMakeContext(){
+        var context = makeContext();
         try {
             parseInContext(Grammar.declarationSequence, source, context);
         }
@@ -76,7 +86,7 @@ function setupWithContext(grammar, source){
         return context;
     }
 
-    return setup(grammar, makeContext);
+    return setup(grammar, innerMakeContext);
 }
 
 function context(grammar, source){
@@ -863,8 +873,8 @@ identifier: function(){
                    , "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);
+    function innerMakeContext(cx){return new Context.ProcDecl(makeContext());}
+    var test = setup(Grammar.procedureHeading, innerMakeContext);
 
     test.parse("PROCEDURE p");
     test.parse("PROCEDURE p(a1: INTEGER)");
@@ -1162,14 +1172,21 @@ assert: function(){
           "cannot export from within procedure: variable 'i'"]
          )
     ),
-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.");
-    test.expectError("MODULE m; IMPORT unknown; END m.", "module(s) not found: unknown");
-    test.expectError("MODULE m; IMPORT unknown1, unknown2; END m.", "module(s) not found: unknown1, unknown2");
-    test.expectError("MODULE m; IMPORT u1 := unknown1, unknown2; END m.", "module(s) not found: unknown1, unknown2");
-}};
+"IMPORT": testWithGrammar(
+    Grammar.module,
+    pass("MODULE m; IMPORT JS; END m.",
+         "MODULE m; IMPORT JS; BEGIN JS.alert(\"test\") END m.",
+         "MODULE m; IMPORT JS; BEGIN JS.console.info(123) END m.",
+         "MODULE m; IMPORT J := JS; END m.",
+         "MODULE m; IMPORT J := JS; BEGIN J.alert(\"test\") END m."),
+    fail(["MODULE m; IMPORT unknown; END m.", "module(s) not found: unknown"],
+         ["MODULE m; IMPORT unknown1, unknown2; END m.", "module(s) not found: unknown1, unknown2"],
+         ["MODULE m; IMPORT u1 := unknown1, unknown2; END m.", "module(s) not found: unknown1, unknown2"],
+         ["MODULE m; IMPORT a1 := m1, a2 := m1; END m.", "module already imported: 'm1'"],
+         ["MODULE m; IMPORT a1 := u1, a1 := u2; END m.", "duplicated alias: 'a1'"],
+         ["MODULE m; IMPORT J := JS; BEGIN JS.alert(\"test\") END m.", "undeclared identifier: 'JS'"]
+         )
+    )
+};
 
 Test.run(testSuite);