UNPKG

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