2
0
Vladislav Folts 12 жил өмнө
parent
commit
0827b89150

+ 119 - 110
src/context.js

@@ -9,23 +9,15 @@ var op = require("operator.js");
 var Parser = require("parser.js");
 var Procedure = require("procedure.js");
 var ImportRTL = require("rtl.js");
+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;
-var Symbol = Type.Symbol;
-
-var FoundSymbol = Class.extend({
-    init: function(symbol, scope){
-        this.__symbol = symbol;
-        this.__scope = scope;
-    },
-    symbol: function(){return this.__symbol;},
-    scope: function(){return this.__scope;}
-});
 
 function getSymbolAndScope(context, id){
     var s = context.findSymbol(id);
@@ -104,9 +96,8 @@ var ChainedContext = Class.extend({
     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(id){this.__parent.pushScope(id);},
+    pushScope: function(scope){this.__parent.pushScope(scope);},
     popScope: function(){this.__parent.popScope();},
     setType: function(type){this.__parent.setType(type);},
     setDesignator: function(d){this.__parent.setDesignator(d);},
@@ -127,7 +118,7 @@ exports.Integer = ChainedContext.extend({
     isLexem: function(){return true;},
     handleChar: function(c){this.__result += c;},
     handleLiteral: function(){this.__isHex = true;},
-    toInt: function(s){return parseInt(this.__result);},
+    toInt: function(s){return parseInt(this.__result, 10);},
     endParse: function(){
         var n = this.toInt();
         this.parent().handleConst(basicTypes.int, n, n.toString());
@@ -238,6 +229,28 @@ exports.QualifiedIdentificator = ChainedContext.extend({
     }
 });
 
+var Identdef = Class.extend({
+    init: function Identdef(id, exported){
+        this.__id = id;
+        this.__exported = exported;
+    },
+    id: function(){return this.__id;},
+    exported: function(){return this.__exported;}
+});
+
+exports.Identdef = ChainedContext.extend({
+    init: function IdentdefContext(context){
+        ChainedContext.prototype.init.bind(this)(context);
+        this.__id = undefined;
+        this.__export = false;
+    },
+    setIdent: function(id){this.__id = id;},
+    handleLiteral: function(){this.__export = true;},
+    endParse: function(){
+        this.parent().handleIdentef(new Identdef(this.__id, this.__export));
+    }
+});
+
 exports.Designator = ChainedContext.extend({
     init: function DesignatorContext(context){
         ChainedContext.prototype.init.bind(this)(context);
@@ -267,7 +280,7 @@ exports.Designator = ChainedContext.extend({
             this.__denote(id);
         }
         else if (t instanceof Type.Record
-              || t instanceof Module.Type
+              || t instanceof Type.Module
               || t instanceof Module.AnyType)
             this.__denote(id);
         else
@@ -431,39 +444,36 @@ exports.FormalParametersProcDecl = exports.FormalParameters.extend({
 exports.ProcDecl = ChainedContext.extend({
     init: function ProcDeclContext(context){
         ChainedContext.prototype.init.bind(this)(context);
-        this.__name = undefined;
+        this.__id = undefined;
         this.__firstArgument = true;
         this.__type = undefined;
         this.__returnParsed = false;
         this.__outerScope = this.parent().currentScope();
     },
+    handleIdentef: function(id){
+        this.__id = id;
+        this.codeGenerator().write("\nfunction " + id.id() + "(");
+        this.parent().pushScope(new Scope.Procedure());
+    },
     setIdent: function(id){
-        var gen = this.codeGenerator();
-        if (this.__name === undefined){ // first call
-            this.__name = id;
-            gen.write("\nfunction " + id + "(");
-            this.parent().pushScope("procedure");
-        }
-        else if (this.__name === id){
-            gen.closeScope();
-            this.parent().popScope();
-        }
-        else
-            throw new Errors.Error("mismatched procedure names: '" + this.__name
+        if (this.__id.id() != id)
+            throw new Errors.Error("mismatched procedure names: '" + this.__id.id()
                                  + "' at the begining and '" + id + "' at the end");
+        this.codeGenerator().closeScope();
+        this.parent().popScope();
     },
     typeName: function(){return undefined;},
     setType: function(type){
-        var procSymbol = new Symbol(this.__name, type);
-        this.__outerScope.addSymbol(procSymbol);
+        var procSymbol = new Symbol.Symbol(this.__id.id(), type);
+        this.__outerScope.addSymbol(procSymbol, this.__id.exported());
         this.__type = type;
     },
     addArgument: function(name, arg){
-        if (name == this.__name)
+        if (name == this.__id.id())
             throw new Errors.Error("argument '" + name + "' has the same name as procedure");
         var readOnly = !arg.isVar && (arg.type instanceof Type.Array);
-        var s = new Symbol(name, new Type.Variable(arg.type, arg.isVar, readOnly));
-        this.parent().addSymbol(s);
+        var s = new Symbol.Symbol(name, new Type.Variable(arg.type, arg.isVar, readOnly));
+        this.currentScope().addSymbol(s);
 
         var code = this.codeGenerator();
         if (!this.__firstArgument)
@@ -542,7 +552,7 @@ exports.PointerDecl = ChainedContext.extend({
         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(
@@ -560,14 +570,15 @@ exports.PointerDecl = ChainedContext.extend({
         var resolve = function(){return getSymbol(parent, id).info().type();};
 
         var type = new Type.ForwardRecord(resolve);
-        return new FoundSymbol(
-            new Symbol(id, new Type.TypeId(type)),
+        return new Symbol.Found(
+            new Symbol.Symbol(id, new Type.TypeId(type)),
             scope
             );
     },
     genTypeName: function(){
         return this.__name + "$base";
     },
+    exportField: function(field){this.parent().exportField(field);},
     endParse: function(){
         var type = new Type.Pointer(this.__name, this.__base);
         this.parent().setType(type);
@@ -1323,9 +1334,9 @@ exports.ConstDecl = ChainedContext.extend({
         this.__type = undefined;
         this.__value = undefined;
     },
-    setIdent: function(id){
+    handleIdentef: function(id){
         this.__id = id;
-        this.codeGenerator().write("var " + id + " = ");
+        this.codeGenerator().write("var " + id.id() + " = ");
     },
     handleExpression: function(e){
         var value = e.constValue();
@@ -1336,28 +1347,45 @@ exports.ConstDecl = ChainedContext.extend({
     },
     endParse: function(){
         var c = new Type.Const(this.__type, this.__value);
-        this.addSymbol(new Symbol(this.__id, c));
+        this.currentScope().addSymbol(new Symbol.Symbol(this.__id.id(), c), this.__id.exported());
         this.codeGenerator().write(";\n");
     }
 });
 
+function checkIfFFieldCanBeExported(name, idents, hint){
+    for(var i = 0; i < idents.length; ++i){
+        var id = idents[i];
+        if (!id.exported())
+            throw new Errors.Error(
+                "field '" + name + "' can be exported only if " + hint + " '" +
+                id.id() + "' itself is exported too");
+    }
+}
+
 exports.VariableDeclaration = 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;},
+    handleIdentef: function(id){this.__idents.push(id);},
+    exportField: function(name){
+        checkIfFFieldCanBeExported(name, this.__idents, "variable");
+    },
+    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));
+        for(var i = 0; i < idents.length; ++i){
+            var id = idents[i];
+            if (id.exported()
+                && (this.__type instanceof Type.Record
+                    || this.__type instanceof Type.Array))
+                throw new Errors.Error("only scalar type variables can be exported");
+            var varName = id.id();
+            this.currentScope().addSymbol(new Symbol.Symbol(varName, v), id.exported());
             var t = v.type();
             gen.write("var " + varName + " = " + t.initializer() + ";");
         }
@@ -1373,15 +1401,16 @@ exports.FieldListDeclaration = ChainedContext.extend({
         this.__type = undefined;
     },
     typeName: function(){return undefined;},
-    setIdent: function(id) {this.__idents.push(id);},
+    handleIdentef: function(id) {this.__idents.push(id);},
+    exportField: function(name){
+        checkIfFFieldCanBeExported(name, this.__idents, "field");
+    },
     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);
-        }
+        var parent = this.parent();
+        for(var i = 0; i < idents.length; ++i)
+            parent.addField(idents[i], this.__type);
     }
 });
 
@@ -1394,7 +1423,7 @@ exports.ActualParameters = ChainedContext.extend({
     init: function ActualParametersContext(context){
         ChainedContext.prototype.init.bind(this)(context);
         this.parent().hasActualParameters();
-    },
+    }
 });
 
 var ProcedureCall = ChainedContext.extend({
@@ -1474,7 +1503,11 @@ exports.RecordDecl = ChainedContext.extend({
         var gen = this.codeGenerator();
         gen.write("var " + id + " = ");
     },
-    addField: function(name, type) {this.__type.addField(name, type);},
+    addField: function(field, type){
+        this.__type.addField(field, type);
+        if (field.exported())
+            this.parent().exportField(field.id());
+    },
     setBaseType: function(type){this.__type.setBaseType(type);},
     endParse: function(){
         var type = this.__type;
@@ -1501,13 +1534,18 @@ exports.TypeDeclaration = ChainedContext.extend({
         ChainedContext.prototype.init.bind(this)(context);
         this.__id = undefined;
     },
-    setIdent: function(id){this.__ident = id;},
+    handleIdentef: function(id){this.__id = id;},
     setType: function(type){
-        this.addSymbol(new Symbol(this.__ident, new Type.TypeId(type)));
+        this.currentScope().addSymbol(
+            new Symbol.Symbol(this.__id.id(), new Type.TypeId(type)),
+            this.__id.exported());
     },
-    typeName: function(){return this.__ident;},
-    genTypeName: function(){return this.__ident;},
-    type: function(){return this.parent().type();}
+    typeName: function(){return this.__id.id();},
+    genTypeName: function(){return this.__id.id();},
+    type: function(){return this.parent().type();},
+    exportField: function(name){
+        checkIfFFieldCanBeExported(name, [this.__id], "record");
+    }
 });
 
 exports.TypeSection = ChainedContext.extend({
@@ -1540,6 +1578,20 @@ 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();
+        if (e.isVariable() && !(e.info().type() instanceof Type.Pointer))
+            access = "function(){return " + access + ";}";
+        gen.write("\t" + e.id() + ": " + access + "\n");
+    }
+    gen.write("}\n");
+}
+
 exports.ModuleDeclaration = ChainedContext.extend({
     init: function ModuleDeclarationContext(context){
         ChainedContext.prototype.init.bind(this)(context);
@@ -1549,11 +1601,14 @@ exports.ModuleDeclaration = ChainedContext.extend({
         var gen = this.codeGenerator();
         if (this.__name === undefined ) {
             this.__name = id;
-            this.addSymbol(new Symbol(id, Type.module));
+            this.currentScope().addSymbol(new Symbol.Symbol(id, Type.module));
             gen.write("var " + id + " = function " + "(){\n");
         }
-        else if (id === this.__name)
+        else if (id === this.__name){
+            var exports = this.parent().currentScope().exports();
+            genExports(exports, gen);
             gen.write("}();");
+        }
         else
             throw new Errors.Error("original module name '" + this.__name + "' expected, got '" + id + "'" );
     }
@@ -1569,7 +1624,7 @@ var ModuleImport = ChainedContext.extend({
             this.__import.push(id);
         else {
             this.rtl().supportJS();
-            this.addSymbol(new Symbol("JS", new Module.JS()));
+            this.currentScope().addSymbol(new Symbol.Symbol("JS", new Module.JS()));
         }
     },
     handleLiteral: function(s){
@@ -1583,57 +1638,12 @@ var ModuleImport = ChainedContext.extend({
 });
 exports.ModuleImport = ModuleImport;
 
-var stdSymbols = function(){
-    var symbols = {};
-    for(var t in basicTypes){
-        var type = basicTypes[t];
-        symbols[type.name()] = new Symbol(type.name(), new Type.TypeId(type));
-    }
-    symbols["LONGREAL"] = new Symbol("LONGREAL", new Type.TypeId(basicTypes.real));
-    
-    var predefined = Procedure.predefined;
-    for(var i = 0; i < predefined.length; ++i){
-        var s = predefined[i];
-        symbols[s.id()] = s;
-    }
-    return symbols;
-}();
-
-var Scope = Class.extend({
-    init: function Scope(id){
-        this.__id = id;
-        this.__symbols = {};
-        for(var p in stdSymbols)
-            this.__symbols[p] = stdSymbols[p];
-        this.__unresolved = [];
-    },
-    id: function(){return this.__id;},
-    addSymbol: function(symbol){
-        var id = symbol.id();
-        if (this.findSymbol(id))
-            throw new Errors.Error( "'" + id + "' already declared");
-        this.__symbols[id] = symbol;
-
-        var i = this.__unresolved.indexOf(id);
-        if (i != -1){
-            var info = symbol.info();
-            if (!(info.type() instanceof Type.Record))
-                throw new Errors.Error(
-                    "'" + id + "' must be of RECORD type because it was used before in the declation of POINTER");
-            this.__unresolved.splice(i, 1);
-        }
-    },
-    findSymbol: function(ident){return this.__symbols[ident];},
-    addUnresolved: function(id){this.__unresolved.push(id);},
-    unresolved: function(){return this.__unresolved;}
-});
-
 exports.Context = Class.extend({
     init: function Context(){
         this.__code = new Code.Generator();
         this.__designator = undefined;
         this.__type = undefined;
-        this.__scopes = [new Scope()];
+        this.__scopes = [new Scope.Module()];
         this.__gen = 0;
         this.__vars = [];
         this.__rtl = new RTL();
@@ -1651,18 +1661,17 @@ exports.Context = Class.extend({
             this.__vars.push(id);
         }
     },
-    addSymbol: function(symbol){this.currentScope().addSymbol(symbol);},
     findSymbol: function(ident){
         for(var i = this.__scopes.length; i--;){
             var scope = this.__scopes[i];
             var s = scope.findSymbol(ident);
             if (s)
-                return new FoundSymbol(s, scope);
+                return new Symbol.Found(s, scope);
         }
         return undefined;
     },
     currentScope: function(){return this.__scopes[this.__scopes.length - 1];},
-    pushScope: function(id){this.__scopes.push(new Scope(id));},
+    pushScope: function(scope){this.__scopes.push(scope);},
     popScope: function(){this.__scopes.pop();},
     handleExpression: function(){},
     handleLiteral: function(){},
@@ -1672,5 +1681,5 @@ exports.Context = Class.extend({
     codeGenerator: function(){return this.__code;},
     rtl: function(){
         return this.__rtl;
-    },
+    }
 });

+ 84 - 83
src/grammar.js

@@ -23,173 +23,174 @@ var emit = Parser.emit;
 var required = Parser.required;
 
 var qualident = context(and(optional(and(ident, ".")), ident),
-						Context.QualifiedIdentificator);
-var identdef = and(ident, optional("*"));
+                        Context.QualifiedIdentificator);
+var identdef = context(and(ident, optional("*")),
+                       Context.Identdef);
 var selector = or(and(point, ident)
-				// break recursive declaration of expList
-			    , and("[", function(stream, context){return expList(stream, context);}, "]")
-				, "^"
-				, context(and("(", qualident, ")"), Context.TypeCast)
-			    );
+                // break recursive declaration of expList
+                , and("[", function(stream, context){return expList(stream, context);}, "]")
+                , "^"
+                , context(and("(", qualident, ")"), Context.TypeCast)
+                );
 var designator = context(and(qualident, repeat(selector)), Context.Designator);
 var type = or(context(qualident, Context.Type),
-			  function(stream, context){return strucType(stream, context);} // break recursive declaration of strucType
-			 );
-var identList = and(identdef, repeat(and(",", ident)));
+              function(stream, context){return strucType(stream, context);} // break recursive declaration of strucType
+             );
+var identList = and(identdef, repeat(and(",", identdef)));
 var variableDeclaration = context(and(identList, ":", type), Context.VariableDeclaration);
 
 var integer = or(context(and(digit, repeat(hexDigit), "H", separator), Context.HexInteger)
-			   , context(and(digit, repeat(digit), separator), Context.Integer));
+               , 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);
+                 , Context.Real);
 
 var number = or(real, integer);
 
 var string = or(context(Lexer.string, Context.String)
-			  , context(and(digit, repeat(hexDigit), "X"), Context.Char));
+              , context(and(digit, repeat(hexDigit), "X"), Context.Char));
 
 var factor = context(
-	or(string, number, "NIL", "TRUE", "FALSE"
-	 , function(stream, context){return set(stream, context);} // break recursive declaration of set
-	 , context(and(designator
-				 // break recursive declaration of actualParameters
-				 , optional(function(stream, context){return actualParameters(stream, context);})
-				  )
-			 , Context.ExpressionProcedureCall)
-	 , and("(", function(stream, context){return expression(stream, context);}
+    or(string, number, "NIL", "TRUE", "FALSE"
+     , function(stream, context){return set(stream, context);} // break recursive declaration of set
+     , context(and(designator
+                 // break recursive declaration of actualParameters
+                 , optional(function(stream, context){return actualParameters(stream, context);})
+                  )
+             , Context.ExpressionProcedureCall)
+     , and("(", function(stream, context){return expression(stream, context);}
          , required(")", "no matched ')'"))
      , and("~", function(stream, context){
-					return factor(stream, context);}) // break recursive declaration of factor
-	 )
-	, Context.Factor);
+                    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);
+        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);
+                       , Context.Expression);
 var constExpression = expression;
 
 var element = context(and(expression, optional(and("..", expression))), Context.SetElement);
 var set = and("{", context(optional(and(element, repeat(and(",", element)))), Context.Set)
-			, "}");
+            , "}");
 
 var expList = and(expression, repeat(and(",", expression)));
 var actualParameters = and("(", context(optional(expList), Context.ActualParameters), ")");
 var procedureCall = context(and(designator, optional(actualParameters))
-						  , Context.StatementProcedureCall);
+                          , Context.StatementProcedureCall);
 
 var assignment = context(and(designator, ":=", required(expression, "expression expected"))
-					   , Context.Assignment);
+                       , Context.Assignment);
 
 var statement = optional(or(
                    emit(assignment, Context.emitEndStatement)
-				 , emit(procedureCall, Context.emitEndStatement)
-				   // break recursive declaration of ifStatement/caseStatement/whileStatement/repeatStatement
-				 , function(stream, context){return ifStatement(stream, context);}
-				 , function(stream, context){return caseStatement(stream, context);}
-				 , function(stream, context){return whileStatement(stream, context);}
-				 , function(stream, context){return repeatStatement(stream, context);}
-				 , function(stream, context){return forStatement(stream, context);}
-				 ));
+                 , 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));
+                    , 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));
+                      , "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)
-					   );
+                       , 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));
+                        , "UNTIL", context(expression, Context.Until));
 
 var forStatement = context(and("FOR", ident, ":=", expression, "TO", expression
-							 , optional(and("BY", constExpression))
-							 , emit("DO", Context.emitForBegin)
+                             , optional(and("BY", constExpression))
+                             , emit("DO", Context.emitForBegin)
                              , statementSequence, required("END", "END expected (FOR)"))
-						 , Context.For);
+                         , Context.For);
 
 var fieldList = context(and(identList, ":", type), Context.FieldListDeclaration);
 var fieldListSequence = and(fieldList, repeat(and(";", fieldList)));
 
 var arrayType = and("ARRAY", context(and(
-						context(and(constExpression, repeat(and(",", constExpression)))
-							  , Context.ArrayDimensions)
-				  , "OF", type), Context.ArrayDecl));
+                        context(and(constExpression, repeat(and(",", constExpression)))
+                              , Context.ArrayDimensions)
+                  , "OF", type), Context.ArrayDecl));
 
 var baseType = context(qualident, Context.BaseType);
 var recordType = and("RECORD", context(and(optional(and("(", baseType, ")")), optional(fieldListSequence)
-									 , "END"), Context.RecordDecl));
+                                     , "END"), Context.RecordDecl));
 
 var pointerType = and("POINTER", "TO", context(type, Context.PointerDecl));
 
 var formalType = context(and(repeat(and("ARRAY", "OF")), qualident), Context.FormalType);
 var fpSection = and(optional(literal("VAR")), ident, repeat(and(",", ident)), ":", formalType);
 var formalParameters = and(
-		  "("
-		, optional(context(and(fpSection, repeat(and(";", fpSection))), Context.ProcParams))
-		, ")"
-		, optional(and(":", qualident)));
+          "("
+        , optional(context(and(fpSection, repeat(and(";", fpSection))), Context.ProcParams))
+        , ")"
+        , optional(and(":", qualident)));
 
 var procedureType = and("PROCEDURE"
-					  , context(optional(formalParameters), Context.FormalParameters)
-					    );
+                      , context(optional(formalParameters), Context.FormalParameters)
+                        );
 var strucType = or(arrayType, recordType, pointerType, procedureType);
 var typeDeclaration = context(and(identdef, "=", strucType), Context.TypeDeclaration);
 
 var procedureHeading = and("PROCEDURE"
-						 , identdef
-						 , context(optional(formalParameters), Context.FormalParametersProcDecl));
+                         , identdef
+                         , context(optional(formalParameters), Context.FormalParametersProcDecl));
 var procedureDeclaration = context(
-	  and(procedureHeading, ";"
-	      // break recursive declaration of procedureBody
-	    , function(stream, context){return procedureBody(stream, context);}
-	    , ident)
-	, Context.ProcDecl);
+      and(procedureHeading, ";"
+          // break recursive declaration of procedureBody
+        , function(stream, context){return procedureBody(stream, context);}
+        , ident)
+    , Context.ProcDecl);
 
 var constantDeclaration = context(and(identdef, "=", constExpression), Context.ConstDecl);
 
 var declarationSequence = and(optional(and("CONST", repeat(and(constantDeclaration, ";"))))
-							, optional(and("TYPE", context(repeat(and(typeDeclaration, ";")), Context.TypeSection)))
-							, optional(and("VAR", repeat(and(variableDeclaration, ";"))))
-							, repeat(and(procedureDeclaration, ";")));
+                            , optional(and("TYPE", context(repeat(and(typeDeclaration, ";")), Context.TypeSection)))
+                            , optional(and("VAR", repeat(and(variableDeclaration, ";"))))
+                            , repeat(and(procedureDeclaration, ";")));
 var procedureBody = and(declarationSequence
-					  , optional(and("BEGIN", statementSequence))
-					  , optional(context(and("RETURN", expression), Context.Return))
-					  , required("END", "END expected (PROCEDURE)"));
+                      , optional(and("BEGIN", statementSequence))
+                      , optional(context(and("RETURN", expression), Context.Return))
+                      , required("END", "END expected (PROCEDURE)"));
 
 var imprt = and(ident, optional(and(":=", ident)));
 var importList = context(and("IMPORT", imprt, repeat(and(",", imprt))),
-						 Context.ModuleImport);
-var module = context(and("MODULE", ident, ";",
-						 optional(and(importList, ";")),
-						 declarationSequence,
-						 optional(and("BEGIN", statementSequence)),
-						 required("END", "END expected (MODULE)"), ident, point),
-					 Context.ModuleDeclaration);
+                         Context.ModuleImport);
+var modul = context(and("MODULE", ident, ";",
+                         optional(and(importList, ";")),
+                         declarationSequence,
+                         optional(and("BEGIN", statementSequence)),
+                         required("END", "END expected (MODULE)"), ident, point),
+                     Context.ModuleDeclaration);
 
 exports.declarationSequence = declarationSequence;
 exports.expression = expression;
 exports.ident = ident;
-exports.module = module;
+exports.module = modul;
 exports.procedureBody = procedureBody;
 exports.procedureDeclaration = procedureDeclaration;
 exports.procedureHeading = procedureHeading;

+ 6 - 10
src/module.js

@@ -1,4 +1,7 @@
+"use strict";
+
 var Procedure = require("procedure.js");
+var Symbol = require("symbol.js");
 var Type = require("type.js");
 
 var AnyType = Type.Basic.extend({
@@ -13,21 +16,14 @@ var AnyType = Type.Basic.extend({
 
 var any = new AnyType();
 
-var Module = Type.Basic.extend({
-	init: function(){
-		Type.Basic.prototype.init.call(this, "MODULE");
-	}
-});
-
-var JSModule = Module.extend({
+var JSModule = Type.Module.extend({
 	init: function(){
-		Module.prototype.init.call(this);
+		Type.Module.prototype.init.call(this);
 	},
 	findSymbol: function(id){
-		return any;
+		return new Symbol.Found(new Symbol.Symbol("JS." + id, any));
 	}
 });
 
 exports.AnyType = AnyType;
 exports.JS = JSModule;
-exports.Type = Module;

+ 17 - 16
src/procedure.js

@@ -6,6 +6,7 @@ var Code = require("code.js");
 var Errors = require("errors.js");
 var op = require("operator.js");
 var precedence = require("operator.js").precedence;
+var Symbol = require("symbol.js");
 var Type = require("type.js");
 
 var Arg = Class.extend({
@@ -202,7 +203,7 @@ function setBitImpl(name, op){
             return new TwoArgToOperatorProcCallGenerator(
                 context, id, type, operator);
             });
-    var symbol = new Type.Symbol(name, proc);
+    var symbol = new Symbol.Symbol(name, proc);
     return symbol;
 }
 
@@ -235,7 +236,7 @@ function incImpl(name, unary, op){
             return new CallGenerator(
                 context, id, type, operator);
             });
-    var symbol = new Type.Symbol(name, proc);
+    var symbol = new Symbol.Symbol(name, proc);
     return symbol;
 }
 
@@ -259,7 +260,7 @@ function bitShiftImpl(name, op){
         function(context, id, type){
             return new CallGenerator(context, id, type);
         });
-    var symbol = new Type.Symbol(name, proc);
+    var symbol = new Symbol.Symbol(name, proc);
     return symbol;
 }
 
@@ -278,7 +279,7 @@ function longShort(name){
         function(context, id, type){
             return new CallGenerator(context, id, type);
         });
-    var symbol = new Type.Symbol(name, proc);
+    var symbol = new Symbol.Symbol(name, proc);
     return symbol;
 }
 
@@ -319,7 +320,7 @@ exports.predefined = [
             function(context, id, type){
                 return new NewProcCallGenerator(context, id, type);
             });
-        var symbol = new Type.Symbol(name, type);
+        var symbol = new Symbol.Symbol(name, type);
         return symbol;
     }(),
     function(){
@@ -348,7 +349,7 @@ exports.predefined = [
             function(context, id, type){
                 return new LenProcCallGenerator(context, id, type);
             });
-        var symbol = new Type.Symbol(name, type);
+        var symbol = new Symbol.Symbol(name, type);
         return symbol;
     }(),
     function(){
@@ -371,7 +372,7 @@ exports.predefined = [
             function(context, id, type){
                 return new CallGenerator(context, id, type);
             });
-        var symbol = new Type.Symbol(name, type);
+        var symbol = new Symbol.Symbol(name, type);
         return symbol;
     }(),
     function(){
@@ -393,7 +394,7 @@ exports.predefined = [
             function(context, id, type){
                 return new AssertProcCallGenerator(context, id, type);
             });
-        var symbol = new Type.Symbol("ASSERT", proc);
+        var symbol = new Symbol.Symbol("ASSERT", proc);
         return symbol;
     }(),
     setBitImpl("INCL", function(x, y){return x + " |= " + y;}),
@@ -425,7 +426,7 @@ exports.predefined = [
             function(context, id, type){
                 return new CallGenerator(context, id, type);
             });
-        var symbol = new Type.Symbol("ABS", proc);
+        var symbol = new Symbol.Symbol("ABS", proc);
         return symbol;
     }(),
     function(){
@@ -443,7 +444,7 @@ exports.predefined = [
             function(context, id, type){
                 return new CallGenerator(context, id, type);
             });
-        var symbol = new Type.Symbol("FLOOR", proc);
+        var symbol = new Symbol.Symbol("FLOOR", proc);
         return symbol;
     }(),
     function(){
@@ -464,7 +465,7 @@ exports.predefined = [
             function(context, id, type){
                 return new CallGenerator(context, id, type);
             });
-        var symbol = new Type.Symbol("FLT", proc);
+        var symbol = new Symbol.Symbol("FLT", proc);
         return symbol;
     }(),
     longShort("LONG"),
@@ -515,7 +516,7 @@ exports.predefined = [
             function(context, id, type){
                 return new CallGenerator(context, id, type);
             });
-        var symbol = new Type.Symbol(name, type);
+        var symbol = new Symbol.Symbol(name, type);
         return symbol;
     }(),
     function(){
@@ -535,7 +536,7 @@ exports.predefined = [
             function(context, id, type){
                 return new CallGenerator(context, id, type);
             });
-        var symbol = new Type.Symbol(name, type);
+        var symbol = new Symbol.Symbol(name, type);
         return symbol;
     }(),
     function(){
@@ -557,7 +558,7 @@ exports.predefined = [
             function(context, id, type){
                 return new CallGenerator(context, id, type);
             });
-        var symbol = new Type.Symbol(name, type);
+        var symbol = new Symbol.Symbol(name, type);
         return symbol;
     }(),
     function(){
@@ -575,7 +576,7 @@ exports.predefined = [
                 return new TwoArgToOperatorProcCallGenerator(
                     context, id, type, operator);
                 });
-        var symbol = new Type.Symbol(name, proc);
+        var symbol = new Symbol.Symbol(name, proc);
         return symbol;
     }(),
     function(){
@@ -595,7 +596,7 @@ exports.predefined = [
                 return new TwoArgToOperatorProcCallGenerator(
                     context, id, type, operator);
                 });
-        var symbol = new Type.Symbol(name, proc);
+        var symbol = new Symbol.Symbol(name, proc);
         return symbol;
     }()
     ];

+ 81 - 0
src/scope.js

@@ -0,0 +1,81 @@
+"use strict";
+
+var Class = require("rtl.js").Class;
+var Errors = require("errors.js");
+var Procedure = require("procedure.js");
+var Symbol = require("symbol.js");
+var Type = require("type.js");
+
+var stdSymbols = function(){
+    var symbols = {};
+    for(var t in Type.basic){
+        var type = Type.basic[t];
+        symbols[type.name()] = new Symbol.Symbol(type.name(), new Type.TypeId(type));
+    }
+    symbols["LONGREAL"] = new Symbol.Symbol("LONGREAL", new Type.TypeId(Type.basic.real));
+    
+    var predefined = Procedure.predefined;
+    for(var i = 0; i < predefined.length; ++i){
+        var s = predefined[i];
+        symbols[s.id()] = s;
+    }
+    return symbols;
+}();
+
+var Scope = Class.extend({
+    init: function Scope(id){
+        this.__id = id;
+        this.__symbols = {};
+        for(var p in stdSymbols)
+            this.__symbols[p] = stdSymbols[p];
+        this.__unresolved = [];
+    },
+    id: function(){return this.__id;},
+    addSymbol: function(symbol){
+        var id = symbol.id();
+        if (this.findSymbol(id))
+            throw new Errors.Error( "'" + id + "' already declared");
+        this.__symbols[id] = symbol;
+
+        var i = this.__unresolved.indexOf(id);
+        if (i != -1){
+            var info = symbol.info();
+            if (!(info.type() instanceof Type.Record))
+                throw new Errors.Error(
+                    "'" + id + "' must be of RECORD type because it was used before in the declation of POINTER");
+            this.__unresolved.splice(i, 1);
+        }
+    },
+    findSymbol: function(ident){return this.__symbols[ident];},
+    addUnresolved: function(id){this.__unresolved.push(id);},
+    unresolved: function(){return this.__unresolved;}
+});
+
+var ProcedureScope = Scope.extend({
+    init: function ProcedureScope(){
+        Scope.prototype.init.call(this, "procedure");
+    },
+    addSymbol: function(symbol, exported){
+        if (exported)
+            throw new Errors.Error("cannot export from within procedure: " 
+                + symbol.info().idType() + " '" + symbol.id() + "'");
+        Scope.prototype.addSymbol.call(this, symbol, exported);
+    }
+});
+
+var Module = Scope.extend({
+    init: function Module(){
+        Scope.prototype.init.call(this, "module");
+        this.__exports = [];
+    },
+    addSymbol: function(symbol, exported){
+        if (exported)
+            if (!symbol.isType() || symbol.info().type() instanceof Type.Record)
+                this.__exports.push(symbol);
+        Scope.prototype.addSymbol.call(this, symbol, exported);
+    },
+    exports: function(){return this.__exports;}
+});
+
+exports.Procedure = ProcedureScope;
+exports.Module = Module;

+ 31 - 0
src/symbol.js

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

+ 7 - 22
src/type.js

@@ -89,14 +89,14 @@ exports.Record = BasicType.extend({
 		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;
+		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){
@@ -107,7 +107,6 @@ exports.Record = BasicType.extend({
 	},
 	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)
@@ -171,20 +170,6 @@ var Module = Id.extend({
 	}
 });
 
-var Symbol = Class.extend({
-	init: function Symbol(id, info){
-		this.__id = id;
-		this.__info = info;
-	},
-	id: function(){return this.__id;},
-	info: function(){return this.__info;},
-	isModule: function(){return this.__info instanceof Module;},
-	isVariable: function(){return this.__info instanceof exports.Variable;},
-	isConst: function(){return this.__info instanceof exports.Const;},
-	isType: function(){return this.__info instanceof TypeId;},
-	isProcedure: function(){return this.__info instanceof exports.Procedure;},
-});
-
-exports.Symbol = Symbol;
+exports.Module = Module;
 exports.Type = Type;
 exports.TypeId = TypeId;

+ 3 - 0
test/expected/blur.js

@@ -52,4 +52,7 @@ function Blur2DArray(){
 	}
 }
 Blur2DArray();
+return {
+	Blur2DArray: Blur2DArray
+}
 }();

+ 52 - 0
test/expected/export.js

@@ -0,0 +1,52 @@
+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 (/*dimensions, initializer*/){
+        var forward = Array.prototype.slice.call(arguments);
+        var result = new Array(forward.shift());
+        var i;
+        if (forward.length == 1){
+            var init = forward[0];
+            if (typeof init == "function")
+                for(i = 0; i < result.length; ++i)
+                    result[i] = init();
+            else
+                for(i = 0; i < result.length; ++i)
+                    result[i] = init;
+        }
+        else
+            for(i = 0; i < result.length; ++i)
+                result[i] = this.makeArray.apply(this, forward);
+        return result;
+    }
+};
+var m = function (){
+var ci = 123;
+var T1 = RTL$.extend({
+	init: function T1(){
+	}
+});
+var pr1 = null;
+var p2 = null;
+var vi = 0;
+
+function p1(){
+}
+return {
+	ci: ci
+	T1: T1
+	pr1: pr1
+	p2: function(){return p2;}
+	vi: function(){return vi;}
+	p1: p1
+}
+}();

+ 16 - 0
test/input/export.ob

@@ -0,0 +1,16 @@
+MODULE m;
+CONST
+    ci* = 123;
+TYPE
+    T1* = RECORD END;
+    T2* = PROCEDURE;
+    T3* = ARRAY 5 OF INTEGER;
+VAR
+    pr1*: POINTER TO T1;
+    p2*: T2;
+    vi*: INTEGER;
+
+PROCEDURE p1*;
+END p1;
+
+END m.

+ 40 - 2
test/test_unit.js

@@ -87,9 +87,9 @@ function pass(/*...*/){return Array.prototype.slice.call(arguments);}
 
 function fail(/*...*/){return Array.prototype.slice.call(arguments);}
 
-function testWithContext(context, pass, fail){
+function testWithSetup(setup, pass, fail){
     return function(){
-        var test = setupWithContext(context.grammar, context.source);
+        var test = setup();
         var i;
         for(i = 0; i < pass.length; ++i)
             test.parse(pass[i]);
@@ -100,6 +100,20 @@ function testWithContext(context, pass, fail){
     };
 }
 
+function testWithContext(context, pass, fail){
+    return testWithSetup(
+        function(){return setupWithContext(context.grammar, context.source);},
+        pass,
+        fail);
+}
+
+function testWithGrammar(grammar, pass, fail){
+    return testWithSetup(
+        function(){return setup(grammar);},
+        pass,
+        fail);
+}
+
 var testSuite = {
 comment: function(){
     var test = setup(Grammar.expression);
@@ -1114,6 +1128,30 @@ assert: function(){
     test.expectError("ASSERT()", "at least 1 argument expected, got 0");
     test.expectError("ASSERT(123, TRUE)", "type mismatch for argument 1: 'INTEGER' cannot be converted to 'BOOLEAN'");
 },
+"export": testWithGrammar(
+    Grammar.declarationSequence,
+    pass("CONST i* = 1;",
+         "TYPE T* = RECORD END;",
+         "TYPE PT* = POINTER TO RECORD f*: INTEGER END;",
+         "VAR i*: INTEGER;",
+         "VAR i*: POINTER TO RECORD f*: INTEGER END;",
+         "VAR i*: POINTER TO RECORD r*: RECORD f*: INTEGER END END;",
+         "PROCEDURE p*; END p;"
+         ),
+    fail(["VAR r*: RECORD END;",
+          "only scalar type variables can be exported"],
+         ["VAR a*: ARRAY 5 OF INTEGER;",
+          "only scalar type variables can be exported"],
+         ["TYPE T = RECORD f*: INTEGER END;",
+          "field 'f' can be exported only if record 'T' itself is exported too"],
+         ["VAR p: POINTER TO RECORD f*: INTEGER END;",
+          "field 'f' can be exported only if variable 'p' itself is exported too"],
+         ["VAR p*: POINTER TO RECORD r: RECORD f*: INTEGER END END;",
+          "field 'f' can be exported only if field 'r' itself is exported too"],
+         ["PROCEDURE p*; VAR i*: INTEGER; END p;",
+          "cannot export from within procedure: variable 'i'"]
+         )
+    ),
 IMPORT: function(){
     var test = setup(Grammar.module);
     test.parse("MODULE m; IMPORT JS; END m.");