neutrinoscript
Version:
Like C for Javascript
390 lines (334 loc) • 11.9 kB
JavaScript
(function (exports) {
const NODE_JS = 1;
const JS_SHELL = 2;
const BROWSER = 3;
var mode;
if (typeof process !== "undefined") {
mode = NODE_JS;
// Install compiler as an extension for '.ljs' files that are loaded using the
// |require| function. This is how mocha tests are executed.
var fs = require('fs');
if (require.extensions) {
require.extensions['.ljs'] = function(module, filename) {
var source = fs.readFileSync(filename, 'utf8');
return module._compile(compile(source, {filename: filename, memcheck: false}), filename);
};
}
} else if (typeof snarf !== "undefined") {
mode = JS_SHELL;
} else {
mode = BROWSER;
}
var util, esprima, escodegen, estransform, compiler;
var argv;
if (mode === NODE_JS) {
util = require("./util.js");
esprima = require("./esprima.js");
escodegen = require("./escodegen.js");
estransform = require("./estransform.js");
compiler = require("./compiler.js");
argv = process.argv.slice(2);
print = console.log;
quit = process.exit;
} else if (mode === JS_SHELL) {
load("./estransform.js");
load("./util.js");
load("./esprima.js");
load("./escodegen.js");
load("./compiler.js");
argv = this.arguments;
}
if (mode !== NODE_JS) {
util = this.util;
esprima = this.esprima;
escodegen = this.escodegen;
estransform = this.estransform;
compiler = this.compiler;
}
const assert = util.assert;
const lang = estransform.lang;
const allFields = estransform.allFields;
function pretty(node, indent) {
if (typeof indent === "undefined") {
indent = "";
}
var s = "";
if (node instanceof Array) {
for (var i = 0, j = node.length; i < j; i++) {
s += pretty(node[i], indent);
}
return s;
}
s += indent + node.type;
var spec = lang[node.type];
if (!spec) {
s += " ???\n";
return;
}
var fields = allFields(spec);
var children = [];
var values = [];
// We do loc manually.
fields.pop();
for (var i = 0, j = fields.length; i < j; i++) {
var fname = fields[i];
if (fname.charAt(0) === "@") {
fname = fname.substr(1);
if (node[fname]) {
children.push(pretty(node[fname], indent + " "));
}
} else {
if (typeof node[fname] !== "undefined") {
values.push(node[fname]);
}
}
}
if (values.length) {
s += " '" + values.join("' '") + "'";
}
var loc = node.loc;
if (loc) {
s += (" (" + loc.start.line + ":" + loc.start.column + "-" +
loc.end.line + ":" + loc.end.column + ")");
}
s += "\n" + children.join("");
return s;
}
function cli() {
var optparser = new util.OptParser([
["E", "only-parse", false, "Only parse"],
["A", "emit-ast", false, "Do not generate JS, emit AST"],
["P", "pretty-print", false, "Pretty-print AST instead of emitting JSON (with -A)"],
["b", "bare", false, "Do not wrap in a module"],
["l", "load-instead", false, "Emit load('memory') instead of require('memory')"],
["W", "warn", true, "Print warnings (enabled by default)"],
["Wconversion", null, false, "Print intra-integer and pointer conversion warnings"],
["0", "simple-log", false, "Log simple messages. No colors and snippets."],
["t", "trace", false, "Trace compiler execution"],
["o", "output", "", "Output file name"],
["m", "memcheck", false, "Compile with memcheck instrumentation"],
["h", "help", false, "Print this message"],
["w", "nowarn", false, "Inhibit all warning messages"]
]);
var p = optparser.parse(argv);
if (!p) {
quit(1);
}
var options = p.options;
//console.log(options);
options['bare'] = true;
var files = p.rest;
if (!files.length || options.help) {
print("ljc: [option(s)] file");
print(optparser.usage());
quit();
}
var filename = files[0];
var path = filename.split("/");
var basename = path.pop();
var dir = path.join("/");
basename = basename.substr(0, basename.lastIndexOf(".")) || basename;
var ccompiler = require("../../compile-js-like-c");
ccompiler.compile(filename, function(err, result) {
if (err) throw err;
//var source = snarf(filename);
var source = result;
options.filename = filename;
options.basename = basename;
var code = compile(source, options);
if (options["pretty-print"]) {
print(pretty(code));
} else {
// SpiderMonkey has no way to write to a file, but if we're on node we can
// emit .js.
if (options["output"] && mode === NODE_JS && !options["only-parse"]) {
// var outname = (dir ? dir + "/" : "") + basename;
// Don't overwrite the source file by mistake.
if (options["output"] !== filename) {
if (options["emit-ast"]) {
require('fs').writeFileSync(options["output"], JSON.stringify(code, null, 2));
} else {
// Escodegen doesn't emit a final newline for some reason, so add one.
require('fs').writeFileSync(options["output"], code + "\n");
}
}
} else {
print(code);
}
}
});
}
function RunNEUScript(script, options, cb) {
var ccompiler = require("../../compile-js-like-c");
ccompiler.compileStr(script, function(err, result) {
result = neutrinoShortcuts(result);
compileWEB(result, options, cb);
});
}
function RunNEU(filename, options, cb) {
var ccompiler = require("../../compile-js-like-c");
var path = filename.split("/");
var basename = path.pop();
var dir = path.join("/");
basename = basename.substr(0, basename.lastIndexOf(".")) || basename;
ccompiler.compile(filename, function(err, result) {
if (err) throw err;
result = neutrinoShortcuts(result);
options.filename = filename;
options.basename = basename;
cb(compile(result, options));
});
}
function neutrinoShortcuts(str) {
// Neutrino Shortcuts
str = str.replace(/\_\^/g,'console.log');
str = str.replace(/\^\_/g,'return');
str = str.replace(/←/g,'<<'); // Bitwise Left Shift
str = str.replace(/→/g,'>>'); // Bitwise Right Shift
str = str.replace(/→→/g,'>>>'); // Bitwise Zero-fill Right
str = str.replace(/\~\~/g,'-1*'); // Bitwise Not Operator
str = str.replace(/\((.*\))=>|\((.*\)) =>/g, 'function($1)');
str = str.replace(/~\(/g,'function(');
str = str.replace(/fn\(/g,'function(');
str = str.replace(/fn \(/g,'function(');
str = str.replace(/fun\(/g,'function(');
str = str.replace(/fun \(/g,'function(');
str = str.replace(/\)~/g,') {');
str = str.replace(/~>/g,'setTimeout');
str = str.replace(/~</g,'clearTimeout');
str = str.replace(/>~/g,'setInterval');
str = str.replace(/<~/g,'clearInterval');
str = str.replace(/!~/g,'{');
str = str.replace(/(if\s*\(.*?)( is )(.*?\))/g, '$1 === $3');
str = str.replace(/(if\s*\(.*?)( no )(.*?\))/g, '$1 != $3');
str = str.replace(/(if\s*\(.*?)( not )(.*?\))/g, '$1 !== $3');
str = str.replace(/~/g,'}'); // @FIXME: possible bug here if someone needs something silly like let tilde = '~';
return str;
}
function FromNEUtoJS(filename, outfile, options) {
var ccompiler = require("../../compile-js-like-c");
var path = filename.split("/");
var basename = path.pop();
var dir = path.join("/");
basename = basename.substr(0, basename.lastIndexOf(".")) || basename;
ccompiler.compile(filename, function(err, result) {
if (err) throw err;
result = neutrinoShortcuts(result);
options.filename = filename;
options.basename = basename;
if (options.compress !== false) {
options.compress = true;
}
var code = compile(result, options);
var ClosureCompiler = require('google-closure-compiler').compiler;
//console.log(ClosureCompiler.COMPILER_PATH); // absolute path the compiler jar
//console.log(ClosureCompiler.CONTRIB_PATH); // absolute path the contrib folder which contains
fs.writeFileSync(outfile,code,'utf8');
var infile = outfile.replace('.js','.min.js');
var closureCompiler = new ClosureCompiler({
js: outfile,
js_output_file: infile,
language_out: 'ECMASCRIPT5_STRICT',
compilation_level: 'SIMPLE',
create_source_map: infile +'.map',
});
var compilerProcess = closureCompiler.run(function(exitCode, stdOut, stdErr) {
//compilation complete
if (options.compress) {
var compressjs = require('compressjs');
var algorithm = compressjs.BWTC;
var data = new Buffer(fs.readFileSync(infile), 'utf8');
var compressed = new Buffer(algorithm.compressFile(data));
fs.writeFileSync(outfile,compressed);
} else {
// @TODO Beautify Code Output
fs.writeFileSync(outfile,fs.readFileSync(infile));
}
process.exit(1);
});
});
}
function compile(source, options) {
// -W anything infers -W.
for (var p in options) {
if (p.charAt(0) === "W") {
options.warn = true;
break;
}
}
if(options.nowarn) {
options.warn = false;
}
var logger = new util.Logger("ljc", options.filename, source, options);
var code;
try {
var node = esprima.parse(source, { loc: true, comment: true, range: true, tokens: true });
node = escodegen.attachComments(node, node.comments, node.tokens);
if (options["only-parse"]) {
code = node;
} else {
node = compiler.compile(node, options.filename, logger, options);
if (options["emit-ast"]) {
code = node;
} else {
code = escodegen.generate(node, { base: "", indent: " ", comment: true });
}
}
} catch (e) {
if (mode === BROWSER) {
throw e;
}
if (e.index) {
// Esprima error, make a loc out of it.
var lc = { line: e.lineNumber, column: e.column - 1 };
e.loc = { start: lc, end: lc };
logger.error(e.message, { start: lc, end: lc });
logger.flush();
quit(1);
}
if (e.logged && mode !== BROWSER) {
// Compiler error that has already been logged, so just flush and
// quit.
logger.flush();
quit(1);
}
throw e;
}
logger.flush();
return code;
}
function compileWEB(source, options, cb) {
// -W anything infers -W.
for (var p in options) {
if (p.charAt(0) === "W") {
options.warn = true;
break;
}
}
if(options.nowarn) {
options.warn = false;
}
var code;
var logger = new util.Logger("ljc", 'test.js', source, options);
try {
var node = esprima.parse(source, { loc: true, comment: true, range: true, tokens: true });
node = escodegen.attachComments(node, node.comments, node.tokens);
node = compiler.compile(node, options.filename, logger, options);
code = escodegen.generate(node, { base: "", indent: " ", comment: true });
} catch (e) {
throw e;
}
//logger.flush();
cb(code);
return code;
}
exports.cli = cli;
exports.compile = compile;
exports.compileWEB = compileWEB;
exports.RunNEU = RunNEU;
exports.RunNEUScript = RunNEUScript;
exports.FromNEUtoJS = FromNEUtoJS;
if (mode === JS_SHELL) {
cli();
}
}).call(this, typeof exports === "undefined" ? (LJC = {}) : exports);