#!/usr/bin/env node var path = require('path'); var fs = require('fs'); var package = require('../package.json'); var compiler = require("../dist/compiler.cjs"); var helpers = compiler.helpers; function iter$(a){ return a ? (a.toArray ? a.toArray() : a) : []; }; var self = {}; // imba$v2=0 var imbac1; var stdin = process.stdin; var stdout = process.stdout; var ansi = helpers.ansi; var parseOpts = { alias: { o: 'output', h: 'help', s: 'stdio', p: 'print', m: 'sourcemap', t: 'tokenize', v: 'version', w: 'watch', d: 'debug' }, schema: { output: {type: 'string'}, outext: {type: 'string'}, sourcemap: {type: 'string'}, // inline | extern platform: {type: 'string'}, // node | browser | worker styles: {type: 'string'}, // extern | inline imbaPath: {type: 'string'}, // global | inline | import sourceId: {type: 'string'}, bin: {type: 'string'}, hmr: null, raw: null }, groupz: ['sourcemap'] }; var help = `\n Usage: imbac [options] path/to/script.imba -h, --help display this help message -v, --version display the version number -w, --watch recompile files on change -m, --sourcemap generate source map and add inline to .js files -o, --output [dir] set the output directory for compiled JavaScript -p, --print print out the compiled JavaScript -d, --debug Enable compiler debugging -s, --stdio listen for and compile scripts over stdio -t, --tokenize print out the tokens that the lexer/rewriter produce --bin [bin] compile file with a hashbang: 'node', 'bun', etc. --platform [type] Specify platform: 'node', 'browser', 'worker' --styles [format] Specify how styles should be added: 'extern', 'inline' --silent only print out errors (skip warnings) --hmr compile for hot-module-reloading --analyze print out the scopes and variables of your script `; function CLI(options){ if(options === undefined) options = {}; this._options = options; this._sources = []; this._current = null; if(!options.fs) Object.defineProperty(options,'fs',{value: fs, enumerable:false}); if(!options.path) Object.defineProperty(options,'path',{value: path, enumerable:false}); this; }; CLI.prototype.sources = function(v){ return this._sources; } CLI.prototype.setSources = function(v){ this._sources = v; return this; }; CLI.prototype.ensureDir = function (src){ if (fs.existsSync(src)) { return true }; var parts = path.normalize(src).split(path.sep); for (let i = 0, items = iter$(parts), len = items.length, part; i < len; i++) { part = items[i]; if (i < 1) { continue; }; // what about relative paths here? no good? might be important for symlinks etc no? var dir = parts.slice(0,i + 1).join(path.sep); if (fs.existsSync(dir)) { var stat = fs.statSync(dir); } else if (part.match(/\.(imba\d?|js)$/) || i == len - 1) { true; } else { fs.mkdirSync(dir); console.log(ansi.green(("+ mkdir " + dir))); }; }; return; }; CLI.prototype.findRecursive = function (root,pattern){ var m; if(pattern === undefined) pattern = /\.imba\d?$/; var results = []; root = path.relative(process.cwd(),root); root = path.normalize(root); var read = function(src,depth) { src = path.normalize(src); var stat = fs.statSync(src); if (stat.isDirectory() && depth > 0) { var files = fs.readdirSync(src); let res = []; for (let i = 0, items = iter$(files), len = items.length; i < len; i++) { res.push(read(src + '/' + items[i],depth - 1)); }; return res; } else if (src.match(pattern)) { return results.push(src); }; }; if (m = root.match(/\/\*\.imba\d?$/)) { root = root.slice(0,-m[0].length); read(root,1); } else { read(root,10); }; return results; }; CLI.prototype.pathToSource = function (src,coll,o,root){ if(root === undefined) root = null; var abs = path.resolve(process.cwd(),src); var stat = fs.statSync(abs); if (stat.isDirectory()) { // console.log "is directory",findRecursive(abs) var files = this.findRecursive(abs); for (let i = 0, items = iter$(files), len = items.length; i < len; i++) { this.pathToSource(items[i],coll,o,abs); }; return; }; var file = { filename: path.basename(src), sourcePath: abs, sourceBody: fs.readFileSync(abs,'utf8') }; if (o.output) { var rel = root ? path.relative(root,abs) : file.filename; file.targetPath = path.resolve(o.output,rel); } else if (!o.stdio) { file.targetPath = file.sourcePath; }; if (file.targetPath) { var ext = ('.' + (o.outext || 'js')).replace(/^\.+/,'.'); file.targetPath = file.targetPath.replace(/\.imba\d?$/,ext); }; return coll.push(file); }; CLI.prototype.cwd = function (){ return process.cwd(); }; CLI.prototype.run = function (){ var self = this, o_, main_; (o_ = self.o()).platform || (o_.platform = 'node'); if (self.o().output) { self.o().output = path.normalize(path.resolve(self.cwd(),self.o().output)); }; var paths = ((typeof (main_ = self.o().main)=='string'||main_ instanceof String)) ? [self.o().main] : ((self.o().main || [])); for (let i = 0, items = iter$(paths), len = items.length; i < len; i++) { self.pathToSource(items[i],self._sources,self._options); }; if (self.o().stdio) { if (!self.o().output) { self.o().print = true }; var chunks = []; stdin.resume(); stdin.setEncoding('utf8'); stdin.on('data',function(chunk) { return chunks.push(chunk); }); stdin.on('end',function() { self.sources().push({sourcePath: 'stdin',sourceBody: chunks.join()}); return self.finish(); }); } else { self.finish(); }; return self; }; CLI.prototype.o = function (){ return this._options; }; CLI.prototype.traverse = function (cb){ for (let i = 0, items = iter$(this.sources()), len = items.length, src; i < len; i++) { src = items[i]; this._current = src; cb(src); }; return this; }; CLI.prototype.log = function (message){ if (!this.o().print) { console.log(message) }; return this; }; CLI.prototype.b = function (text){ return this.o().colors ? ansi.bold(text) : text; }; CLI.prototype.gray = function (text){ return this.o().colors ? ansi.gray(text) : text; }; CLI.prototype.red = function (text){ return this.o().colors ? ansi.red(text) : text; }; CLI.prototype.green = function (text){ return this.o().colors ? ansi.green(text) : text; }; CLI.prototype.yellow = function (text){ return this.o().colors ? ansi.yellow(text) : text; }; CLI.prototype.rel = function (src){ src = src.sourcePath || src; return path.relative(process.cwd(),src); }; CLI.prototype.present = function (data){ if (this.o().print) { process.stdout.write(data); }; return this; }; CLI.prototype.analyze = function (){ var self = this; return self.traverse(function(src) { var o2 = Object.create(self.o()); o2.sourcePath = src.sourcePath; o2.entities = true; var out = compiler.analyze(src.sourceBody,o2); src.analysis = out; return self.present(JSON.stringify(out,null,2)); }); }; CLI.prototype.tokenize = function (){ // should prettyprint tokens var self = this; return self.traverse(function(src) { var o2 = Object.create(self.o()); o2.sourcePath = src.sourcePath; o2.rewrite = self.o().rewrite; var out try { out = compiler.tokenize(src.sourceBody,o2); } catch(e){ console.log('error in tokenizer:',e.message); throw e; out = e._options.tokens; } src.tokens = out; let res = []; for (let i = 0, items = iter$(src.tokens), len = items.length, t; i < len; i++) { t = items[i]; var typ = t._type; var id = t._value; var s; if (typ == 'TERMINATOR') { s = "[" + self.b(id.replace(/\n/g,"\\n").replace(/\t/g,"\\t")) + "]\n"; } else if (typ == 'IDENTIFIER') { s = id; } else if (typ == 'NUMBER') { s = id; } else if (id == typ) { s = "[" + self.b(id) + "]"; } else { s = self.b(("[" + typ + " " + id + "]")); }; if (t._loc != -1 && self.o().sourcemap) { s = ("(" + (t._loc) + ":" + (t._len) + ")") + s; // chalk.bold(s) }; res.push(s); }; var strings = res; return process.stdout.write(strings.join(' ') + '\n'); }); }; CLI.prototype.compile = function (){ var self = this; return self.traverse(function(src) { return self.compileFile(src); }); }; CLI.prototype.compileFile = function (src){ var self = this; var opts = Object.create(self.o()); // opts.filename = src.filename; opts.sourcePath = src.sourcePath; if(opts.extlib) opts.imbaPath = null; var out = {}; var t = Date.now(); var at = new Date().toTimeString().substr(0,8); var srcp = self.o().stdio ? src.sourcePath : path.relative(process.cwd(),src.sourcePath); var dstp = src.targetPath && path.relative(process.cwd(),src.targetPath); var srcpAbs = path.resolve(srcp); var dstpAbs = path.resolve(dstp); if (srcp.indexOf("../") >= 0) { srcp = srcpAbs; dstp = dstpAbs; }; opts.sourcePath = srcpAbs; try { if (src.sourcePath.match(/\.imba1$/)) { imbac1 || (imbac1 = require("../script/bootstrap.compiler.js")); opts.filename = opts.sourcePath; out = imbac1.compile(src.sourceBody,opts); } else { out = compiler.compile(src.sourceBody,opts); if(opts.runtime == 'inline'){ out.js = fs.readFileSync(path.resolve(__dirname,'..','dist','imba.js'),'utf8') + '\n' + out.js; } }; } catch (e) { console.log('error',e); out = {errors: [e]}; }; if(out.errors && out.errors.length){ // out = { error: out.errors[0].toError() } } out.compileTime = Date.now() - t; let sourcemap = opts.sourcemap && out.sourcemap; if (sourcemap) { // TODO this should clearly happen inside the compiler?? if(opts.sourcemap == 'inline'){ // var base64 = new Buffer(JSON.stringify(sourcemap)).toString("base64"); // out.js = out.js + "\n//# sourceMappingURL=data:application/json;base64," + base64; } else if (opts.sourcemap && opts.sourcemap != 'external'){ out.js = out.js + "\n//# sourceMappingURL=" + path.basename(src.targetPath + '.map'); } }; src.output = out; let status = ("" + self.gray(("" + at + " compile")) + " " + srcp + " " + self.gray("to") + " " + dstp + " " + self.green(out.compileTime + "ms")); if (out.error) { status += self.red(" [1 error]"); }; if (out.warnings && out.warnings.length) { let count = out.warnings.length; status += self.yellow((" [" + count + " warning" + ((count > 1) ? 's' : '') + "]")); }; if (out.errors && out.errors.length) { let count = out.errors.length; status += self.red((" [" + count + " error" + ((count > 1) ? 's' : '') + "]")); if (!self.o().print) { self.log(status); out.errors.forEach(function(err,i){ if(err.toError instanceof Function){ err = err.toError(); } // log "{gray("{at} compile")} {srcp} {gray("to")} {dstp} {red(out:compileTime + "ms")}" if (err.excerpt) { self.log(" " + err.excerpt({colors: self.o().colors})); } else if(err instanceof SyntaxError && err.stack){ self.log(err.stack); } else { self.log(" " + err.message); self.log(" " + ("in file " + srcp)); if (err.stack) { self.log(err.stack) }; }; }) }; } else if (src.targetPath && out.js !== undefined && !self.o().print) { self.ensureDir(src.targetPath); let outString if (opts.bin) { let hashbang = '#! /usr/bin/env ' + opts.bin + '\n' outString = hashbang + out.js } else { outString = out.js } fs.writeFileSync(src.targetPath,outString,'utf8'); if (!self.o().print) { self.log(status) }; if(sourcemap && !opts.print && opts.sourcemap != 'inline') { // And webpack fs.writeFileSync(src.targetPath + '.map',JSON.stringify(sourcemap,null,2),'utf8'); } // log "{gray("{at} compile")} {srcp} {gray("to")} {dstp} {green(out:compileTime + "ms")}" }; if (out.warnings && !self.o().silent && !self.o().print) { // log " {out:warnings:length} warnings" out.warnings.forEach(function(err,i){ if(err.toError instanceof Function){ err = err.toError(); } // log "{gray("{at} compile")} {srcp} {gray("to")} {dstp} {red(out:compileTime + "ms")}" if (err.excerpt) { self.log(" " + err.excerpt({colors: self.o().colors})); } else if(err instanceof SyntaxError && err.stack){ self.log(err.stack); } else { self.log(" " + err.message); self.log(" " + ("in file " + srcp)); if (err.stack) { self.log(err.stack) }; }; }) // helpers }; if (self.o().watch && !src.watcher) { var now = Date.now(); src.watcher = fs.watch(src.sourcePath,function(type,filename) { if (type == 'change') { return setTimeout(function() { return fs.readFile(src.sourcePath,'utf8',function(err,body) { if (body != src.sourceBody) { src.sourceBody = body; return self.compileFile(src); }; }); },100); }; }); }; if (self.o().print && out.js) { process.stdout.write(out.js); }; return self; }; CLI.prototype.finish = function (){ try { if (this.o().analyze) { this.o().print = true; this.analyze(this.sources(),this.o()); } else if (this.o().tokenize) { this.o().print = true; this.tokenize(this.sources(),this.o()); } else { this.compile(this.sources(),this.o()); }; } catch (e) { if (this._current) { this.log(("ERROR in " + this.b(this.rel(this._current)))); }; if (e.excerpt) { this.log(e.excerpt({colors: true})); } else { throw e; }; }; return this; }; function main(){ var o = helpers.parseArgs(process.argv.slice(2),parseOpts); (o.colors == null) ? (o.colors = true) : o.colors; if (o.version) { return console.log(package.version); } else if (!(o.main || o.stdio) || o.help) { return console.log(help); } else { return new CLI(o).run(); }; }; main();