nodent-compiler
Version:
NoDent - Asynchronous Javascript language extensions
275 lines (246 loc) • 9.09 kB
JavaScript
;
var acorn = require("acorn");
var treeSurgeon = require('nodent-transform') ;
var outputCode = require('./output') ;
var alreadyInstalledPlugin = false ;
function acornParse(code,config) {
var comments = [] ;
var options = {
ecmaVersion:9,
allowHashBang:true,
allowReturnOutsideFunction:true,
allowImportExportEverywhere:true,
locations:true,
onComment:comments
} ;
if (config)
for (var k in config)
if (k !== 'noNodentExtensions' && k != 'onParserInstallation')
options[k] = config[k] ;
if (!(config && config.noNodentExtensions) || parseInt(acorn.version) < 4) {
if (!alreadyInstalledPlugin) {
if (parseInt(acorn.version) < 4)
console.warn("Nodent: Warning - noNodentExtensions option requires acorn >=v4.x. Extensions installed.") ;
require('acorn-es7-plugin')(acorn) ;
if (config && config.onParserInstallation) config.onParserInstallation(acorn) ;
alreadyInstalledPlugin = true ;
}
options.plugins = options.plugins || {} ;
options.plugins.asyncawait = {asyncExits:true, awaitAnywhere:true} ;
}
var ast = acorn.parse(code,options) ;
// attach comments to the most tightly containing node
treeSurgeon.treeWalker(ast,function(node,descend,path){
descend() ;
while (comments.length && node.loc &&
(node.loc.start.line >= comments[0].loc.start.line && node.loc.end.line>=comments[0].loc.end.line)) {
node.$comments = node.$comments||[] ;
node.$comments.push(comments.shift()) ;
}
}) ;
return ast ;
}
function parse(code) {
return acornParse(code, {
noNodentExtensions:true, // The partial parser only ever parses vanilla JS
locations:false,
ranges:false,
onComment:null
}) ;
}
function printNode(n) {
if (!n) return '' ;
if (Array.isArray(n))
return n.map(printNode).join("|\n");
try {
return outputCode(n) ; //+"\t//@"+Object.keys(n).filter(function(k){ return k[0]==='$'}).map(function(k){ return k+":"+n[k] });
} catch (ex) {
return ex.message + ": " + (n && n.type);
}
} ;
/* Utils */
function copyObj(a){
var o = {} ;
a.forEach(function(b){
if (b && typeof b==='object')
for (var k in b)
o[k] = b[k] ;
}) ;
return o ;
}
function btoa(str) {
var buffer ;
if (str instanceof Buffer) {
buffer = str;
} else {
if (Buffer.from) {
buffer = Buffer.from(str.toString(), 'binary');
} else {
buffer = new Buffer(str.toString(), 'binary');
}
}
return buffer.toString('base64');
}
function noLogger(){}
/* NodentCompiler prototypes, that refer to 'this' */
function compile(code,origFilename,__sourceMapping,opts) {
if (typeof __sourceMapping==="object" && opts===undefined)
opts = __sourceMapping ;
opts = opts || {} ;
// Fill in any default codeGen options
for (var k in NodentCompiler.initialCodeGenOpts) {
if (!(k in opts))
opts[k] = NodentCompiler.initialCodeGenOpts[k] ;
}
var pr = this.parse(code,origFilename,null,opts);
this.transform(pr,opts,{
parse: parse, // Parse a JS fragment into an AST
printNode: printNode, // Print a node as JS source
logger:this.logger || noLogger // Log a warning
}) ;
this.prettyPrint(pr,opts) ;
return pr ;
}
function prettyPrint(pr,opts) {
var map ;
var filepath = pr.filename ? pr.filename.split("/") :["anonymous"] ;
var filename = filepath.pop() ;
var out = outputCode(pr.ast,(opts && opts.sourcemap)?{map:{
startLine: opts.mapStartLine || 0,
file: filename+"(original)",
sourceMapRoot: filepath.join("/"),
sourceContent: pr.origCode
}}:null, pr.origCode) ;
if (opts && opts.sourcemap){
try {
var mapUrl = "" ;
var jsmap = out.map.toJSON();
if (jsmap) {
// require an expression to defeat browserify
var SourceMapConsumer = require('source-map').SourceMapConsumer;
pr.sourcemap = jsmap ;
this.smCache[pr.filename] = {map:jsmap,smc:new SourceMapConsumer(jsmap)} ;
mapUrl = "\n\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,"
+btoa(JSON.stringify(jsmap))+"\n" ;
}
pr.code = out.code+mapUrl ;
} catch (ex) {
pr.code = out ;
}
} else {
pr.code = out ;
}
return pr ;
}
function NodentCompiler(members) {
this.covers = {} ;
this._ident = NodentCompiler.prototype.version+"_"+Math.random() ;
this.setOptions(members || {}) ;
}
NodentCompiler.prototype.smCache = {} ;
NodentCompiler.prototype.setOptions = function(members){
this.log = members.log===false?noLogger:members.log||this.log;
this.options = copyObj([this.options,members]) ;
delete this.options.log ;
return this ;
};
function parseCode(code,origFilename,__sourceMapping,opts) {
if (typeof __sourceMapping==="object" && opts===undefined)
opts = __sourceMapping ;
var r = { origCode:code.toString(), filename:origFilename } ;
try {
r.ast = acornParse(r.origCode, opts && opts.parser) ;
if (opts.babelTree) {
treeSurgeon.treeWalker(r.ast,function(node,descend,path){
if (node.type==='Literal')
path[0].replace(treeSurgeon.babelLiteralNode(node.value)) ;
else if (node.type==='Property') {
// Class/ObjectProperty in babel6
if (path[0].parent.type==='ClassBody'){
// There's no easy mapping here as it appears to be borderline in the specification?
// It's definitely a kind of ClassProperty tho....
node.type = 'ClassProperty' ;
} else {
node.type = 'ObjectProperty' ;
}
}
descend() ;
}) ;
}
return r ;
} catch (ex) {
if (ex instanceof SyntaxError) {
var l = r.origCode.substr(ex.pos-ex.loc.column) ;
l = l.split("\n")[0] ;
ex.message += " "+origFilename+" (nodent)\n"+l+"\n"+l.replace(/[\S ]/g,"-").substring(0,ex.loc.column)+"^" ;
ex.stack = "" ;
}
throw ex ;
}
}
NodentCompiler.prototype.version = require("./package.json").version ;
NodentCompiler.prototype.isThenable = function(x) { return x && x instanceof Object && typeof x.then==="function"} ;
NodentCompiler.prototype.compile = compile ;
// Exported ; but not to be used lightly!
NodentCompiler.prototype.parse = parseCode ;
NodentCompiler.prototype.prettyPrint = prettyPrint ;
NodentCompiler.prototype.getDefaultCompileOptions = undefined ;
NodentCompiler.prototype.transform = treeSurgeon.transform ;
// asynchronize and printNode were previuosly defined in arboriculture, but they are
// are now (>3.2.0) pulled up into compiler so arboriculture.transform can be called from other
// hosts such as babel 7
NodentCompiler.prototype.asynchronize = function asynchronize(pr, __sourceMapping, opts, logger) {
try {
var tree = treeSurgeon.transform(pr, opts, {
parse: parse, // Parse a JS fragment into an AST
printNode: printNode, // Print a node as JS source
logger:logger // Log a warning
}) ;
if (opts.babelTree) {
tree.ast = treeSurgeon.treeWalker(tree.ast, function(node,descend,path){
if (node.type === 'Literal')
path[0].replace(treeSurgeon.babelLiteralNode(node.value)) ;
else
descend() ;
}) ;
}
return tree ;
} catch (ex) {
if (ex instanceof SyntaxError) {
var l = pr.origCode.substr(ex.pos - ex.loc.column);
l = l.split("\n")[0];
ex.message += " (nodent)\n" + l + "\n" + l.replace(/[\S ]/g, "-").substring(0, ex.loc.column) + "^";
ex.stack = "";
}
throw ex;
}
} ;
NodentCompiler.prototype.printNode = printNode ;
Object.defineProperty(NodentCompiler.prototype,"Promise",{
get:function (){
console.warn("Warning: nodent.Promise is deprecated. Use nodent.Thenable instead");
return Thenable;
},
enumerable:false,
configurable:false
}) ;
NodentCompiler.initialCodeGenOpts = {
noRuntime:false,
lazyThenables:false,
es6target:false,
noUseDirective:false,
wrapAwait:null,
mapStartLine:0,
sourcemap:true,
engine:false,
parser:{sourceType:'script'},
generatedSymbolPrefix:"$",
$return:"$return",
$error:"$error",
$arguments:"$args",
$Promise:"Promise",
$asyncspawn:"$asyncspawn",
$asyncbind:"$asyncbind",
$makeThenable:'$makeThenable'
};
module.exports = NodentCompiler ;