소스 검색

support nodejs mosules as output

Vladislav Folts 11 년 전
부모
커밋
ff4ea93617
9개의 변경된 파일272개의 추가작업 그리고 92개의 파일을 삭제
  1. 62 2
      src/code.js
  2. 18 50
      src/context.js
  3. 33 0
      src/nodejs.js
  4. 51 12
      src/oc.js
  5. 5 4
      src/rtl.js
  6. 14 0
      test/expected/nodejs/export.js
  7. 13 0
      test/input/nodejs/export.ob
  8. 55 19
      test/test_compile.js
  9. 21 5
      test/test_unit.js

+ 62 - 2
src/code.js

@@ -7,7 +7,8 @@ var NullCodeGenerator = Class.extend({
 	init: function NullCodeGenerator(){},
 	write: function(){},
     openScope: function(){},
-    closeScope: function(){}
+    closeScope: function(){},
+    getResult: function(){}
 });
 
 exports.Generator = Class.extend({
@@ -82,6 +83,65 @@ function adjustPrecedence(e, precedence){
     return code;
 }
 
+function genExport(symbol){
+    if (symbol.isVariable())
+        return "function(){return " + symbol.id() + ";}";
+    if (symbol.isType()){
+        var type = symbol.info().type();
+        if (!(type instanceof Type.Record || type instanceof Type.Pointer))
+            return undefined;
+    }
+    return symbol.id();
+}
+
+var ModuleGenerator = Class.extend({
+    init: function Code$ModuleGenerator(name, imports){
+        this.__name = name;
+        this.__imports = imports;
+    },
+    prolog: function(){
+        var result = "";            
+        var modules = this.__imports;
+        for(var name in modules){
+            var alias = modules[name];
+            if (result.length)
+                result += ", ";
+            result += alias;
+        }
+        return "var " + this.__name + " = function " + "(" + result + "){\n";
+    },
+    epilog: function(exports){
+        var result = "";
+        for(var access in exports){
+            var e = exports[access];
+            var code = genExport(e);
+            if (code){
+                if (result.length)
+                    result += ",\n";
+                result += "\t" + e.id() + ": " + code;
+            }
+        }
+        if (result.length)
+            result = "return {\n" + result + "\n}\n";
+        
+        result += "}(";
+
+        var modules = this.__imports;
+        var importList = "";
+        for(var name in modules){
+            if (importList.length)
+                importList += ", ";
+            importList += name;
+        }
+        result += importList;
+        result += ");\n";
+
+        return result;
+    }
+});
+
 exports.nullGenerator = new NullCodeGenerator();
 exports.Expression = Expression;
-exports.adjustPrecedence = adjustPrecedence;
+exports.adjustPrecedence = adjustPrecedence;
+exports.genExport = genExport;
+exports.ModuleGenerator = ModuleGenerator;

+ 18 - 50
src/context.js

@@ -1683,64 +1683,27 @@ exports.TypeCast = ChainedContext.extend({
     }
 });
 
-function genExport(symbol){
-    if (symbol.isVariable())
-        return "function(){return " + symbol.id() + ";}";
-    if (symbol.isType()){
-        var type = symbol.info().type();
-        if (!(type instanceof Type.Record || type instanceof Type.Pointer))
-            return undefined;
-    }
-    return symbol.id();
-}
-
-function genExports(exports, gen){
-    var result = "";
-    for(var access in exports){
-        var e = exports[access];
-        var code = genExport(e);
-        if (code){
-            if (result.length)
-                result += ",\n";
-            result += "\t" + e.id() + ": " + code;
-        }
-    }
-    if (!result.length)
-        return;
-    gen.write("return {\n" + result + "\n}\n");
-}
-
 exports.ModuleDeclaration = ChainedContext.extend({
     init: function ModuleDeclarationContext(context){
         ChainedContext.prototype.init.call(this, context);
         this.__name = undefined;
         this.__imports = {};
         this.__moduleScope = undefined;
+        this.__moduleGen = undefined;
     },
     setIdent: function(id){
+        var parent = this.parent();
         if (this.__name === undefined ) {
             this.__name = id;
             this.__moduleScope = new Scope.Module(id);
-            this.parent().pushScope(this.__moduleScope);
+            parent.pushScope(this.__moduleScope);
         }
         else if (id === this.__name){
-            var scope = this.parent().currentScope();
+            var scope = parent.currentScope();
             scope.strip();
             var exports = scope.exports();
             scope.module().info().defineExports(exports);
-            var gen = this.codeGenerator();
-            genExports(exports, gen);
-            gen.write("}(");
-
-            var modules = this.__imports;
-            var importList = "";
-            for(var name in modules){
-                if (importList.length)
-                    importList += ", ";
-                importList += name;
-            }
-            gen.write(importList);
-            gen.write(");\n");
+            this.codeGenerator().write(this.__moduleGen.epilog(exports));
         }
         else
             throw new Errors.Error("original module name '" + this.__name + "' expected, got '" + id + "'" );
@@ -1751,18 +1714,19 @@ exports.ModuleDeclaration = ChainedContext.extend({
         return this.parent().findModule(name);
     },
     handleImport: function(modules){
-        var gen = this.codeGenerator();
-        gen.write("var " + this.__name + " = function " + "(");
         var scope = this.currentScope();
+        var moduleAliases = {};
         for(var i = 0; i < modules.length; ++i){
             var s = modules[i];
-            this.__imports[s.info().name()] = s;
+            var name = s.info().name();
+            this.__imports[name] = s;
             scope.addSymbol(s);
-            if (i)
-                gen.write(", ");
-            gen.write(s.id());
+            moduleAliases[name] = s.id();
         }
-        gen.write("){\n");
+        this.__moduleGen = this.parent().makeModuleGenerator(
+                this.__name,
+                moduleAliases);
+        this.codeGenerator().write(this.__moduleGen.prolog());
     },
     qualifyScope: function(scope){
         if (scope != this.__moduleScope && scope instanceof Scope.Module)
@@ -1825,8 +1789,9 @@ var ModuleImport = ChainedContext.extend({
 exports.ModuleImport = ModuleImport;
 
 exports.Context = Class.extend({
-    init: function Context(code, rtl, moduleResolver){
+    init: function Context(code, moduleGeneratorFactory, rtl, moduleResolver){
         this.__code = code;
+        this.__moduleGeneratorFactory = moduleGeneratorFactory;
         this.__scopes = [];
         this.__gen = 0;
         this.__vars = [];
@@ -1859,6 +1824,9 @@ exports.Context = Class.extend({
     handleExpression: function(){},
     handleLiteral: function(){},
     codeGenerator: function(){return this.__code;},
+    makeModuleGenerator: function(name, imports){
+        return this.__moduleGeneratorFactory(name, imports);
+    },
     rtl: function(){return this.__rtl;},
     findModule: function(name){
         if (name == "JS"){

+ 33 - 0
src/nodejs.js

@@ -0,0 +1,33 @@
+"use strict";
+
+var Class = require("rtl.js").Class;
+var Code = require("code.js");
+
+var ModuleGenerator = Class.extend({
+    init: function Nodejs$ModuleGenerator(name, imports){
+        this.__name = name;
+        this.__imports = imports;
+    },
+    prolog: function(){
+        var result = "";            
+        var modules = this.__imports;
+        for(var name in modules){
+            var alias = modules[name];
+            result += "var " + alias + " = require(" + name + ".js);\n";
+        }
+        return result;
+    },
+    epilog: function(exports){
+        var result = "";
+        for(var access in exports){
+            var e = exports[access];
+            var code = Code.genExport(e);
+            if (code){
+                result += "exports." + e.id() + " = " + code + "\n";
+            }
+        }
+        return result;
+    }
+});
+
+exports.ModuleGenerator = ModuleGenerator;

+ 51 - 12
src/oc.js

@@ -5,6 +5,7 @@ var Context = require("context.js");
 var Errors = require("errors.js");
 var Grammar = require("grammar.js");
 var Lexer = require("lexer.js");
+var Nodejs = require("nodejs.js");
 var ImportRTL = require("rtl.js");
 var Stream = require("stream.js").Stream;
 
@@ -22,9 +23,7 @@ var CompiledModule = Class.extend({
     exports: function(){return this.__exports;}
 });
 
-function compileModule(stream, rtl, moduleResolver, handleErrors){
-    var code = new Code.Generator();
-    var context = new Context.Context(code, rtl, moduleResolver);
+function compileModule(stream, context, handleErrors){
     Lexer.skipSpaces(stream, context);  
     try {
         if (!Grammar.module(stream, context))
@@ -41,28 +40,68 @@ function compileModule(stream, rtl, moduleResolver, handleErrors){
         throw x;
     }
     var scope = context.currentScope();
-    return new CompiledModule(scope.module(), code.getResult(), scope.exports());
+    return new CompiledModule(
+            scope.module(),
+            context.codeGenerator().getResult(),
+            scope.exports());
 }
 
-function compile(text, handleErrors){
+function compileImpl(text, contextFactory, handleErrors, handleCompiledModule){
     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);
+        var context = contextFactory(resolveModule);
+        var module = compileModule(stream, context, handleErrors);
         if (!module)
-            return undefined;
+            return;
         var symbol = module.symbol();
-        modules[symbol.id()] = symbol.info();
-        code += module.code();
+        var moduleName = symbol.id();
+        modules[moduleName] = symbol.info();
+        handleCompiledModule(moduleName, module.code());
         Lexer.skipSpaces(stream);
     } 
     while (!stream.eof());
-    return rtl.generate() + code;
+}
+
+function compile(text, handleErrors){
+    var result = "";
+    var rtl = new RTL();
+    var moduleCode = function(name, imports){return new Code.ModuleGenerator(name, imports);};
+    compileImpl(
+            text,
+            function(moduleResolver){
+                return new Context.Context(new Code.Generator(),
+                                           moduleCode,
+                                           rtl,
+                                           moduleResolver);
+            },
+            handleErrors,
+            function(name, code){result += code;});
+
+    var rtlCode = rtl.generate();
+    if (rtlCode)
+        result = rtlCode + result;
+    return result;
+}
+
+function compileNodejs(text, handleErrors, handleCompiledModule){
+    var rtl = new RTL();
+    var code = new Code.Generator();
+    var moduleCode = function(name, imports){return new Nodejs.ModuleGenerator(name, imports);};
+
+    compileImpl(
+            text,
+            function(moduleResolver){return new Context.Context(code, moduleCode, rtl, moduleResolver);},
+            handleErrors,
+            handleCompiledModule);
+
+    var rtlCode = rtl.generate();
+    if (rtlCode)
+        handleCompiledModule(rtl.name(), rtlCode);
 }
 
 exports.compileModule = compileModule;
 exports.compile = compile;
+exports.compileNodejs = compileNodejs;

+ 5 - 4
src/rtl.js

@@ -123,13 +123,14 @@ exports.RTL = Class.extend({
             this[fName + "Id"] = this.__makeIdOnDemand(fName);
         }
     },
+    name: function(){return "RTL$";},
     baseClass: function(){
         if (!this.__entries["extend"])
             this.__entries.extend = Class.extend;
-        return "RTL$";
+        return this.name();
     },
     generate: function(){
-        var result = "var RTL$ = {\n";
+        var result = "var " + this.name() + " = {\n";
         var firstEntry = true;
         for (var name in this.__entries){
             if (!firstEntry)
@@ -141,7 +142,7 @@ exports.RTL = Class.extend({
         if (!firstEntry)
             result += "\n};\n";
         else
-            result = "";
+            result = undefined;
         
         return result;
     },
@@ -149,7 +150,7 @@ exports.RTL = Class.extend({
         return function(){
             if (!this.__entries[name])
                 this.__entries[name] = impl[name];
-            return "RTL$." + name;
+            return this.name() + "." + name;
         };
     },
     __makeOnDemand: function(name){

+ 14 - 0
test/expected/nodejs/export.js

@@ -0,0 +1,14 @@
+var m = function (){
+var ci = 123;
+var p = null;
+var vi = 0;
+
+function p1(){
+}
+return {
+	ci: ci,
+	p: function(){return p;},
+	vi: function(){return vi;},
+	p1: p1
+}
+}();

+ 13 - 0
test/input/nodejs/export.ob

@@ -0,0 +1,13 @@
+MODULE m;
+CONST
+    ci* = 123;
+TYPE
+    T* = PROCEDURE;
+VAR
+    p*: T;
+    vi*: INTEGER;
+
+PROCEDURE p1*;
+END p1;
+
+END m.

+ 55 - 19
test/test_compile.js

@@ -11,8 +11,8 @@ function normalizeLineEndings(text){
 }
 
 function compareResults(result, name, dirs){
-    fs.writeFileSync(dirs.output + "/" + name, result);
-    var expected = fs.readFileSync(dirs.expected + "/" + name, "utf8");
+    fs.writeFileSync(path.join(dirs.output, name), result);
+    var expected = fs.readFileSync(path.join(dirs.expected, name), "utf8");
     if (normalizeLineEndings(result) != normalizeLineEndings(expected))
         throw new Test.TestError("Failed");
 }
@@ -26,6 +26,27 @@ function compile(src){
     return result;
 }
 
+function compileNodejs(src, dirs){
+    var text = fs.readFileSync(src, "utf8");
+    
+    var subdir = path.basename(src);
+    subdir = subdir.substr(0, subdir.length - path.extname(subdir).length);
+
+    var outDir = path.join(dirs.output, subdir);
+    fs.mkdirSync(outDir);
+
+    var errors = "";
+    oc.compileNodejs(text,
+                     function(e){errors += e;},
+                     function(name, code){
+                        var filePath = path.join(outDir, name + ".js");
+                        fs.writeFileSync(filePath, code);
+                     }
+                    );
+    if (errors)
+        throw new Test.TestError(errors);
+}
+
 function expectOk(src, dirs){
     var result = compile(src);
     var resultName = path.basename(src).replace(".ob", ".js");
@@ -60,13 +81,24 @@ function makeTests(test, dirs){
     var tests = {};
     for(var i = 0; i < sources.length; ++i){
         var source = sources[i];
-        var path = dirs.input + "/" + source;
-        if (fs.statSync(path).isFile())
-            tests[source] = makeTest(test, path, dirs);
+        var filePath = path.join(dirs.input, source);
+        if (fs.statSync(filePath).isFile())
+            tests[source] = makeTest(test, filePath, dirs);
     }
     return tests;
 }
 
+function rmTree(root){
+    fs.readdirSync(root).forEach(function(file){
+        var filePath = path.join(root, file);
+        if (fs.statSync(filePath).isDirectory())
+            rmTree(filePath);
+        else
+            fs.unlinkSync(filePath);
+    });
+    fs.rmdirSync(root);
+}
+
 function main(){
     if (process.argv.length > 2){
         var tests = {};
@@ -79,22 +111,26 @@ function main(){
     var okDirs = {input: "input", output: "output", expected: "expected"};
     var errDirs = {};
     var runDirs = {};
+    var nodejsDirs = {};
     var p;
-    for(p in okDirs)
-        errDirs[p] = okDirs[p] + "/errors";
-    for(p in okDirs)
-        runDirs[p] = okDirs[p] + "/run";
-
-    if (!fs.existsSync(okDirs.output))
-        fs.mkdirSync(okDirs.output);
-    if (!fs.existsSync(errDirs.output))
-        fs.mkdirSync(errDirs.output);
-    if (!fs.existsSync(runDirs.output))
-        fs.mkdirSync(runDirs.output);
-
-    Test.run({"expect OK": makeTests(expectOk, okDirs),
+    for(p in okDirs){
+        errDirs[p] = path.join(okDirs[p], "errors");
+        runDirs[p] = path.join(okDirs[p], "run");
+        nodejsDirs[p] = path.join(okDirs[p], "nodejs");
+    }
+
+    var dirsSet = [okDirs, errDirs, runDirs, nodejsDirs];
+    for(var i = 0; i < dirsSet.length; ++i){
+        var output = dirsSet[i].output;
+        if (fs.existsSync(output))
+            rmTree(output);
+        fs.mkdirSync(output);
+    }
+
+    Test.run({"expect OK": makeTests(expectOk, okDirs, compile),
               "expect compile error": makeTests(expectError, errDirs),
-              "run": makeTests(run, runDirs)}
+              "run": makeTests(run, runDirs),
+              "nodejs": makeTests(compileNodejs, nodejsDirs)}
             );
 }
 

+ 21 - 5
test/test_unit.js

@@ -21,9 +21,19 @@ function parseInContext(grammar, s, context){
         throw new Errors.Error("not parsed");
 }
 
+var TestModuleGenerator = Class.extend({
+    init: function TestModuleGenerator(){},
+    prolog: function(){return undefined;},
+    epilog: function(){return undefined;}
+});
+
 var TestContext = Context.Context.extend({
     init: function TestContext(){
-        Context.Context.prototype.init.call(this, Code.nullGenerator, new RTL());
+        Context.Context.prototype.init.call(
+                this,
+                Code.nullGenerator,
+                function(){return new TestModuleGenerator();},
+                new RTL());
         this.pushScope(new Scope.Module("test"));
     },
     qualifyScope: function(){return "";}
@@ -138,16 +148,22 @@ function testWithGrammar(grammar, pass, fail){
         fail);
 }
 
+var TestContextWithModule = TestContext.extend({
+    init: function(module){
+        TestContext.prototype.init.call(this);
+        this.__module = module;
+    },
+    findModule: function(){return this.__module;}
+});
+
 function testWithModule(src, pass, fail){
     return testWithSetup(
         function(){
-            var rtl = new RTL();
-            var imported = oc.compileModule(new Stream(src), rtl);
+            var imported = oc.compileModule(new Stream(src), makeContext());
             var module = imported.symbol().info();
             return setup(function(s){
                 oc.compileModule(new Stream(s),
-                                 rtl,
-                                 function(){return module;});
+                                 new TestContextWithModule(module));
             });},
         pass,
         fail);