UNPKG

13.8 kBPlain TextView Raw
1#!/usr/bin/env node
2
3var path = require('path');
4var fs = require('fs');
5var package = require('../package.json');
6var compiler = require("../dist/compiler.cjs");
7var helpers = compiler.helpers;
8
9function iter$(a){ return a ? (a.toArray ? a.toArray() : a) : []; };
10
11var self = {};
12// imba$v2=0
13
14var imbac1;
15
16var stdin = process.stdin;
17var stdout = process.stdout;
18var ansi = helpers.ansi;
19
20
21var 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
50var help = `\n
51Usage: 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
70function 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
82CLI.prototype.sources = function(v){ return this._sources; }
83CLI.prototype.setSources = function(v){ this._sources = v; return this; };
84
85CLI.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
106CLI.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
139CLI.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
175CLI.prototype.cwd = function (){
176 return process.cwd();
177};
178
179CLI.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
208CLI.prototype.o = function (){
209 return this._options;
210};
211
212CLI.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
221CLI.prototype.log = function (message){
222 if (!this.o().print) { console.log(message) };
223 return this;
224};
225
226CLI.prototype.b = function (text){
227 return this.o().colors ? ansi.bold(text) : text;
228};
229
230CLI.prototype.gray = function (text){
231 return this.o().colors ? ansi.gray(text) : text;
232};
233
234CLI.prototype.red = function (text){
235 return this.o().colors ? ansi.red(text) : text;
236};
237
238CLI.prototype.green = function (text){
239 return this.o().colors ? ansi.green(text) : text;
240};
241
242CLI.prototype.yellow = function (text){
243 return this.o().colors ? ansi.yellow(text) : text;
244};
245
246CLI.prototype.rel = function (src){
247 src = src.sourcePath || src;
248 return path.relative(process.cwd(),src);
249};
250
251CLI.prototype.present = function (data){
252 if (this.o().print) {
253 process.stdout.write(data);
254 };
255 return this;
256};
257
258CLI.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
270CLI.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
318CLI.prototype.compile = function (){
319 var self = this;
320 return self.traverse(function(src) { return self.compileFile(src); });
321};
322
323CLI.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
480CLI.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
505function 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
519main();