js2 0.1.8 → 0.3.0.pre5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. data/bin/js2 +9 -126
  2. data/bin/js2-ruby +13 -0
  3. data/lib/js2/command.rb +16 -0
  4. data/lib/js2/context.rb +35 -0
  5. data/lib/js2/fs.rb +56 -0
  6. data/lib/js2/js2.js +1265 -0
  7. data/lib/js2/rack.rb +35 -0
  8. data/lib/js2.rb +19 -30
  9. metadata +28 -91
  10. data/CHANGELOG +0 -17
  11. data/Manifest +0 -45
  12. data/README.md +0 -75
  13. data/Rakefile +0 -28
  14. data/config/js2.yml +0 -2
  15. data/js2.gemspec +0 -36
  16. data/lib/js2/parser/haml.rb +0 -145
  17. data/lib/js2/parser/haml_engine.rb +0 -19
  18. data/lib/js2/parser/lexer.rb +0 -37
  19. data/lib/js2/parser/tokenizer.rb +0 -3551
  20. data/lib/js2/ragel/helper.rb +0 -117
  21. data/lib/js2/ragel/tokenizer.rl +0 -561
  22. data/lib/js2/ragel/tokenizer.rl.erb +0 -347
  23. data/lib/js2/standard/class_node.rb +0 -0
  24. data/lib/js2/standard/factory.rb +0 -289
  25. data/lib/js2/standard/node.rb +0 -75
  26. data/lib/js2/util/compilation.rb +0 -77
  27. data/lib/js2/util/config.rb +0 -84
  28. data/lib/js2/util/exec.rb +0 -34
  29. data/lib/js2/util/file_handler.rb +0 -73
  30. data/lib/js2/util/haml_filter.rb +0 -13
  31. data/lib/js2/util/jamis.rb +0 -600
  32. data/lib/js2/util/js2bootstrap.js2 +0 -448
  33. data/lib/js2/util/processor.rb +0 -88
  34. data/lib/js2/util/rdoc.rb +0 -37
  35. data/lib/js2/util/sel_decorator.rb +0 -155
  36. data/test/compiled/bar.js +0 -3
  37. data/test/compiled/basic.comp.js +0 -31
  38. data/test/compiled/basic.js +0 -27
  39. data/test/compiled/foo.js +0 -3
  40. data/test/fixtures/bar.js2 +0 -3
  41. data/test/fixtures/basic.js2 +0 -27
  42. data/test/fixtures/basic.js2.haml +0 -4
  43. data/test/fixtures/basic.js2.yml +0 -5
  44. data/test/fixtures/curry.js2 +0 -5
  45. data/test/fixtures/foo.js2 +0 -3
  46. data/test/fixtures/member.js2 +0 -14
  47. data/test/fixtures/private.js2 +0 -5
  48. data/test/fixtures/property.js2 +0 -4
  49. data/test/test_helper.rb +0 -25
  50. data/test/test_js2.rb +0 -43
  51. data/wiki/features.md +0 -73
  52. data/wiki/installation.md +0 -13
data/lib/js2/js2.js ADDED
@@ -0,0 +1,1265 @@
1
+ (function (root) {
2
+ // temporarily set root
3
+ // to JS2 global var for this scope
4
+ function mainFunction (arg) {
5
+ if (typeof arg == 'string') {
6
+ return JS2.Parser.parse(arg).toString();
7
+ } else if (arg instanceof Array) {
8
+ return new JS2.Array(arg);
9
+ } else {
10
+ return new JS2.Array();
11
+ }
12
+ }
13
+
14
+
15
+ var JS2 = root.JS2 = mainFunction;
16
+ var js2 = root.js2 = JS2;
17
+
18
+ JS2.ROOT = JS2;
19
+
20
+ // CLASS HELPERS
21
+ (function (undefined, JS2) {
22
+
23
+ var OO = function (klass, par) {
24
+ this.klass = klass;
25
+ this.par = par;
26
+
27
+ this['static'] = {
28
+ methods: {},
29
+ members: {},
30
+ };
31
+
32
+ this.methods = {};
33
+ this.members = {};
34
+ this.children = [];
35
+
36
+ if (this.par) this.par.oo.children.push(klass);
37
+ };
38
+
39
+ OO.prototype = {
40
+ forbiddenMembers: {
41
+ 'prototype': undefined,
42
+ 'oo': undefined
43
+ },
44
+
45
+ createNamespace: function(name) {
46
+ var splitted = name.split('.');
47
+ var klassName = splitted.pop();
48
+ var root = JS2.ROOT;
49
+
50
+ while (splitted.length > 0) {
51
+ var name = splitted.shift();
52
+ if (!root[name]) root[name] = JS2.Class.extend({});
53
+ root = root[name];
54
+ }
55
+
56
+ return [ root, klassName ];
57
+ },
58
+
59
+ makeSuper: function(newMethod, oldMethod) {
60
+ if (!oldMethod) return newMethod;
61
+
62
+ return function() {
63
+ this.$super = oldMethod;
64
+ return newMethod.apply(this, arguments);
65
+ };
66
+ },
67
+
68
+ addMember: function(name, member) {
69
+ if (this.forbiddenMembers.hasOwnProperty(name)) return;
70
+
71
+ var proto = this.klass.prototype;
72
+ if (typeof proto[name] == 'function' && !(proto[name] instanceof RegExp)) {
73
+ member = this.makeSuper(member, proto[name]);
74
+ }
75
+
76
+ proto[name] = member;
77
+ },
78
+
79
+ addStaticMember: function(name, member) {
80
+ if (this.forbiddenMembers.hasOwnProperty(name)) return;
81
+
82
+ if (typeof this.klass[name] == 'function') {
83
+ if (!this.klass.hasOwnProperty(name)) {
84
+ member = this.makeSuper(member, this.klass[name]);
85
+ }
86
+ }
87
+
88
+ this.klass[name] = member;
89
+ }
90
+ };
91
+
92
+ JS2.Class = function() { this.initialize.apply(this, arguments); };
93
+ JS2.Class.oo = new OO(JS2.Class);
94
+ JS2.Class.prototype = {
95
+ initialize: function () {},
96
+ oo: JS2.Class.oo
97
+ };
98
+
99
+ var namedClasses = {};
100
+ JS2.getClass = function(name) {
101
+ return namedClasses[name];
102
+ };
103
+
104
+ var noInit = false;
105
+ JS2.Class.extend = function(name, klassDef) {
106
+ var klass = function() { if (!noInit) this.initialize.apply(this, arguments); };
107
+ klass.oo = new OO(klass, this);
108
+
109
+ if (typeof name != 'string') {
110
+ klassDef = name;
111
+ } else {
112
+ namedClasses[name] = klass;
113
+ var namespace = this.oo.createNamespace(name);
114
+ namespace[0][namespace[1]] = klass;
115
+ }
116
+
117
+ // create instance of this as prototype for new this
118
+ noInit = true;
119
+ var proto = new this();
120
+ noInit = false;
121
+
122
+ klass.prototype = proto;
123
+ var oo = klass.oo;
124
+ proto.oo = oo;
125
+
126
+ for (var name in klassDef) {
127
+ oo.addMember(name, klassDef[name]);
128
+ }
129
+
130
+ for (var name in this) {
131
+ oo.addStaticMember(name, this[name]);
132
+ }
133
+
134
+ return klass;
135
+ };
136
+
137
+ var assert = {
138
+ 'eq': function(val, expected) { if (expected != val) console.log("Expected "+expected+", but got "+val+".") },
139
+ 'isFalse': function(val) { if (val) console.log("Expected false, but got "+val+".") },
140
+ 'isTrue': function(val) { if (!val) console.log("Expected true, but got " +val+".") }
141
+ };
142
+
143
+ JS2.test = function(message, callback) {
144
+ if (!callback) callback = message;
145
+ callback(assert);
146
+ };
147
+
148
+
149
+ return JS2;
150
+ })(undefined, JS2);
151
+
152
+ (function (undefined, JS2) {
153
+ var TOKENS = [
154
+ [ 'COMMENT', "\\/\\/|/\\*" ],
155
+ [ 'SPACE', "\\s+" ],
156
+ [ 'REGEX', "\\/" ],
157
+ [ 'CLASS', "class" ],
158
+ [ 'SHORT_FUNCT', "#\\{|#\\(" ],
159
+ [ 'FOREACH', "foreach" ],
160
+ [ 'CURRY', "curry" ],
161
+ [ 'IDENT', "[\\w$]+" ],
162
+ [ 'DSTRING', '"' ],
163
+ [ 'SSTRING', "'" ],
164
+ [ 'ISTRING', "%\\{" ],
165
+ [ 'HEREDOC', "<<-?\\w+" ],
166
+ [ 'OPERATOR', "[^\\w]" ]
167
+ ];
168
+
169
+ var IDS = {};
170
+ var REGEX_TOKENS = [];
171
+ for (var i=0,token; token=TOKENS[i]; i++) {
172
+ IDS[token[0]] = i;
173
+ REGEX_TOKENS.push("(" + token[1] + ")");
174
+ }
175
+
176
+ var PRIMARY_REGEX = new RegExp("^(" + REGEX_TOKENS.join('|') + ")");
177
+
178
+ JS2.Class.extend('Lexer', {
179
+ TOKENS: TOKENS,
180
+ PRIMARY_REGEX: PRIMARY_REGEX,
181
+ IDS: IDS,
182
+
183
+ initialize: function(str) {
184
+ this.tokens = (typeof str == 'string') ? new JS2.Lexer.Tokens(str) : str;
185
+ },
186
+
187
+ tokenize: function(root) {
188
+ if (root) {
189
+ var m = this.tokens.match(/^#!.*/);
190
+ if (m) this.tokens.chomp(m[0].length);
191
+ }
192
+
193
+ while (!this.tokens.finished()) {
194
+ if (! this.consume()) {
195
+ if (root) {
196
+ console.log("ERROR:\n" + this.tokens.toArray().join("\n") + "\n" + this.tokens.str);
197
+ break;
198
+ } else {
199
+ return false;
200
+ }
201
+ }
202
+ }
203
+ return this.tokens;
204
+ },
205
+
206
+ consume: function() {
207
+ var m = this.tokens.match(PRIMARY_REGEX)
208
+ if (!m) return false;
209
+
210
+ for (var i=0,tokenDef;tokenDef=this.TOKENS[i];i++) {
211
+ if (m[0] == m[i+2]) {
212
+ var klass = JS2.Lexer[tokenDef[0]];
213
+ if (klass) {
214
+ var lexer = new klass(this.tokens);
215
+ if (lexer.consume()) {
216
+ return true;
217
+ }
218
+ } else {
219
+ this.tokens.push([ m[0], i ]);
220
+ this.tokens.chomp(m[0]);
221
+ return true;
222
+ }
223
+ }
224
+ }
225
+ }
226
+ });
227
+
228
+ JS2.Lexer.IDS = IDS;
229
+ JS2.Lexer.extend('Lexer.REGEX', {
230
+ REGEX: /^\/(?!\s)[^[\/\n\\]*(?:(?:\\[\s\S]|\[[^\]\n\\]*(?:\\[\s\S][^\]\n\\]*)*])[^[\/\n\\]*)*\/[imgy]{0,4}(?!\w)/,
231
+ ID: IDS.REGEX,
232
+
233
+ consume: function() {
234
+ return this.consumeRegex(this.REGEX);
235
+ },
236
+
237
+ consumeRegex: function(regex) {
238
+ var m = this.tokens.match(regex);
239
+
240
+ if (m) {
241
+ this.tokens.push([ m[0], this.ID ]);
242
+ return this.tokens.chomp(m[0].length);
243
+ }
244
+
245
+ return false;
246
+ }
247
+ });
248
+
249
+ JS2.Lexer.extend('Lexer.SHORT_FUNCT', {
250
+ ID: IDS.SHORT_FUNCT,
251
+ consume: function() {
252
+ this.tokens.chomp(1);
253
+ this.tokens.push([ '#', this.ID ]);
254
+ return true;
255
+ }
256
+ });
257
+
258
+
259
+ JS2.Lexer.REGEX.extend('Lexer.SSTRING', {
260
+ REGEX: /^'[^\\']*(?:\\.[^\\']*)*'/,
261
+ ID: IDS.SSTRING
262
+ });
263
+
264
+ JS2.Lexer.REGEX.extend('Lexer.DSTRING', {
265
+ REGEX: /^"[^\\"]*(?:\\.[^\\"]*)*"/,
266
+ ID: IDS.DSTRING
267
+ });
268
+
269
+ JS2.Lexer.REGEX.extend('Lexer.ISTRING', {
270
+ REGEX_NEXT: /^((\\#|[^#])*?)(#{|})/,
271
+ REGEX: /^%\{/,
272
+ ID: IDS.ISTRING,
273
+ sanitize: function(str) {
274
+ return str.replace('"', '\\"');
275
+ },
276
+ consume: function() {
277
+ var m = this.tokens.match(this.REGEX);
278
+ if (!m) return false;
279
+ this.tokens.chomp(2);
280
+
281
+ // not really ends...
282
+ var toEnd = false;
283
+ while (1) {
284
+ var m = this.tokens.match(this.REGEX_NEXT);
285
+ if (m) {
286
+ var matched = m[1];
287
+ if (m[3] == '#{') {
288
+ this.tokens.push([ '"' + this.sanitize(matched) + '"+(', this.ID ]);
289
+ this.tokens.chomp(m[0].length-1);
290
+ var block = new JS2.Lexer.Block(this.tokens);
291
+ block.tokenize();
292
+ this.tokens.push([ ')+', this.ID ]);
293
+ toEnd = true;
294
+ } else if (m[3] == '}' || m[0] == '}') {
295
+ this.tokens.push([ '"' + this.sanitize(matched) + '"', this.ID ]);
296
+ this.tokens.chomp(m[0].length);
297
+ break;
298
+ }
299
+ } else {
300
+ break;
301
+ }
302
+ }
303
+ return true;
304
+ }
305
+ });
306
+
307
+ JS2.Lexer.ISTRING.extend('Lexer.HEREDOC', {
308
+ REGEX_NEXT: /^((\\#|[^#])*?)(#{|\r?\n)/,
309
+ REGEX: /^<<\-?(\w+)\r?\n/m,
310
+ ID: IDS.HEREDOC,
311
+ consume: function() {
312
+ var m = this.tokens.match(this.REGEX);
313
+ if (!m) return false;
314
+
315
+ this.tokens.chomp(m[0].length);
316
+ this.tokens.push([ "\n", IDS.SPACE ]);
317
+
318
+ var mIndent = this.tokens.match(/^(\s*)([^\s])/m);
319
+ var spacing = mIndent[1];
320
+ var nspace = mIndent[1].length;
321
+ var ender = new RegExp("^\\s*" + m[1] + "(\\r?\\n)?");
322
+
323
+ var first = true;
324
+ var noChomp = false;
325
+
326
+ while (1) {
327
+ var e = this.tokens.match(ender);
328
+ if (e) {
329
+ this.tokens.chomp(e[0].length);
330
+ this.tokens.push([ ';', IDS.DSTRING ]);
331
+ return true;
332
+ }
333
+
334
+ if (noChomp) {
335
+ noChomp = false;
336
+ } else {
337
+ this.tokens.chomp(nspace);
338
+ }
339
+
340
+ var next = this.tokens.match(this.REGEX_NEXT);
341
+ if (next) {
342
+ if (next[1]) {
343
+ this.tokens.chomp(next[1].length);
344
+ this.tokens.push([ (first ? '' : '+') + '"' + this.sanitize(next[1]) + '\\\\n"', IDS.DSTRING ]);
345
+ }
346
+
347
+ if (next[3] == '#{') {
348
+ this.tokens.chomp(1);
349
+ this.tokens.push([ '+(', IDS.DSTRING ]);
350
+ var block = new JS2.Lexer.Block(this.tokens);
351
+ block.tokenize();
352
+ this.tokens.push([ ')', IDS.DSTRING ]);
353
+ noChomp = true;
354
+ } else {
355
+ this.tokens.chomp(next[3].length);
356
+ }
357
+ }
358
+ first = false;
359
+ }
360
+ return true;
361
+ }
362
+ });
363
+
364
+
365
+ JS2.Lexer.extend('Lexer.Block', {
366
+ initialize: function(tokens) {
367
+ this.$super(tokens);
368
+ this.started = false;
369
+ },
370
+
371
+ consume: function() {
372
+ if (! this.started) {
373
+ this.started = true;
374
+ this.tokens.chomp(1);
375
+ this.curlyCount = 1;
376
+ return true;
377
+ } else if (this.tokens.str.charAt(0) == '{') {
378
+ this.curlyCount++;
379
+ } else if (this.tokens.str.charAt(0) == '}') {
380
+ this.curlyCount--;
381
+ }
382
+
383
+ if (this.curlyCount == 0) {
384
+ this.tokens.chomp(1);
385
+ return false;
386
+ } else {
387
+ this.$super();
388
+ return true;
389
+ }
390
+ }
391
+ });
392
+
393
+ JS2.Lexer.extend('Lexer.COMMENT', {
394
+ ID: IDS.COMMENT,
395
+ consume: function() {
396
+ var m = this.tokens.match(/^\/\/.*/);
397
+ if (m) {
398
+ this.tokens.push([ m[0], IDS.COMMENT ]);
399
+ this.tokens.chomp(m[0].length);
400
+ return true;
401
+ }
402
+
403
+ var str = this.tokens.str;
404
+ var mode = 0;
405
+ for (var i=0; i<str.length; i++) {
406
+ if (str.charAt(i) == '*') {
407
+ mode++;
408
+ } else if (str.charAt(i) == '/' && mode == 1) {
409
+ mode++;
410
+ } else {
411
+ mode = 0;
412
+ }
413
+
414
+ if (mode == 2) {
415
+ this.tokens.push([ str.substr(0, i+1), IDS.COMMENT ]);
416
+ this.tokens.chomp(i+1);
417
+ return true;
418
+ }
419
+ }
420
+ return false;
421
+ }
422
+ });
423
+
424
+
425
+
426
+
427
+ JS2.Class.extend('Lexer.Tokens', {
428
+ initialize: function(str) {
429
+ this.curlyCount = 0;
430
+ this.braceCount = 0;
431
+ this.tokens = [];
432
+ this.index = 0;
433
+ this.str = str;
434
+ this.orig = str;
435
+ },
436
+
437
+ toArray: function() {
438
+ return this.tokens;
439
+ },
440
+
441
+ match: function(regex) {
442
+ return this.str.match(regex);
443
+ },
444
+
445
+ compare: function(str) {
446
+ return this.str.substr(0, str.length) == str;
447
+ },
448
+
449
+ // stuff can be string, integer, or regex
450
+ // will return true if it actually made the string
451
+ // smaller
452
+ chomp: function(stuff) {
453
+ var len = this.str.length;
454
+ if (typeof stuff == 'number') {
455
+ this.str = this.str.substr(stuff);
456
+ } else if (typeof stuff == 'string') {
457
+ this.str = this.str.substr(stuff.length);
458
+ } else if (stuff instanceof RegExp) {
459
+ var m = this.str.match(stuff);
460
+ if (m) {
461
+ this.str = this.str.substr(m[0].length);
462
+ }
463
+ }
464
+ return len > this.str.length;
465
+ },
466
+
467
+ finished: function(token) {
468
+ return this.str.length == 0;
469
+ },
470
+
471
+ push: function(token) {
472
+ this.tokens.push(token);
473
+ },
474
+
475
+ pop: function() {
476
+ return this.tokens.pop();
477
+ },
478
+
479
+ peek: function() {
480
+ return this.tokens[0];
481
+ },
482
+
483
+ shift: function() {
484
+ var token = this.tokens.shift();
485
+ var str = token[0];
486
+ switch(str) {
487
+ case '{': this.curlyCount++; break;
488
+ case '}': this.curlyCount--; break;
489
+ case '(': this.braceCount++; break;
490
+ case ')': this.braceCount--; break;
491
+ }
492
+ return token;
493
+ },
494
+
495
+ freeze: function(obj) {
496
+ obj.curlyCount = this.curlyCount;
497
+ obj.braceCount = this.braceCount;
498
+ },
499
+
500
+ isBalancedCurly: function(obj) {
501
+ return obj.curlyCount == this.curlyCount;
502
+ },
503
+
504
+ isBalancedBrace: function(obj) {
505
+ return obj.braceCount == this.braceCount;
506
+ },
507
+
508
+ empty: function() {
509
+ return this.tokens.length <= 0;
510
+ },
511
+
512
+ charAt: function(n) {
513
+ return this.str.charAt(n);
514
+ },
515
+
516
+ indexOf: function(n) {
517
+ return this.str.indexOf(n);
518
+ }
519
+
520
+ });
521
+ })(undefined, JS2);
522
+
523
+ (function (undefined, JS2) {
524
+ JS2.Parser = {
525
+ parse: function(str) {
526
+ var lexer = new JS2.Lexer(str);
527
+ var tokens = lexer.tokenize(true);
528
+ var root = new Content(tokens);
529
+ return root;
530
+ },
531
+
532
+ parseFile: function(file) {
533
+ return this.parse(js2.fs.read(file, 'utf8'));
534
+ }
535
+ };
536
+
537
+ var KEYWORDS = { 'var': null, 'class': null, 'function': null, 'in': null, 'with': null, 'curry': null};
538
+ var IDS = JS2.Lexer.IDS;
539
+ IDS['NODE'] = -1;
540
+
541
+ var Validator = JS2.Class.extend({
542
+ initialize: function(content) {
543
+ this.content = content;
544
+ },
545
+
546
+ validate: function(regex, n) {
547
+ var str = this.getString(n);
548
+ var m = regex.exec(str);
549
+ if (!m) return false;
550
+
551
+ var ret = [ m ];
552
+ var cIdx = 0;
553
+ for (var i=1; i<=m.length; i++) {
554
+ while (this.content[cIdx] && this.content[cIdx][1] == IDS.COMMENT) cIdx++;
555
+
556
+ if (!m[i] || m[i].length == 0) {
557
+ ret.push('');
558
+ } else {
559
+ ret.push(this.content[cIdx++][0]);
560
+ }
561
+ }
562
+
563
+ if (n == null) {
564
+ var last = [];
565
+ while (cIdx<this.content.length) {
566
+ last.push(this.content[cIdx++][0].toString());
567
+ }
568
+
569
+ ret.last = last.join('');
570
+ }
571
+ return ret;
572
+ },
573
+
574
+ getString: function(n) {
575
+ n = n ? n : this.content.length;
576
+ var ret = [];
577
+
578
+ for (var i=0; i<n; i++) {
579
+ var token = this.content[i];
580
+ if (! token) break;
581
+ var str = this.getTokenString(token);
582
+ if (str != null) ret.push(str);
583
+ }
584
+
585
+ return ret.join('');
586
+ },
587
+
588
+ getTokenString: function(token) {
589
+ if (token[1] == IDS.COMMENT) {
590
+ return null;
591
+ } else if (token[0] in KEYWORDS) {
592
+ return token[0];
593
+ } else if (token[1] == IDS.SPACE) {
594
+ return token[0];
595
+ } else if (token[1] == IDS.IDENT) {
596
+ return 'I';
597
+ } else if (typeof token[0] == 'object') {
598
+ return token[0].name;
599
+ } else if (typeof token[0] == 'string') {
600
+ return token[0];
601
+ }
602
+ }
603
+ });
604
+
605
+ var Content = JS2.Class.extend({
606
+ name: 'Content',
607
+ initialize: function(tokens) {
608
+ this.curlyCount = tokens.curlyCount;
609
+ this.braceCount = tokens.braceCount;
610
+ this.content = [];
611
+ this.tokens = tokens;
612
+ this.handOffs = [];
613
+
614
+ this.processTokens(tokens);
615
+ },
616
+
617
+ processTokens: function() {
618
+ while (!this.tokens.empty() && !this.closed) {
619
+ var token = this.tokens.peek();
620
+ var klass = this.handOff(token);
621
+ if (this.closed) break;
622
+
623
+ if (klass) {
624
+ this.handOffs.push(this.newNode(klass, this.tokens));
625
+ } else {
626
+ this.content.push(token);
627
+ this.handleToken(this.tokens.shift());
628
+ }
629
+
630
+ if (this.closed) break;
631
+ }
632
+ },
633
+
634
+ handleToken: function(token) {},
635
+
636
+ newNode: function(klass, tokens) {
637
+ var node = new klass(tokens);
638
+ this.content.push([ node, IDS.NODE ]);
639
+ return node;
640
+ },
641
+
642
+ validate: function(regex) {
643
+ return (new Validator(this.content)).validate(regex);
644
+ },
645
+
646
+ getValidateString: function() {
647
+ return (new Validator(this.content)).getString();
648
+ },
649
+
650
+ handOff: function(token) {
651
+ switch (token[1]) {
652
+ case IDS.CLASS: return Klass;
653
+ case IDS.FOREACH: return Foreach;
654
+ case IDS.SHORT_FUNCT: return ShortFunct;
655
+ case IDS.CURRY: return Curry;
656
+ }
657
+ },
658
+
659
+ toString: function() {
660
+ var ret = [];
661
+ for (var i=0; i<this.content.length; i++) {
662
+ ret.push(this.content[i][0].toString());
663
+ }
664
+ return ret.join('');
665
+ }
666
+ });
667
+
668
+ var Klass = Content.extend({
669
+ name: 'Klass',
670
+ handOff: function(token) {
671
+ if (this.started) this.closed = true;
672
+ switch (token[0]) {
673
+ case '{': this.started = true; return KlassBlock;
674
+ }
675
+ },
676
+
677
+ toString: function() {
678
+ var v = this.validate(/(class)(\s+)/);
679
+ var last = v.last;
680
+ var m = last.match(/^([\w$]+(\.[\w$]+)*)(\s+extends\s+([\w$]+(\.?[\w$]+))*)?/);
681
+
682
+ var name = m[1];
683
+ var par = m[4] || 'JS2.Class';
684
+ var source = last.substr(m[0].length);
685
+
686
+ return JS2.DECORATOR.klass(name, par, source);
687
+ }
688
+ });
689
+
690
+ var Block = Content.extend({
691
+ name: 'Block',
692
+ handleToken: function(token) {
693
+ if (this.tokens.isBalancedCurly(this) && token[0] == '}') {
694
+ this.closed = true;
695
+ }
696
+ }
697
+ });
698
+
699
+ var KlassBlock = Block.extend({
700
+ name: 'KlassBlock',
701
+ handOff: function(token) {
702
+ switch (token[0]) {
703
+ case 'var': return Member;
704
+ case 'function': return Method;
705
+ }
706
+ },
707
+
708
+ handleToken: function(token) {
709
+ if (this.tokens.isBalancedCurly(this) && token[0] == '}') {
710
+ this.closed = true;
711
+ }
712
+ },
713
+
714
+ toString: function() {
715
+ var str = this.$super();
716
+ return str.replace(/,(\s+\})$/, "$1");
717
+ }
718
+ });
719
+
720
+ var Method = Content.extend({
721
+ name: 'Method',
722
+ handOff: function(token) {
723
+ if (this.started) this.closed = true;
724
+ if (token[0] == '(') {
725
+ return Braces;
726
+ } else if (token[0] == '{') {
727
+ this.started = true;
728
+ return Block;
729
+ }
730
+ },
731
+
732
+ toString: function () {
733
+ var v = this.validate(/^(function)(\s+)(I)(\s*)(Braces)(\s*)(Block)/);
734
+ return v[3] + ':' + "function" + v[2] + v[5] + ' ' + v[7] + ',';
735
+ }
736
+ });
737
+
738
+ var Member = Content.extend({
739
+ name: 'Member',
740
+ handleToken: function(token) {
741
+ if (token[0] == ';') this.closed = true;
742
+ },
743
+
744
+ toString: function () {
745
+ var v = this.validate(/(var)(\s+)(I)(\s*)(=)?(\s*)/);
746
+ var last = v.last.replace(/;$/, '');
747
+ if (last.length == 0) last = 'null';
748
+
749
+ return '"' + v[3] + '":' + last + ',';
750
+ }
751
+ });
752
+
753
+
754
+
755
+ var Braces = Content.extend({
756
+ name: 'Braces',
757
+ handleToken: function(token) {
758
+ if (this.tokens.isBalancedBrace(this) && token[0] == ')') {
759
+ this.closed = true;
760
+ }
761
+ }
762
+ });
763
+
764
+ var Foreach = Content.extend({
765
+ cache: { count: 1 },
766
+ name: 'Foreach',
767
+ handOff: function(token) {
768
+ if (this.started) {
769
+ this.closed = true;
770
+ }
771
+ switch (token[0]) {
772
+ case '(': return Braces;
773
+ case '{': this.started = true; return Block;
774
+ }
775
+ },
776
+
777
+ toString: function() {
778
+ var v = this.validate(/(foreach)(\s*)(Braces)(\s*)(Block)/);
779
+ return "for" + this.getBrace(v[3]) + v[5].toString();
780
+ },
781
+
782
+ getBrace: function(brace) {
783
+ var n = this.cache.count++;
784
+ var iteratorName = "_i" + n;
785
+ var collectionName = "_c" + n;
786
+ var l = "_l" + n;
787
+
788
+ var v = brace.validate(/(\()(\s*)(var)(\s+)(I)(\s+)(in)(\s+)/);
789
+ if (!v) return '';
790
+
791
+ var holder = v[5];
792
+ var collection = v.last.replace(/\)$/, '');
793
+
794
+ return "(var " + iteratorName + "=0," +
795
+ collectionName + "=" + collection + "," +
796
+ l + "=" + collectionName + ".length," +
797
+ holder + ";" +
798
+ holder + '=' + collectionName + '[' + iteratorName + ']||' +
799
+ iteratorName + '<' + l + ';' +
800
+ iteratorName + '++)';
801
+ }
802
+ });
803
+
804
+ var ShortFunct = Content.extend({
805
+ name: "ShortFunct",
806
+ handOff: function(token) {
807
+ if (this.started) {
808
+ this.closed = true;
809
+ var foo = (new Validator(this.tokens.toArray())).getString(2);
810
+ this.semi = (new Validator(this.tokens.toArray())).validate(/^(\s*)([^\s\w$])/, 2) ? '' : ';';
811
+ }
812
+
813
+ switch (token[0]) {
814
+ case '(': return Braces;
815
+ case '{': this.started = true; return Block;
816
+ }
817
+ },
818
+
819
+ toString: function() {
820
+ var v = this.validate(/(#)(Braces)?(\s*)(Block)/);
821
+ return "function" + (v[2] ? v[2] : "($1,$2,$3)") + v[4] + this.semi;
822
+ }
823
+ });
824
+
825
+ var Curry = Content.extend({
826
+ name: "Curry",
827
+ handOff: function(token) {
828
+ if (this.started) {
829
+ var v = (new Validator(this.tokens.toArray())).validate(/^(\s*)([\w$]+)/, 2);
830
+ if (v) this.addSemiColon = true;
831
+ this.closed = true;
832
+ }
833
+
834
+ if (this.nbraces == null) this.nbraces = 0;
835
+
836
+ switch (token[0]) {
837
+ case '(': return Braces;
838
+ case '{': this.started = true; return Block;
839
+ }
840
+ },
841
+
842
+ toString: function() {
843
+ var v = this.validate(/(curry)(\s*)(Braces)?(\s*)(with)?(\s*)(Braces)?(\s*)(Block)/);
844
+ var ret = [ '(function(){return function' ];
845
+
846
+ // args
847
+ ret.push(v[3] ? v[3].toString() : '($1,$2,$3)');
848
+
849
+ // block
850
+ ret.push(v[9].toString());
851
+
852
+ // close outer block
853
+ ret.push("})");
854
+
855
+ // scope
856
+ ret.push(v[5] ? v[7].toString() : "()");
857
+
858
+ if (this.addSemiColon) ret.push(';');
859
+
860
+ return ret.join('');
861
+ }
862
+ });
863
+
864
+ JS2.require = function(file) {
865
+ var str = JS2.Parser.parseFile(file + '.js2').toString();
866
+ eval(str);
867
+ }
868
+
869
+ JS2.parse = function(str) { return this.Parser.parse(str); };
870
+ JS2.parseFile = function(file) { return this.Parser.parseFile(file); };
871
+ JS2.render = function(str) { return this.parse(str).toString(); };
872
+ JS2.renderFile = function(file) { return this.parseFile(file).toString(); };
873
+
874
+ })(undefined, JS2);
875
+ JS2.Array = function (arr) {
876
+ if (arr instanceof Array) {
877
+ this.append(arr);
878
+ }
879
+ };
880
+
881
+ JS2.Array.prototype = new Array();
882
+ JS2.Array.prototype.each = function(f) {
883
+ for (var i=0; i<this.length; i++) {
884
+ f.call(this, this[i], i );
885
+ }
886
+ return this;
887
+ };
888
+
889
+ JS2.Array.prototype.toString = function() {
890
+ return this.join(',');
891
+ };
892
+
893
+
894
+ JS2.Array.prototype.until = function(f) {
895
+ for (var i=0; i<this.length; i++) {
896
+ if (f.call(this, this[i], i )) return true;;
897
+ }
898
+ return false;
899
+ };
900
+
901
+
902
+ JS2.Array.prototype.collect = function(f) {
903
+ var ret = new JS2.Array();
904
+ this.each(function($1,$2,$3){ ret.push(f.call(this, $1, $2)) });
905
+ return ret;
906
+ };
907
+
908
+ JS2.Array.prototype.reduce = function(f, val) {
909
+ this.each(function($1,$2,$3){ val = f.call(this, $1, val) });
910
+ };
911
+
912
+ JS2.Array.prototype.reject = function(f) {
913
+ var ret = new JS2.Array();
914
+ if (f instanceof RegExp) {
915
+ this.each(function($1,$2,$3){ if (!$1.match(f)) ret.push($1) });
916
+ } else if (typeof f == 'string' || typeof f == 'number') {
917
+ this.each(function($1,$2,$3){ if ($1 != f) ret.push($1) });
918
+ } else if (typeof f == 'function') {
919
+ this.each(function($1,$2,$3){ if (!f.call(this, $1, $2)) ret.push($1) });
920
+ }
921
+ return ret;
922
+ };
923
+
924
+ JS2.Array.prototype.select = function(f) {
925
+ var ret = new JS2.Array();
926
+ if (f instanceof RegExp) {
927
+ this.each(function($1,$2,$3){ if ($1.match(f)) ret.push($1) });
928
+ } else if (typeof f == 'string' || typeof f == 'number') {
929
+ this.each(function($1,$2,$3){ if ($1 == f) ret.push($1) });
930
+ } else if (typeof f == 'function') {
931
+ this.each(function($1,$2,$3){ if (f.call(this, $1, $2)) ret.push($1) });
932
+ }
933
+ return ret;
934
+ };
935
+
936
+ JS2.Array.prototype.append = function(arr) {
937
+ this.push.apply(this, arr);
938
+ return this;
939
+ };
940
+
941
+ JS2.Array.prototype.empty = function() {
942
+ return this.length == 0;
943
+ };
944
+
945
+ JS2.Array.prototype.any = function() {
946
+ return this.length > 0;
947
+ };
948
+
949
+
950
+ JS2.Class.extend('FileSystem', {
951
+ initialize:function (adapter) {
952
+ this.adapter = adapter;
953
+ },
954
+
955
+ find:function (dir, ext, recursive) {
956
+ return this._find(this.expandPath(dir), new RegExp('\\.' + ext + '$'), recursive);
957
+ },
958
+
959
+ _find:function (dir, regex, recursive) {
960
+ if (!this.isDirectory(dir)) return [];
961
+
962
+ var parts = this.adapter.readdir(dir);
963
+
964
+ var files = js2();
965
+ var self = this;
966
+
967
+ js2(parts).reject(/^\.\.?$/).each(function($1,$2,$3){
968
+ var file = dir + '/' + $1;
969
+ if (self.isFile(file) && file.match(regex)) {
970
+ files.push(file);
971
+ } else if (self.isDirectory(file)) {
972
+ var found = self._find(file, regex, recursive);
973
+ for (var i=0; i<found.length; i++) {
974
+ files.push(found[i]);
975
+ }
976
+ }
977
+ });
978
+
979
+ return files;
980
+ },
981
+
982
+ canonical:function (file) {
983
+ var abs = this.expandPath(file);
984
+ abs = abs.replace(/\/$/, '');
985
+ return abs;
986
+ },
987
+
988
+ mkpath:function (file) {
989
+ var dirname = this.canonical(this.dirname(file));
990
+
991
+ var subdirs = js2(dirname.split('/'));
992
+ subdirs.shift();
993
+ var toMake = '';
994
+
995
+ var self = this;
996
+ subdirs.each(function($1,$2,$3){
997
+ toMake += '/' + $1;
998
+ self.mkdir(toMake);
999
+ });
1000
+ },
1001
+
1002
+ // ADAPTER USAGE
1003
+ dirname:function (file) {
1004
+ return this.adapter.dirname(file);
1005
+ },
1006
+
1007
+ readdir:function (file) {
1008
+ return this.adapter.readdir(file);
1009
+ },
1010
+
1011
+ read:function (file) {
1012
+ var data = this.adapter.read(file);
1013
+ return data;
1014
+ },
1015
+
1016
+ write:function (file, data) {
1017
+ return this.adapter.write(file, data);
1018
+ },
1019
+
1020
+ mtime:function (file) {
1021
+ return this.adapter.mtime(file);
1022
+ },
1023
+
1024
+ exists:function (file) {
1025
+ return this.isDirectory(file) || this.isFile(file);
1026
+ },
1027
+
1028
+ mkdir:function (file) {
1029
+ if (!this.exists(file)) {
1030
+ return this.adapter.mkdir(file);
1031
+ }
1032
+ },
1033
+
1034
+ isFile:function (file) {
1035
+ try {
1036
+ return this.adapter.isFile(file);
1037
+ } catch(e) {
1038
+ return false;
1039
+ }
1040
+ },
1041
+
1042
+ setInterval:function (code, interval) {
1043
+ return this.adapter.setInterval(code, interval);
1044
+ },
1045
+
1046
+ isDirectory:function (file) {
1047
+ try {
1048
+ return this.adapter.isDirectory(file);
1049
+ } catch(e) {
1050
+ return false;
1051
+ }
1052
+ },
1053
+
1054
+ expandPath:function (file) {
1055
+ return this.adapter.expandPath(file);
1056
+ }
1057
+ });
1058
+
1059
+
1060
+ JS2.Class.extend('Updater', {
1061
+ initialize:function (fs, inDir, outDir, recursive) {
1062
+ this.recursive = recursive;
1063
+ this.fs = fs;
1064
+ this.inDir = this.fs.canonical(inDir);
1065
+ this.outDir = this.fs.canonical(outDir);
1066
+ this.verbose = true;
1067
+ },
1068
+
1069
+ update:function (force, funct) {
1070
+ var self = this;
1071
+ this.matchDirs(this.inDir);
1072
+ this.fs.find(this.inDir, 'js2', this.recursive).each(function($1,$2,$3){
1073
+ self.tryUpdate($1, force, funct);
1074
+ });
1075
+ },
1076
+
1077
+ matchDirs:function (dir) {
1078
+ var subs = this.fs.readdir(dir);
1079
+ for(var _i4=0,_c4=subs,_l4=_c4.length,sub;sub=_c4[_i4]||_i4<_l4;_i4++){
1080
+ var path = dir + '/' + sub;
1081
+ if (this.fs.isDirectory(path)) {
1082
+ this.fs.mkdir(path.replace(this.inDir, this.outDir));
1083
+ this.matchDirs(path);
1084
+ }
1085
+ }
1086
+ },
1087
+
1088
+ tryUpdate:function (file, force, funct) {
1089
+ var outFile = file.replace(this.inDir, this.outDir).replace(/\.js2$/, '.js');
1090
+
1091
+ var dir = this.fs.dirname(file);
1092
+ if (! this.fs.isDirectory(dir)) this.fs.mkpath(dir);
1093
+
1094
+ if (force || this.fs.mtime(file) > this.fs.mtime(outFile)) {
1095
+ if (funct) {
1096
+ this.fs.write(outFile, funct(JS2(this.fs.read(file))));
1097
+ } else {
1098
+ this.fs.write(outFile, JS2(this.fs.read(file)));
1099
+ }
1100
+ }
1101
+ }
1102
+ });
1103
+
1104
+
1105
+ JS2.Class.extend('Commander', {
1106
+ "BANNER":"js2 <command> [options] <arguments>\n" +
1107
+ "Commands:\n" +
1108
+ " * run <file> -- Executes file\n" +
1109
+ " * render <file> -- Shows JS2 compiled output\n" +
1110
+ " * compile <inDir> [outDir] -- Compiles a directory and puts js files into outDir. If outDir is not specified, inDir will be used\n" +
1111
+ " Options:\n" +
1112
+ " -r -- Traverse directories recursively\n" +
1113
+ " -m=<mode> -- Compile for different modes: node, ringo, or browser\n" +
1114
+ " * compile <file> -- Compiles a single js2 file into js\n" +
1115
+ " * watch <inDir> <outDir> -- Similar to compile, but update will keep looping while watching for modifications\n" +
1116
+ " Options:\n" +
1117
+ " -r -- Traverse directories recursively\n" +
1118
+ " -i=<seconds> -- Interval time in seconds between loops\n",
1119
+
1120
+ "DEFAULT_CONFIG":{
1121
+ compile: { inDir: 'src', outDir: 'lib', recursive: true, decorator: 'Node' },
1122
+ watch: { inDir: 'src', outDir: 'lib', recursive: true, decorator: 'Node' }
1123
+ },
1124
+
1125
+ initialize:function (argv) {
1126
+ this.argv = argv;
1127
+ this.command = this.argv.shift();
1128
+ this.fs = JS2.fs;
1129
+ this.parseOpts(argv);
1130
+ },
1131
+
1132
+ cli:function () {
1133
+ if (this[this.command]) {
1134
+ if (this.argv.length == 0) {
1135
+ this.loadArgvFromConfig();
1136
+ }
1137
+
1138
+ this[this.command](this.argv);
1139
+ } else {
1140
+ this.showBanner();
1141
+ }
1142
+ },
1143
+
1144
+ loadArgvFromConfig:function () {
1145
+ },
1146
+
1147
+ render:function (argv) {
1148
+ console.log(js2.render(this.fs.read(argv[0])));
1149
+ },
1150
+
1151
+ run:function (argv) {
1152
+ var file;
1153
+ var i = 0;
1154
+ while (file = argv[i++]) {
1155
+ eval(js2.render(this.fs.read(file)));
1156
+ }
1157
+ },
1158
+
1159
+ "options":{
1160
+ 'r': 'recursive',
1161
+ 'i': 'interval',
1162
+ 'm': 'mode'
1163
+ },
1164
+
1165
+ parseOpts:function (argv) {
1166
+ this.opts = { main: [] };
1167
+ var opts = this.opts;
1168
+
1169
+ for (var i=0; i<argv.length; i++) {
1170
+ var arg = argv[i];
1171
+ var m = arg.match(/^-(\w)(=(\w+))?$/);
1172
+ if (m) {
1173
+ var key = this.options[m[1]];
1174
+ if (! key) console.log('Invalid option: ' + m[1]);
1175
+ opts[key] = m[3] || true;
1176
+ } else {
1177
+ opts.main.push(arg);
1178
+ }
1179
+ }
1180
+
1181
+ switch(opts['mode']) {
1182
+ case 'ringo': JS2.DECORATOR = new JS2.Decorator.Ringo(); break;
1183
+ case 'node': JS2.DECORATOR = new JS2.Decorator.Node(); break;
1184
+ default: JS2.DECORATOR = new JS2.Decorator.Browser(); break;
1185
+ }
1186
+ },
1187
+
1188
+ compile:function () {
1189
+ var inDir = this.opts.main[0];
1190
+ var self = this;
1191
+
1192
+ this.getUpdater().update(true, function($1,$2,$3){ return JS2.DECORATOR.file((self.handleSource($1))); });
1193
+ },
1194
+
1195
+ handleSource:function (code) {
1196
+ if (this.opts.browsers) {
1197
+ return code;
1198
+ } else {
1199
+ return code;
1200
+ }
1201
+ },
1202
+
1203
+ getUpdater:function () {
1204
+ var inDir = this.opts.main[0] || '.';
1205
+ var outDir = this.opts.main[1] || inDir;
1206
+ return new JS2.Updater(this.fs, inDir, outDir, this.opts.recursive);
1207
+ },
1208
+
1209
+ watch:function () {
1210
+ var updater = this.getUpdater();
1211
+ var self = this;
1212
+ var interval = this.opts.interval || 2;
1213
+ console.log('Input Directory:' + updater.inDir + ' -> Output Directory:' + updater.outDir);
1214
+ if (updater.recursive) console.log('RECURSIVE');
1215
+
1216
+ // HACK to get this integrated with ruby
1217
+ updater.update();
1218
+ setInterval(function($1,$2,$3){ console.log('updating'); updater.update() }, interval * 1000);
1219
+ },
1220
+
1221
+ showBanner:function () {
1222
+ console.log(this.BANNER);
1223
+ }
1224
+ });
1225
+
1226
+
1227
+
1228
+ JS2.Class.extend('Decorator.Browser', {
1229
+ file:function (code) {
1230
+ return code;
1231
+ },
1232
+
1233
+ klass:function (name, par, source) {
1234
+ return par+".extend('"+name+"',"+source+");";
1235
+ }
1236
+ });
1237
+
1238
+ JS2.Class.extend('Decorator.Node', {
1239
+ file:function (code) {
1240
+ return "var js2 = require('js2').js2;\nvar JS2 = js2;\n" + code;
1241
+ },
1242
+
1243
+ klass:function (name, par, source) {
1244
+ return "var "+name+"=exports['"+name+"']="+par+".extend("+source+");";
1245
+ }
1246
+ });
1247
+
1248
+ JS2.Class.extend('Decorator.Ringo', {
1249
+ file:function (code) {
1250
+ return "var js2 = require('js2').js2;\nvar JS2 = js2;\n" + code;
1251
+ },
1252
+
1253
+ klass:function (name, par, source) {
1254
+ return "var "+name+"=exports['"+name+"']="+par+".extend("+source+");";
1255
+ }
1256
+ });
1257
+
1258
+
1259
+
1260
+ JS2.fs = new JS2.FileSystem(JS2_RUBY_FILE_ADAPTER);
1261
+ js2.DECORATOR = new JS2.Decorator.Browser();
1262
+ js2.ROOT = root;
1263
+
1264
+ return JS2;
1265
+ })(this);