1 | #!/usr/bin/env node
|
2 |
|
3 | var path = require('path');
|
4 | var fs = require('fs');
|
5 | var package = require('../package.json');
|
6 | var compiler = require("../dist/compiler.cjs");
|
7 | var helpers = compiler.helpers;
|
8 |
|
9 | function iter$(a){ return a ? (a.toArray ? a.toArray() : a) : []; };
|
10 |
|
11 | var self = {};
|
12 | // imba$v2=0
|
13 |
|
14 | var imbac1;
|
15 |
|
16 | var stdin = process.stdin;
|
17 | var stdout = process.stdout;
|
18 | var ansi = helpers.ansi;
|
19 |
|
20 |
|
21 | var parseOpts = {
|
22 | alias: {
|
23 | o: 'output',
|
24 | h: 'help',
|
25 | s: 'stdio',
|
26 | p: 'print',
|
27 | m: 'sourcemap',
|
28 | t: 'tokenize',
|
29 | v: 'version',
|
30 | w: 'watch',
|
31 | d: 'debug'
|
32 | },
|
33 |
|
34 | schema: {
|
35 | output: {type: 'string'},
|
36 | outext: {type: 'string'},
|
37 | sourcemap: {type: 'string'}, // inline | extern
|
38 | platform: {type: 'string'}, // node | browser | worker
|
39 | styles: {type: 'string'}, // extern | inline
|
40 | imbaPath: {type: 'string'}, // global | inline | import
|
41 | sourceId: {type: 'string'},
|
42 | bin: {type: 'string'},
|
43 | hmr: null,
|
44 | raw: null
|
45 | },
|
46 |
|
47 | groupz: ['sourcemap']
|
48 | };
|
49 |
|
50 | var help = `\n
|
51 | Usage: imbac [options] path/to/script.imba
|
52 |
|
53 | -h, --help display this help message
|
54 | -v, --version display the version number
|
55 | -w, --watch recompile files on change
|
56 | -m, --sourcemap generate source map and add inline to .js files
|
57 | -o, --output [dir] set the output directory for compiled JavaScript
|
58 | -p, --print print out the compiled JavaScript
|
59 | -d, --debug Enable compiler debugging
|
60 | -s, --stdio listen for and compile scripts over stdio
|
61 | -t, --tokenize print out the tokens that the lexer/rewriter produce
|
62 | --bin [bin] compile file with a hashbang: 'node', 'bun', etc.
|
63 | --platform [type] Specify platform: 'node', 'browser', 'worker'
|
64 | --styles [format] Specify how styles should be added: 'extern', 'inline'
|
65 | --silent only print out errors (skip warnings)
|
66 | --hmr compile for hot-module-reloading
|
67 | --analyze print out the scopes and variables of your script
|
68 | `;
|
69 |
|
70 | function CLI(options){
|
71 | if(options === undefined) options = {};
|
72 | this._options = options;
|
73 | this._sources = [];
|
74 | this._current = null;
|
75 | if(!options.fs) Object.defineProperty(options,'fs',{value: fs, enumerable:false});
|
76 | if(!options.path) Object.defineProperty(options,'path',{value: path, enumerable:false});
|
77 |
|
78 | this;
|
79 | };
|
80 |
|
81 |
|
82 | CLI.prototype.sources = function(v){ return this._sources; }
|
83 | CLI.prototype.setSources = function(v){ this._sources = v; return this; };
|
84 |
|
85 | CLI.prototype.ensureDir = function (src){
|
86 | if (fs.existsSync(src)) { return true };
|
87 | var parts = path.normalize(src).split(path.sep);
|
88 | for (let i = 0, items = iter$(parts), len = items.length, part; i < len; i++) {
|
89 | part = items[i];
|
90 | if (i < 1) { continue; };
|
91 | // what about relative paths here? no good? might be important for symlinks etc no?
|
92 | var dir = parts.slice(0,i + 1).join(path.sep);
|
93 |
|
94 | if (fs.existsSync(dir)) {
|
95 | var stat = fs.statSync(dir);
|
96 | } else if (part.match(/\.(imba\d?|js)$/) || i == len - 1) {
|
97 | true;
|
98 | } else {
|
99 | fs.mkdirSync(dir);
|
100 | console.log(ansi.green(("+ mkdir " + dir)));
|
101 | };
|
102 | };
|
103 | return;
|
104 | };
|
105 |
|
106 | CLI.prototype.findRecursive = function (root,pattern){
|
107 | var m;
|
108 | if(pattern === undefined) pattern = /\.imba\d?$/;
|
109 | var results = [];
|
110 | root = path.relative(process.cwd(),root);
|
111 | root = path.normalize(root);
|
112 |
|
113 | var read = function(src,depth) {
|
114 | src = path.normalize(src);
|
115 | var stat = fs.statSync(src);
|
116 |
|
117 | if (stat.isDirectory() && depth > 0) {
|
118 | var files = fs.readdirSync(src);
|
119 | let res = [];
|
120 | for (let i = 0, items = iter$(files), len = items.length; i < len; i++) {
|
121 | res.push(read(src + '/' + items[i],depth - 1));
|
122 | };
|
123 | return res;
|
124 | } else if (src.match(pattern)) {
|
125 | return results.push(src);
|
126 | };
|
127 | };
|
128 |
|
129 | if (m = root.match(/\/\*\.imba\d?$/)) {
|
130 | root = root.slice(0,-m[0].length);
|
131 | read(root,1);
|
132 | } else {
|
133 | read(root,10);
|
134 | };
|
135 |
|
136 | return results;
|
137 | };
|
138 |
|
139 | CLI.prototype.pathToSource = function (src,coll,o,root){
|
140 | if(root === undefined) root = null;
|
141 | var abs = path.resolve(process.cwd(),src);
|
142 | var stat = fs.statSync(abs);
|
143 |
|
144 | if (stat.isDirectory()) {
|
145 | // console.log "is directory",findRecursive(abs)
|
146 | var files = this.findRecursive(abs);
|
147 | for (let i = 0, items = iter$(files), len = items.length; i < len; i++) {
|
148 | this.pathToSource(items[i],coll,o,abs);
|
149 | };
|
150 | return;
|
151 | };
|
152 |
|
153 | var file = {
|
154 | filename: path.basename(src),
|
155 | sourcePath: abs,
|
156 | sourceBody: fs.readFileSync(abs,'utf8')
|
157 | };
|
158 |
|
159 | if (o.output) {
|
160 | var rel = root ? path.relative(root,abs) : file.filename;
|
161 | file.targetPath = path.resolve(o.output,rel);
|
162 | } else if (!o.stdio) {
|
163 | file.targetPath = file.sourcePath;
|
164 | };
|
165 |
|
166 | if (file.targetPath) {
|
167 | var ext = ('.' + (o.outext || 'js')).replace(/^\.+/,'.');
|
168 | file.targetPath = file.targetPath.replace(/\.imba\d?$/,ext);
|
169 | };
|
170 |
|
171 | return coll.push(file);
|
172 | };
|
173 |
|
174 |
|
175 | CLI.prototype.cwd = function (){
|
176 | return process.cwd();
|
177 | };
|
178 |
|
179 | CLI.prototype.run = function (){
|
180 | var self = this, o_, main_;
|
181 | (o_ = self.o()).platform || (o_.platform = 'node');
|
182 |
|
183 | if (self.o().output) {
|
184 | self.o().output = path.normalize(path.resolve(self.cwd(),self.o().output));
|
185 | };
|
186 |
|
187 | var paths = ((typeof (main_ = self.o().main)=='string'||main_ instanceof String)) ? [self.o().main] : ((self.o().main || []));
|
188 | for (let i = 0, items = iter$(paths), len = items.length; i < len; i++) {
|
189 | self.pathToSource(items[i],self._sources,self._options);
|
190 | };
|
191 |
|
192 | if (self.o().stdio) {
|
193 | if (!self.o().output) { self.o().print = true };
|
194 | var chunks = [];
|
195 | stdin.resume();
|
196 | stdin.setEncoding('utf8');
|
197 | stdin.on('data',function(chunk) { return chunks.push(chunk); });
|
198 | stdin.on('end',function() {
|
199 | self.sources().push({sourcePath: 'stdin',sourceBody: chunks.join()});
|
200 | return self.finish();
|
201 | });
|
202 | } else {
|
203 | self.finish();
|
204 | };
|
205 | return self;
|
206 | };
|
207 |
|
208 | CLI.prototype.o = function (){
|
209 | return this._options;
|
210 | };
|
211 |
|
212 | CLI.prototype.traverse = function (cb){
|
213 | for (let i = 0, items = iter$(this.sources()), len = items.length, src; i < len; i++) {
|
214 | src = items[i];
|
215 | this._current = src;
|
216 | cb(src);
|
217 | };
|
218 | return this;
|
219 | };
|
220 |
|
221 | CLI.prototype.log = function (message){
|
222 | if (!this.o().print) { console.log(message) };
|
223 | return this;
|
224 | };
|
225 |
|
226 | CLI.prototype.b = function (text){
|
227 | return this.o().colors ? ansi.bold(text) : text;
|
228 | };
|
229 |
|
230 | CLI.prototype.gray = function (text){
|
231 | return this.o().colors ? ansi.gray(text) : text;
|
232 | };
|
233 |
|
234 | CLI.prototype.red = function (text){
|
235 | return this.o().colors ? ansi.red(text) : text;
|
236 | };
|
237 |
|
238 | CLI.prototype.green = function (text){
|
239 | return this.o().colors ? ansi.green(text) : text;
|
240 | };
|
241 |
|
242 | CLI.prototype.yellow = function (text){
|
243 | return this.o().colors ? ansi.yellow(text) : text;
|
244 | };
|
245 |
|
246 | CLI.prototype.rel = function (src){
|
247 | src = src.sourcePath || src;
|
248 | return path.relative(process.cwd(),src);
|
249 | };
|
250 |
|
251 | CLI.prototype.present = function (data){
|
252 | if (this.o().print) {
|
253 | process.stdout.write(data);
|
254 | };
|
255 | return this;
|
256 | };
|
257 |
|
258 | CLI.prototype.analyze = function (){
|
259 | var self = this;
|
260 | return self.traverse(function(src) {
|
261 | var o2 = Object.create(self.o());
|
262 | o2.sourcePath = src.sourcePath;
|
263 | o2.entities = true;
|
264 | var out = compiler.analyze(src.sourceBody,o2);
|
265 | src.analysis = out;
|
266 | return self.present(JSON.stringify(out,null,2));
|
267 | });
|
268 | };
|
269 |
|
270 | CLI.prototype.tokenize = function (){
|
271 | // should prettyprint tokens
|
272 | var self = this;
|
273 | return self.traverse(function(src) {
|
274 | var o2 = Object.create(self.o());
|
275 | o2.sourcePath = src.sourcePath;
|
276 | o2.rewrite = self.o().rewrite;
|
277 | var out
|
278 | try {
|
279 | out = compiler.tokenize(src.sourceBody,o2);
|
280 | } catch(e){
|
281 | console.log('error in tokenizer:',e.message);
|
282 | throw e;
|
283 | out = e._options.tokens;
|
284 | }
|
285 |
|
286 | src.tokens = out;
|
287 |
|
288 | let res = [];
|
289 | for (let i = 0, items = iter$(src.tokens), len = items.length, t; i < len; i++) {
|
290 | t = items[i];
|
291 | var typ = t._type;
|
292 | var id = t._value;
|
293 | var s;
|
294 | if (typ == 'TERMINATOR') {
|
295 | s = "[" + self.b(id.replace(/\n/g,"\\n").replace(/\t/g,"\\t")) + "]\n";
|
296 | } else if (typ == 'IDENTIFIER') {
|
297 | s = id;
|
298 | } else if (typ == 'NUMBER') {
|
299 | s = id;
|
300 | } else if (id == typ) {
|
301 | s = "[" + self.b(id) + "]";
|
302 | } else {
|
303 | s = self.b(("[" + typ + " " + id + "]"));
|
304 | };
|
305 |
|
306 | if (t._loc != -1 && self.o().sourcemap) {
|
307 | s = ("(" + (t._loc) + ":" + (t._len) + ")") + s; // chalk.bold(s)
|
308 | };
|
309 |
|
310 | res.push(s);
|
311 | };
|
312 | var strings = res;
|
313 |
|
314 | return process.stdout.write(strings.join(' ') + '\n');
|
315 | });
|
316 | };
|
317 |
|
318 | CLI.prototype.compile = function (){
|
319 | var self = this;
|
320 | return self.traverse(function(src) { return self.compileFile(src); });
|
321 | };
|
322 |
|
323 | CLI.prototype.compileFile = function (src){
|
324 | var self = this;
|
325 | var opts = Object.create(self.o());
|
326 | // opts.filename = src.filename;
|
327 | opts.sourcePath = src.sourcePath;
|
328 |
|
329 | if(opts.extlib) opts.imbaPath = null;
|
330 |
|
331 | var out = {};
|
332 | var t = Date.now();
|
333 | var at = new Date().toTimeString().substr(0,8);
|
334 | var srcp = self.o().stdio ? src.sourcePath : path.relative(process.cwd(),src.sourcePath);
|
335 | var dstp = src.targetPath && path.relative(process.cwd(),src.targetPath);
|
336 |
|
337 | var srcpAbs = path.resolve(srcp);
|
338 | var dstpAbs = path.resolve(dstp);
|
339 |
|
340 | if (srcp.indexOf("../") >= 0) {
|
341 | srcp = srcpAbs;
|
342 | dstp = dstpAbs;
|
343 | };
|
344 |
|
345 | opts.sourcePath = srcpAbs;
|
346 |
|
347 | try {
|
348 | if (src.sourcePath.match(/\.imba1$/)) {
|
349 | imbac1 || (imbac1 = require("../script/bootstrap.compiler.js"));
|
350 | opts.filename = opts.sourcePath;
|
351 | out = imbac1.compile(src.sourceBody,opts);
|
352 | } else {
|
353 | out = compiler.compile(src.sourceBody,opts);
|
354 | if(opts.runtime == 'inline'){
|
355 | out.js = fs.readFileSync(path.resolve(__dirname,'..','dist','imba.js'),'utf8') + '\n' + out.js;
|
356 | }
|
357 | };
|
358 | } catch (e) {
|
359 | console.log('error',e);
|
360 | out = {errors: [e]};
|
361 | };
|
362 |
|
363 | if(out.errors && out.errors.length){
|
364 | // out = { error: out.errors[0].toError() }
|
365 | }
|
366 |
|
367 | out.compileTime = Date.now() - t;
|
368 |
|
369 | let sourcemap = opts.sourcemap && out.sourcemap;
|
370 |
|
371 | if (sourcemap) {
|
372 | // TODO this should clearly happen inside the compiler??
|
373 | if(opts.sourcemap == 'inline'){
|
374 | // var base64 = new Buffer(JSON.stringify(sourcemap)).toString("base64");
|
375 | // out.js = out.js + "\n//# sourceMappingURL=data:application/json;base64," + base64;
|
376 | } else if (opts.sourcemap && opts.sourcemap != 'external'){
|
377 | out.js = out.js + "\n//# sourceMappingURL=" + path.basename(src.targetPath + '.map');
|
378 | }
|
379 | };
|
380 |
|
381 | src.output = out;
|
382 |
|
383 | let status = ("" + self.gray(("" + at + " compile")) + " " + srcp + " " + self.gray("to") + " " + dstp + " " + self.green(out.compileTime + "ms"));
|
384 |
|
385 | if (out.error) {
|
386 |
|
387 | status += self.red(" [1 error]");
|
388 | };
|
389 |
|
390 | if (out.warnings && out.warnings.length) {
|
391 | let count = out.warnings.length;
|
392 | status += self.yellow((" [" + count + " warning" + ((count > 1) ? 's' : '') + "]"));
|
393 | };
|
394 |
|
395 | if (out.errors && out.errors.length) {
|
396 | let count = out.errors.length;
|
397 | status += self.red((" [" + count + " error" + ((count > 1) ? 's' : '') + "]"));
|
398 | if (!self.o().print) {
|
399 | self.log(status);
|
400 |
|
401 | out.errors.forEach(function(err,i){
|
402 | if(err.toError instanceof Function){
|
403 | err = err.toError();
|
404 | }
|
405 | // log "{gray("{at} compile")} {srcp} {gray("to")} {dstp} {red(out:compileTime + "ms")}"
|
406 | if (err.excerpt) {
|
407 | self.log(" " + err.excerpt({colors: self.o().colors}));
|
408 | } else if(err instanceof SyntaxError && err.stack){
|
409 | self.log(err.stack);
|
410 | } else {
|
411 | self.log(" " + err.message);
|
412 | self.log(" " + ("in file " + srcp));
|
413 | if (err.stack) { self.log(err.stack) };
|
414 | };
|
415 | })
|
416 | };
|
417 | }
|
418 | else if (src.targetPath && out.js !== undefined && !self.o().print) {
|
419 | self.ensureDir(src.targetPath);
|
420 | let outString
|
421 | if (opts.bin) {
|
422 | let hashbang = '#! /usr/bin/env ' + opts.bin + '\n'
|
423 | outString = hashbang + out.js
|
424 | } else {
|
425 | outString = out.js
|
426 | }
|
427 | fs.writeFileSync(src.targetPath,outString,'utf8');
|
428 | if (!self.o().print) { self.log(status) };
|
429 |
|
430 | if(sourcemap && !opts.print && opts.sourcemap != 'inline') { // And webpack
|
431 | fs.writeFileSync(src.targetPath + '.map',JSON.stringify(sourcemap,null,2),'utf8');
|
432 | }
|
433 | // log "{gray("{at} compile")} {srcp} {gray("to")} {dstp} {green(out:compileTime + "ms")}"
|
434 | };
|
435 |
|
436 | if (out.warnings && !self.o().silent && !self.o().print) {
|
437 | // log " {out:warnings:length} warnings"
|
438 | out.warnings.forEach(function(err,i){
|
439 | if(err.toError instanceof Function){
|
440 | err = err.toError();
|
441 | }
|
442 | // log "{gray("{at} compile")} {srcp} {gray("to")} {dstp} {red(out:compileTime + "ms")}"
|
443 | if (err.excerpt) {
|
444 | self.log(" " + err.excerpt({colors: self.o().colors}));
|
445 | } else if(err instanceof SyntaxError && err.stack){
|
446 | self.log(err.stack);
|
447 | } else {
|
448 | self.log(" " + err.message);
|
449 | self.log(" " + ("in file " + srcp));
|
450 | if (err.stack) { self.log(err.stack) };
|
451 | };
|
452 | })
|
453 | // helpers
|
454 | };
|
455 |
|
456 |
|
457 | if (self.o().watch && !src.watcher) {
|
458 | var now = Date.now();
|
459 | src.watcher = fs.watch(src.sourcePath,function(type,filename) {
|
460 | if (type == 'change') {
|
461 | return setTimeout(function() {
|
462 | return fs.readFile(src.sourcePath,'utf8',function(err,body) {
|
463 | if (body != src.sourceBody) {
|
464 | src.sourceBody = body;
|
465 | return self.compileFile(src);
|
466 | };
|
467 | });
|
468 | },100);
|
469 | };
|
470 | });
|
471 | };
|
472 |
|
473 |
|
474 | if (self.o().print && out.js) {
|
475 | process.stdout.write(out.js);
|
476 | };
|
477 | return self;
|
478 | };
|
479 |
|
480 | CLI.prototype.finish = function (){
|
481 | try {
|
482 | if (this.o().analyze) {
|
483 | this.o().print = true;
|
484 | this.analyze(this.sources(),this.o());
|
485 | } else if (this.o().tokenize) {
|
486 | this.o().print = true;
|
487 | this.tokenize(this.sources(),this.o());
|
488 | } else {
|
489 | this.compile(this.sources(),this.o());
|
490 | };
|
491 | } catch (e) {
|
492 | if (this._current) {
|
493 | this.log(("ERROR in " + this.b(this.rel(this._current))));
|
494 | };
|
495 | if (e.excerpt) {
|
496 | this.log(e.excerpt({colors: true}));
|
497 | } else {
|
498 | throw e;
|
499 | };
|
500 | };
|
501 | return this;
|
502 | };
|
503 |
|
504 |
|
505 | function main(){
|
506 | var o = helpers.parseArgs(process.argv.slice(2),parseOpts);
|
507 |
|
508 | (o.colors == null) ? (o.colors = true) : o.colors;
|
509 |
|
510 | if (o.version) {
|
511 | return console.log(package.version);
|
512 | } else if (!(o.main || o.stdio) || o.help) {
|
513 | return console.log(help);
|
514 | } else {
|
515 | return new CLI(o).run();
|
516 | };
|
517 | };
|
518 |
|
519 | main();
|