minjs 0.3.0 → 0.4.0

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.
@@ -0,0 +1,1146 @@
1
+ # coding: utf-8
2
+ require 'minjs'
3
+ require 'logger'
4
+
5
+ module Minjs::Compressor
6
+ # Compressor class
7
+ class Compressor
8
+ include Minjs
9
+
10
+ attr_reader :prog
11
+
12
+ def initialize(options = {})
13
+ @logger = options[:logger]
14
+ if !@logger
15
+ @logger = Logger.new(STDERR)
16
+ @logger.level = (options[:debug_level] || Logger::WARN)
17
+ @logger.formatter = proc{|severity, datetime, progname, message|
18
+ "#{message}\n"
19
+ }
20
+ end
21
+ end
22
+
23
+ # debuging method
24
+ def debug
25
+ puts @prog.to_js()
26
+ end
27
+
28
+ # Returns a ECMAScript string
29
+ def to_js(options = {})
30
+ remove_empty_statement
31
+ @prog.to_js(options).sub(/;;\Z/, ";")
32
+ end
33
+
34
+ # Removes empty statement
35
+ def remove_empty_statement(node = @prog)
36
+ node.traverse(nil) {|parent, st|
37
+ if st.kind_of? ECMA262::StatementList
38
+ st.remove_empty_statement
39
+ end
40
+ }
41
+ self
42
+ end
43
+
44
+ # Compresses ECMAScript
45
+ def compress(data, options = {})
46
+ @logger.info '* parse'
47
+ parse(data)
48
+
49
+ if options[:only_parse]
50
+ return
51
+ end
52
+
53
+ algo = [
54
+ :reorder_function_decl,
55
+ :simple_replacement,
56
+ :reorder_var,
57
+ :assignment_after_var,
58
+ :grouping_statement,
59
+ :block_to_statement,
60
+ :reduce_if,
61
+ :if_to_cond,
62
+ :if_to_return2,
63
+ :compress_var,
64
+ :reduce_exp,
65
+ :grouping_statement,
66
+ :block_to_statement,
67
+ :if_to_cond,
68
+ :remove_then_or_else,
69
+ :block_to_statement,
70
+ :add_remove_paren,
71
+ ]
72
+ algo.each do |a|
73
+ if (options.empty? || options[:all] || options[a]) && !options[("no_" + a.to_s).to_sym]
74
+ @logger.info "* #{a}"
75
+ __send__(a, @prog)
76
+ end
77
+ end
78
+
79
+ @heading_comments.reverse.each do |c|
80
+ @prog.source_elements.source_elements.unshift(c)
81
+ end
82
+ self
83
+ end
84
+
85
+ # parses input elements and create node element tree
86
+ #
87
+ # @param data [String] ECMAScript input element
88
+ # @return self
89
+ def parse(data)
90
+ @lex = Minjs::Lex::Parser.new(data, :logger => @logger)
91
+ @global_context = ECMA262::Context.new
92
+ @heading_comments = []
93
+
94
+ while a = (@lex.comment || @lex.line_terminator || @lex.white_space)
95
+ @heading_comments.push(a)
96
+ end
97
+ while @heading_comments.last == ECMA262::LIT_LINE_TERMINATOR and
98
+ !(@heading_comments[-2].kind_of?(ECMA262::SingleLineComment))
99
+ @heading_comments.pop
100
+ end
101
+ @prog = @lex.program(@global_context)
102
+
103
+ remove_empty_statement
104
+ @lex.clear_cache
105
+ self
106
+ end
107
+
108
+ def c2i(c)
109
+ c = c.ord
110
+ if c >= 0x30 and c <= 0x39
111
+ c = c - 0x30
112
+ elsif c >= 0x61 and c <= 0x7a
113
+ c = c - 0x61 + 10
114
+ elsif c >= 0x41 and c <= 0x5a
115
+ c = c - 0x41 + 10 + 26
116
+ elsif c == 0x5f
117
+ c = 62
118
+ elsif c == 0x24
119
+ c = 63
120
+ end
121
+ end
122
+
123
+ def i2c(c)
124
+ if c < 10
125
+ c = "%c" % (0x30 + c)
126
+ elsif c < 10 + 26
127
+ c = "%c" % (0x61 + c - 10)
128
+ elsif c < 10 + 26 + 26
129
+ c = "%c" % (0x41 + c - 10 - 26)
130
+ elsif c < 63
131
+ c = "_"
132
+ elsif c < 64
133
+ c = "$"
134
+ end
135
+ end
136
+
137
+ def next_sym(s)
138
+ v = 0
139
+ s.to_s.split("").each do |x|
140
+ v *= 64
141
+ v += c2i(x)
142
+ end
143
+
144
+ while true
145
+ v += 1
146
+ ret = []
147
+ vv = v
148
+ while vv > 0
149
+ ret.unshift(i2c(vv % 64))
150
+ vv /= 64
151
+ end
152
+ ret = ret.join("")
153
+ if ECMA262::IdentifierName.reserved?(ret.to_sym)
154
+ ;
155
+ elsif ret.to_s.match(/^\d/)
156
+ ;
157
+ else
158
+ break
159
+ end
160
+ end
161
+ ret.to_sym
162
+ end
163
+ private :c2i, :i2c, :next_sym
164
+
165
+ # Groups statements in the block and reduce number of them as few as posibble.
166
+ def grouping_statement(node = @prog)
167
+ node.traverse(nil) {|parent, st|
168
+ if st.kind_of? ECMA262::StatementList
169
+ st.grouping
170
+ end
171
+ }
172
+ add_remove_paren
173
+ self
174
+ end
175
+
176
+ # Moves function declaration to first of the scope.
177
+ def reorder_function_decl(node = @prog)
178
+ flist = []
179
+ node.traverse(nil) {|parent, st|
180
+ if st.kind_of? ECMA262::StFunc and parent.kind_of? ECMA262::StatementList and st.decl?
181
+ if parent.index(st)
182
+ flist.push([parent, st])
183
+ end
184
+ end
185
+ }
186
+ flist.reverse.each do |parent, st|
187
+ parent.remove(st)
188
+ sl = parent.statement_list
189
+ if sl[0].kind_of? ECMA262::StExp and sl[0].exp.kind_of? ECMA262::ECMA262String and sl[0].exp.val == "use strict"
190
+ sl[1,0] = st
191
+ else
192
+ sl.unshift(st)
193
+ end
194
+ end
195
+ self
196
+ end
197
+
198
+ # Collect all variable statment in this scope and puts together one statement.
199
+ #
200
+ # After collecting all variable, this method moves it to the best place in
201
+ # this scope.
202
+ def reorder_var(node = @prog)
203
+ node.traverse(nil) {|parent, st|
204
+ if st.kind_of? ECMA262::Prog
205
+ vars = nil
206
+ context = st.context
207
+ #
208
+ # collect all of var variable in this function
209
+ #
210
+ var_vars = {}
211
+ context.var_env.record.binding.each do|k, v|
212
+ if v and v[:_parameter_list].nil? and !v[:value].kind_of?(ECMA262::StFunc)
213
+ var_vars[k] = true
214
+ end
215
+ end
216
+ #
217
+ # traverse block and convert var statement to assignment expression
218
+ # if variable has initializer
219
+ #
220
+ st.traverse(parent){|parent2, st2|
221
+ if st2.kind_of? ECMA262::StVar and st2.context.var_env == context.var_env
222
+ exp = nil
223
+ st2.vars.each do |name, initializer|
224
+ if initializer
225
+ if exp.nil?
226
+ exp = ECMA262::ExpAssign.new(name, initializer)
227
+ else
228
+ exp = ECMA262::ExpComma.new(exp, ECMA262::ExpAssign.new(name, initializer))
229
+ end
230
+ end
231
+ end
232
+ if exp
233
+ parent2.replace(st2, ECMA262::StExp.new(exp))
234
+ else
235
+ parent2.replace(st2, ECMA262::StEmpty.new())
236
+ end
237
+ elsif st2.kind_of? ECMA262::StForVar and st2.context.var_env == context.var_env
238
+ parent2.replace(st2, st2.to_st_for)
239
+ elsif st2.kind_of? ECMA262::StForInVar and st2.context.var_env == context.var_env
240
+ parent2.replace(st2, st2.to_st_for_in)
241
+ end
242
+ }
243
+ if var_vars.length > 0
244
+ elems = st.source_elements.source_elements
245
+ v = ECMA262::StVar.new(
246
+ context,
247
+ var_vars.collect do |k, v|
248
+ [ECMA262::IdentifierName.get(context, k)]
249
+ end
250
+ )
251
+
252
+ idx = 0
253
+ elems.each do |e|
254
+ found = false
255
+ if e.kind_of? ECMA262::StFunc and e.decl?
256
+ ;
257
+ elsif e.kind_of? ECMA262::StExp and e.exp.kind_of? ECMA262::ECMA262String and e.exp.val == "use strict"
258
+ ;
259
+ else
260
+ e.traverse(nil){|pp, ee|
261
+ if ee.kind_of? ECMA262::IdentifierName and var_vars[ee.val.to_sym]
262
+ found = true
263
+ break
264
+ end
265
+ }
266
+ end
267
+ break if found
268
+ idx += 1
269
+ end
270
+
271
+ if idx == 0
272
+ elems.unshift(v)
273
+ else
274
+ elems[idx..0] = v
275
+ end
276
+ st.source_elements.remove_empty_statement
277
+ end
278
+ end
279
+ self
280
+ }
281
+ self
282
+ end
283
+
284
+ # Removes parenthesis if possible and add parentesis if need.
285
+ def add_remove_paren(node = @prog)
286
+ node.traverse(nil) {|parent, st|
287
+ if st.respond_to? :remove_paren
288
+ st.remove_paren
289
+ st.add_paren
290
+ end
291
+ }
292
+ self
293
+ end
294
+
295
+ # Converts every statement of 'then' to block even if
296
+ # it contain only one statement.
297
+ #
298
+ # To determine removing "block" is posibble or not is difficult.
299
+ # For example, next code's if-block must not be removed, because
300
+ # "else" cluase combined to second "if" statement.
301
+ #
302
+ # if(a){ //<= this block must not be removed
303
+ # while(true)
304
+ # if(b){
305
+ # ;
306
+ # }
307
+ # }
308
+ # else{
309
+ # ;
310
+ # }
311
+ #
312
+ # The next code's while-block must not be removed, because
313
+ # "else" cluase combined to second "if" statement.
314
+ #
315
+ # if(a)
316
+ # while(true){ //<= this block must not be removed
317
+ # if(b){
318
+ # ;
319
+ # }
320
+ # }
321
+ # else{
322
+ # ;
323
+ # }
324
+ #
325
+ # To solve this problem, first, every then-clause without block
326
+ # converts to block statement. After converted, all blocks
327
+ # except then-clause can be removed safety.
328
+ #
329
+ def then_to_block(node = @prog)
330
+ node.traverse(nil) {|parent, st|
331
+ if st.kind_of? ECMA262::StIf
332
+ if !st.then_st.kind_of?(ECMA262::StBlock)
333
+ st.replace(st.then_st, ECMA262::StBlock.new([st.then_st]))
334
+ end
335
+ end
336
+ }
337
+ end
338
+
339
+ # Converts Block to single statement if possible
340
+ def block_to_statement(node = @prog)
341
+ remove_empty_statement
342
+ then_to_block
343
+ node.traverse(nil) {|parent, st|
344
+ if st.kind_of? ECMA262::StBlock and !parent.kind_of?(ECMA262::StTry) and !parent.kind_of?(ECMA262::StIf)
345
+ if st.to_statement?
346
+ parent.replace(st, st.to_statement)
347
+ end
348
+ end
349
+ }
350
+ if_block_to_statement
351
+ end
352
+
353
+ # Converts If statement's block to single statement if possible
354
+ def if_block_to_statement(node = @prog)
355
+ remove_empty_statement
356
+ # The "else" cluase's block can be removed always
357
+ node.traverse(nil) {|parent, st|
358
+ if st.kind_of? ECMA262::StIf
359
+ if st.else_st and st.else_st.kind_of? ECMA262::StBlock
360
+ st.else_st.remove_empty_statement
361
+ end
362
+
363
+ if st.else_st and st.else_st.kind_of? ECMA262::StBlock and st.else_st.to_statement?
364
+ st.replace(st.else_st, st.else_st.to_statement)
365
+ end
366
+ end
367
+ }
368
+ node.traverse(nil) {|parent, st|
369
+ if st.kind_of? ECMA262::StIf
370
+ if st.then_st and st.then_st.kind_of? ECMA262::StBlock
371
+ st.then_st.remove_empty_statement
372
+ end
373
+ if !st.else_st and st.then_st.kind_of? ECMA262::StBlock and st.then_st.to_statement?
374
+ st.replace(st.then_st, st.then_st.to_statement)
375
+ elsif st.then_st.kind_of? ECMA262::StBlock and st.then_st.to_statement?
376
+ _st = st.then_st
377
+ st2 = st.then_st.to_statement
378
+ while true
379
+ if st2.kind_of? ECMA262::StVar or st2.kind_of? ECMA262::StEmpty or
380
+ st2.kind_of? ECMA262::StExp or st2.kind_of? ECMA262::StBlock or
381
+ st2.kind_of? ECMA262::StDoWhile or st2.kind_of? ECMA262::StSwitch or
382
+ st2.kind_of? ECMA262::StContinue or st2.kind_of? ECMA262::StBreak or
383
+ st2.kind_of? ECMA262::StReturn or st2.kind_of? ECMA262::StThrow or
384
+ st2.kind_of? ECMA262::StTry or st2.kind_of? ECMA262::StDebugger
385
+ st.replace(st.then_st, st.then_st.to_statement)
386
+ break;
387
+ elsif st2.kind_of? ECMA262::StWhile or
388
+ st2.kind_of? ECMA262::StFor or
389
+ st2.kind_of? ECMA262::StForIn or
390
+ st2.kind_of? ECMA262::StForVar or
391
+ st2.kind_of? ECMA262::StForInVar or
392
+ st2.kind_of? ECMA262::StWith or
393
+ st2.kind_of? ECMA262::StLabelled
394
+ st2 = st2.statement
395
+ elsif st2.kind_of? ECMA262::StIf
396
+ if st2.else_st
397
+ st2 = st2.else_st
398
+ else
399
+ break
400
+ end
401
+ else #?
402
+ break
403
+ end
404
+ end
405
+ end
406
+ end
407
+ }
408
+ self
409
+ end
410
+
411
+ # Convers if statement to expression statement if possible.
412
+ #
413
+ # if(a)b;else c;
414
+ # =>
415
+ # a?b:c
416
+ #
417
+ # if(a)b
418
+ # =>
419
+ # a&&b;
420
+ # or
421
+ # a?b:0;
422
+ #
423
+ # @note
424
+ # Sometimes, "conditional operator" will be shorter than
425
+ # "logical and operator", because "conditional operator"'s
426
+ # priority is lower than almost all other expressions.
427
+ #
428
+ def if_to_cond(node = @prog)
429
+ node.traverse(nil) {|parent, st|
430
+ if st.kind_of? ECMA262::StIf
431
+ if st.to_exp?
432
+ t = ECMA262::StExp.new(st.to_exp({}))
433
+ t2 = ECMA262::StExp.new(st.to_exp({cond: true}))
434
+ if t2.to_js.length < t.to_js.length
435
+ t = t2
436
+ end
437
+ add_remove_paren(t)
438
+ simple_replacement(t)
439
+
440
+ if t.to_js.length <= st.to_js.length
441
+ parent.replace(st, t)
442
+ end
443
+ end
444
+ end
445
+ }
446
+ if_to_return(node)
447
+ self
448
+ end
449
+ # Converts 'if statement' to 'return statement'
450
+ #
451
+ # The condition is:
452
+ # 'if statement' which has 'return statement' in its then-clause
453
+ # or else-cluase to 'return statement'
454
+ #
455
+ # if(a)return b;else return c;
456
+ # =>
457
+ # return a?b:c;
458
+ #
459
+ def if_to_return(node = @prog)
460
+ node.traverse(nil) {|parent, st|
461
+ if st.kind_of? ECMA262::StIf
462
+ if st.to_return?
463
+ t = st.to_return
464
+ add_remove_paren(t)
465
+ simple_replacement(t)
466
+ if t.to_js.length <= st.to_js.length
467
+ parent.replace(st, t)
468
+ end
469
+ end
470
+ end
471
+ }
472
+ self
473
+ end
474
+
475
+ # Optimize 'if statement'.
476
+ #
477
+ # The condition is:
478
+ # 'if statement' which has 'return statement' in its then-clause and
479
+ # its next statement is 'return statement'
480
+ #
481
+ # if(a)return b;
482
+ # return c;
483
+ # =>
484
+ # return a?b:c;
485
+ #
486
+ def if_to_return2(node = @prog)
487
+ node.traverse(nil) {|parent0, st0|
488
+ if st0.kind_of? ECMA262::StatementList
489
+ st0.remove_empty_statement
490
+ st = st0.deep_dup
491
+ while true
492
+ #check last statement
493
+ ls = st.statement_list[-1]
494
+ ls2 = st.statement_list[-2]
495
+ if st.kind_of? ECMA262::SourceElements and !(ls.kind_of? ECMA262::StReturn)
496
+ ls2 = ls
497
+ ls = ECMA262::StReturn.new(ECMA262::ExpVoid.new(ECMA262::ECMA262Numeric.new(0)))
498
+ end
499
+ break if ls.nil?
500
+ break if ls2.nil?
501
+ break if !ls.to_return?
502
+ break if !ls2.kind_of?(ECMA262::StIf)
503
+ break if ls2.else_st
504
+ break if !ls2.then_st.to_return?
505
+
506
+ # if !ls2.then_st.kind_of? ECMA262::StIf and !ls2.then_st.to_return?
507
+ # break
508
+ # end
509
+ # if ls2.then_st.kind_of? ECMA262::StIf and !ls2.then_to_return?
510
+ # break
511
+ # end
512
+
513
+ then_exp = ls2.then_st.to_return.exp
514
+ else_exp = ls.to_return.exp
515
+ then_exp = ECMA262::ExpVoid.new(ECMA262::ECMA262Numeric.new(0)) if then_exp.nil?
516
+ else_exp = ECMA262::ExpVoid.new(ECMA262::ECMA262Numeric.new(0)) if else_exp.nil?
517
+ if ls2.cond.kind_of? ECMA262::ExpLogicalNot
518
+ cond = ECMA262::ExpCond.new(ls2.cond.val, else_exp, then_exp)
519
+ else
520
+ cond = ECMA262::ExpCond.new(ls2.cond, then_exp, else_exp)
521
+ end
522
+ ret = ECMA262::StReturn.new(cond)
523
+ #puts ret.to_js
524
+ #puts ls2.to_js
525
+ st.replace(ls2, ret)
526
+ st.remove(ls)
527
+ end
528
+ if st0.to_js.length > st.to_js.length
529
+ parent0.replace(st0, st)
530
+ end
531
+ end
532
+ }
533
+ self
534
+ end
535
+
536
+ # Optimize 'if statement'.
537
+ #
538
+ # The condition is:
539
+ # 'if statement' which has 'return statement' in its then-clause and
540
+ # its else-caluse has no 'return statement'
541
+ #
542
+ # if(a)return b;else c;
543
+ # =>
544
+ # if(a)return b;c;
545
+ #
546
+ def remove_then_or_else(node = @prog)
547
+ node.traverse(nil) {|parent, st|
548
+ if st.kind_of? ECMA262::StIf and st.else_st and parent.kind_of? ECMA262::StatementList
549
+ st.remove_empty_statement
550
+ if (st.then_st.kind_of? ECMA262::StBlock and st.then_st[-1].kind_of? ECMA262::StReturn) or
551
+ st.then_st.kind_of? ECMA262::StReturn
552
+ idx = parent.index(st)
553
+ parent[idx+1..0] = st.else_st
554
+ st.replace(st.else_st, nil)
555
+ elsif (st.else_st.kind_of? ECMA262::StBlock and st.else_st[-1].kind_of? ECMA262::StReturn) or
556
+ st.else_st.kind_of? ECMA262::StReturn
557
+ idx = parent.index(st)
558
+ parent[idx+1..0] = st.then_st
559
+ st.instance_eval{
560
+ @then_st = @else_st
561
+ @else_st = nil
562
+ @cond = ECMA262::ExpLogicalNot.new(@cond)
563
+ }
564
+ end
565
+ end
566
+ }
567
+ self
568
+ end
569
+
570
+ # Compresses variable name as short as possible.
571
+ #
572
+ # This method collects and counts all variables under this function,
573
+ # then trying to rename var_vars(see bellow) to
574
+ # new name.
575
+ #
576
+ # outer_vars::
577
+ # Variables which locate out of this function(or global variable)
578
+ # Them name cannot be renamed
579
+ # nesting_vars::
580
+ # Variables which locate in the function of this function.
581
+ # Them name cannot be renamed
582
+ # var_vars::
583
+ # Variables which have same scope in this function.
584
+ # all_vars::
585
+ # All variables under this function.
586
+ #
587
+ # 1. If the new name is not in all_vars, the name can be renamed to it.
588
+ # 2. If the new name belongs to var_vars, the name cannot be renamed.
589
+ # 3. If the new name belongs to outer_vars the name cannot be renamed.
590
+ # 4. If the new name belongs to nesting_vars, the name can be rename
591
+ # to it after renaming nesting_vars's name to another name.
592
+ #
593
+ #
594
+ def compress_var(node = @prog)
595
+ func_scopes = []
596
+ catch_scopes = []
597
+ with_scopes = []
598
+ #
599
+ # ECMA262 10.2:
600
+ #
601
+ # Usually a Lexical Environment is associated with some
602
+ # specific syntactic structure of ECMAScript code such as a
603
+ # FunctionDeclaration, a WithStatement, or a Catch clause of a
604
+ # TryStatement and a new Lexical Environment is created each
605
+ # time such code is evaluated.
606
+ #
607
+ node.traverse(nil) {|parent, st|
608
+ if st.kind_of? ECMA262::StFunc
609
+ func_scopes.push([parent, st])
610
+ elsif st.kind_of? ECMA262::StTry
611
+ catch_scopes.push([parent, st])
612
+ elsif st.kind_of? ECMA262::StWith
613
+ with_scopes.push([parent, st])
614
+ end
615
+ }
616
+ #
617
+ # 10.2, 12.14
618
+ #
619
+ #eee = 'global';
620
+ #function test()
621
+ #{
622
+ # /*
623
+ # "eee" is local variable(belongs to this function)
624
+ # because var declaration is exist in this function.
625
+ # (see also catch's scope comment)
626
+ # So, global variable 'eee' is not changed.
627
+ # */
628
+ # eee = 'function';
629
+ # try{
630
+ # console.log(eee); //=>function
631
+ # throw "exception";
632
+ # }
633
+ # catch(eee){
634
+ # /*
635
+ # The catch's variable scope will be created at execution time.
636
+ # so next var declaration should belong to "test" function.
637
+ # */
638
+ # var eee;
639
+ # /*
640
+ # In execution time, "eee" belongs to this
641
+ # catch-clause's scope.
642
+ # */
643
+ # console.log(eee); //=>exception
644
+ # /*
645
+ # Next function has its own scope and 'eee' belongs to its.
646
+ # */
647
+ # (function(){
648
+ # var eee;
649
+ # console.log(eee); //=>undefined
650
+ # })();
651
+ # }
652
+ #}
653
+ #console.log(eee); //=>global
654
+ #test();
655
+ #
656
+ catch_scopes.each{|parent, st|
657
+ if st.catch
658
+ catch_context = ECMA262::Context.new
659
+ catch_context.lex_env = st.context.lex_env.new_declarative_env()
660
+ catch_context.var_env = st.context.var_env
661
+ catch_context.lex_env.record.create_mutable_binding(st.catch[0], nil)
662
+ catch_context.lex_env.record.set_mutable_binding(st.catch[0], :undefined, nil)
663
+ st.catch[0].context = catch_context
664
+
665
+ st.catch[1].traverse(parent){|parent2, st2|
666
+ if st2.kind_of? ECMA262::IdentifierName and st2 == st.catch[0] and st2.binding_env == st.catch[0].binding_env
667
+ st2.context = catch_context
668
+ end
669
+ }
670
+ func_scopes.unshift([parent, st])
671
+ end
672
+ }
673
+ # with_scopes.each{|st, parent|
674
+ # with_context = ECMA262::Context.new
675
+ # with_context.lex_env = st.context.lex_env.new_declarative_env()
676
+ # with_context.var_env = st.context.var_env
677
+ # st.statement.traverse(st) {|st2|
678
+ # if st2.kind_of? ECMA262::IdentifierName and st2.binding_env == st.context.var_env
679
+ # st2.context = with_context
680
+ # with_context.lex_env.record.create_mutable_binding(st2, nil)
681
+ # with_context.lex_env.record.set_mutable_binding(st2, :undefined, nil)
682
+ # end
683
+ # }
684
+ # }
685
+ func_scopes.reverse!
686
+ func_scopes.each {|parent, st|
687
+ if st.kind_of? ECMA262::StFunc
688
+ context = st.context
689
+ elsif st.kind_of? ECMA262::StTry
690
+ context = st.catch[0].context
691
+ end
692
+ var_sym = :a
693
+ all_vars = {}
694
+ var_vars = {}
695
+ var_vars_list = []
696
+ outer_vars = {}
697
+ nesting_vars = {}
698
+ nesting_vars_list = []
699
+
700
+ st.traverse(parent) {|parent2, st2|
701
+ if st2.kind_of? ECMA262::IdentifierName
702
+ var_name = st2.val.to_sym
703
+ #st2_var_env = st2.binding_env
704
+ st2_lex_env = st2.binding_env(:lex)
705
+ all_vars[var_name] ||= 0
706
+ all_vars[var_name] += 1
707
+ if st2_lex_env == nil #global
708
+ outer_vars[var_name] ||= 0
709
+ outer_vars[var_name] += 1
710
+ elsif st2_lex_env == @global_context.lex_env #global
711
+ outer_vars[var_name] ||= 0
712
+ outer_vars[var_name] += 1
713
+ elsif st2_lex_env == context.lex_env
714
+ var_vars[var_name] ||= 0
715
+ var_vars[var_name] += 1
716
+ var_vars_list.push(st2)
717
+ else
718
+ e = st2.binding_env(:lex)
719
+ while e
720
+ e = e.outer
721
+ if e == context.lex_env
722
+ nesting_vars[var_name] ||= 0
723
+ nesting_vars[var_name] += 1
724
+ nesting_vars_list.push(st2)
725
+ break
726
+ end
727
+ if e.nil?
728
+ outer_vars[var_name] ||= 0
729
+ outer_vars[var_name] += 1
730
+ break
731
+ end
732
+ end
733
+ end
734
+ end
735
+ }
736
+ unless var_vars[:eval]
737
+ eval_flag = false
738
+ st.traverse(parent) {|parent2, st2|
739
+ if st2.kind_of? ECMA262::ExpCall and st2.name.to_js({}) == "eval"
740
+ eval_flag = true
741
+ break
742
+ end
743
+ if st2.kind_of? ECMA262::StWith
744
+ eval_flag = true
745
+ break
746
+ end
747
+ }
748
+ if eval_flag
749
+ next
750
+ end
751
+ end
752
+ #
753
+ # sort var_vars
754
+ #
755
+ var_vars_array = var_vars.sort {|(k1,v1), (k2,v2)| v2 <=> v1}
756
+ #
757
+ # create renaming table
758
+ #
759
+ rename_table = {}
760
+ var_vars_array.each {|name, count|
761
+ if name.nil?
762
+ next #bug?
763
+ end
764
+ if name.length == 1
765
+ #STDERR.puts "#{name}=>#{count}"
766
+ next
767
+ end
768
+ #STDERR.puts "trying to rename #{name}(#{count})"
769
+ while true
770
+ #condition b
771
+ if outer_vars[var_sym]
772
+ #STDERR.puts "outer_vars has #{var_sym}"
773
+ elsif var_vars[var_sym]
774
+ #STDERR.puts "var_vars has #{var_sym}(#{var_vars[var_sym]})"
775
+ #condigion c
776
+ else #condition a&d
777
+ #STDERR.puts "->#{var_sym}"
778
+ break
779
+ end
780
+ var_sym = next_sym(var_sym)
781
+ end
782
+ #rename nesting_vars
783
+ if nesting_vars[var_sym]
784
+ #STDERR.puts "nesting_vars has #{var_sym}"
785
+ nesting_vars_list.each do |x|
786
+ #raise 'error' if x.binding_env(:var).nil?
787
+ raise 'error' if x.binding_env(:lex).nil?
788
+ end
789
+
790
+ var_sym2 = "XXX#{var_sym.to_s}".to_sym
791
+ while all_vars[var_sym2]
792
+ var_sym2 = next_sym(var_sym2)
793
+ end
794
+ #STDERR.puts "#{var_sym}->#{var_sym2}"
795
+ rl = {}
796
+ nesting_vars_list.each do |x|
797
+ if x.val.to_sym == var_sym
798
+ _var_env = x.binding_env(:var)
799
+ _lex_env = x.binding_env(:lex)
800
+ rl[_var_env] = true
801
+ rl[_lex_env] = true
802
+ end
803
+ end
804
+ rl.keys.each do |_env|
805
+ if _env && _env.record.binding[var_sym]
806
+ _env.record.binding[var_sym2] = _env.record.binding[var_sym]
807
+ _env.record.binding.delete var_sym
808
+ end
809
+ end
810
+
811
+ nesting_vars_list.each do |x|
812
+ if x.val.to_sym == var_sym
813
+ x.instance_eval{
814
+ @val = var_sym2
815
+ }
816
+ end
817
+ #raise 'error' if x.binding_env(:var).nil?
818
+ raise x.to_js if x.binding_env(:lex).nil?
819
+ end
820
+ end
821
+ rename_table[name] = var_sym
822
+ var_sym = next_sym(var_sym)
823
+ }
824
+ var_vars_list.each {|st2|
825
+ raise st2.to_js if st2.binding_env(:lex).nil?
826
+ }
827
+
828
+ rename_table.each do |name, new_name|
829
+ if name != new_name
830
+ if context.var_env.record.binding[name]
831
+ context.var_env.record.binding[new_name] = context.var_env.record.binding[name]
832
+ context.var_env.record.binding.delete(name)
833
+ end
834
+ if context.lex_env.record.binding[name]
835
+ context.lex_env.record.binding[new_name] = context.lex_env.record.binding[name]
836
+ context.lex_env.record.binding.delete(name)
837
+ end
838
+ end
839
+ end
840
+
841
+ var_vars_list.each {|st2|
842
+ st2.instance_eval{
843
+ if rename_table[@val]
844
+ @val = rename_table[@val]
845
+ #raise 'error' if st2.binding_env(:var).nil?
846
+ raise st2.to_js if st2.binding_env(:lex).nil?
847
+ end
848
+ }
849
+ }
850
+ }
851
+ self
852
+ end
853
+
854
+ # Reduces expression
855
+ def reduce_exp(node = @prog)
856
+ node.traverse(nil) {|parent, st|
857
+ if st.kind_of? ECMA262::Expression
858
+ st.reduce(parent)
859
+ end
860
+ }
861
+ self
862
+ end
863
+
864
+ # Simple replacement
865
+ def simple_replacement(node = @prog)
866
+ node.traverse(nil) {|parent, st|
867
+ #
868
+ #true => !0
869
+ #false => !1
870
+ #
871
+ if st.kind_of? ECMA262::Boolean
872
+ if st.true?
873
+ parent.replace(st, ECMA262::ExpParen.new(ECMA262::ExpLogicalNot.new(ECMA262::ECMA262Numeric.new(0))))
874
+ else
875
+ parent.replace(st, ECMA262::ExpParen.new(ECMA262::ExpLogicalNot.new(ECMA262::ECMA262Numeric.new(1))))
876
+ end
877
+ #
878
+ #if(true){<then>}else{<else>} => <then>
879
+ #if(false){<then>}else{<else>} => <else>
880
+ #
881
+ elsif st.kind_of? ECMA262::StIf
882
+ if st.cond.respond_to? :to_ecma262_boolean
883
+ if st.cond.to_ecma262_boolean.nil?
884
+ ;
885
+ elsif st.cond.to_ecma262_boolean == true
886
+ parent.replace(st, st.then_st)
887
+ elsif st.cond.to_ecma262_boolean == false and st.else_st
888
+ parent.replace(st, st.else_st)
889
+ elsif st.cond.to_ecma262_boolean == false
890
+ parent.replace(st, ECMA262::StEmpty.new)
891
+ end
892
+ end
893
+ #
894
+ # while(true) => for(;;)
895
+ # while(false) => remove
896
+ #
897
+ elsif st.kind_of? ECMA262::StWhile and st.exp.respond_to? :to_ecma262_boolean
898
+ if st.exp.to_ecma262_boolean.nil?
899
+ ;
900
+ elsif st.exp.to_ecma262_boolean
901
+ parent.replace(st, ECMA262::StFor.new(nil,nil,nil, st.statement))
902
+ else
903
+ parent.replace(st, ECMA262::StEmpty.new)
904
+ end
905
+ #
906
+ # new A() => (new A)
907
+ #
908
+ elsif st.kind_of? ECMA262::ExpNew and st.args and st.args.length == 0
909
+ st.replace(st.args, nil)
910
+ parent.add_paren.remove_paren
911
+ #
912
+ # !c?a:b => c?b:a
913
+ # true?a:b => a
914
+ # false?a:b => b
915
+ #
916
+ elsif st.kind_of? ECMA262::ExpCond
917
+ if st.val.kind_of? ECMA262::ExpLogicalNot
918
+ st.instance_eval{
919
+ @val = @val.val
920
+ t = @val2
921
+ @val2 = @val3
922
+ @val3 = t
923
+ }
924
+ simple_replacement(st)
925
+ end
926
+
927
+ if st.val.respond_to? :to_ecma262_boolean
928
+ if st.val.to_ecma262_boolean.nil?
929
+ ;
930
+ elsif st.val.to_ecma262_boolean
931
+ parent.replace(st, st.val2)
932
+ else
933
+ parent.replace(st, st.val3)
934
+ end
935
+ end
936
+ #
937
+ # A["B"] => A.N
938
+ #
939
+ elsif st.kind_of? ECMA262::ExpPropBrac and st.val2.kind_of? ECMA262::ECMA262String
940
+ if @lex.idname?(st.val2.val)
941
+ parent.replace(st, ECMA262::ExpProp.new(st.val, st.val2))
942
+ elsif !st.val2.to_ecma262_number.nil? and (v=ECMA262::ECMA262Numeric.new(st.val2.to_ecma262_number)).to_ecma262_string == st.val2.to_ecma262_string
943
+ st.replace(st.val2, v)
944
+ end
945
+ end
946
+ }
947
+ self
948
+ end
949
+
950
+ # reduce if statement
951
+ def reduce_if(node = @prog)
952
+ retry_flag = true
953
+ while retry_flag
954
+ retry_flag = false
955
+ node.traverse(nil) {|parent, st|
956
+ if st.kind_of? ECMA262::StIf
957
+ # if(a)
958
+ # if(b) ...;
959
+ # if(a && b) ...;
960
+ #
961
+ if st.else_st.nil? and
962
+ st.then_st.kind_of? ECMA262::StIf and st.then_st.else_st.nil?
963
+ st.replace(st.cond, ECMA262::ExpLogicalAnd.new(st.cond, st.then_st.cond))
964
+ st.replace(st.then_st, st.then_st.then_st)
965
+ end
966
+ #if(a)z;else;
967
+ #if(a)z;else{}
968
+ # => {if(a)z;}
969
+ if st.else_st and st.else_st.empty?
970
+ st.replace(st.else_st, nil)
971
+ parent.replace(st, ECMA262::StBlock.new([st]))
972
+ retry_flag = true
973
+ break
974
+ end
975
+ #if(a);else z;
976
+ #=>{if(!a)z};
977
+ #if(a){}else z;
978
+ #=>{if(!a)z};
979
+ if st.then_st.empty? and st.else_st
980
+ st.replace(st.cond, ECMA262::ExpLogicalNot.new(st.cond));
981
+ else_st = st.else_st
982
+ st.replace(st.else_st, nil)
983
+ st.replace(st.then_st, else_st)
984
+ parent.replace(st, ECMA262::StBlock.new([st]))
985
+ retry_flag = true
986
+ break
987
+ end
988
+ #if(a);
989
+ # => a
990
+ #if(a){}
991
+ # => a
992
+ if st.then_st.empty? and st.else_st.nil?
993
+ parent.replace(st, ECMA262::StExp.new(st.cond))
994
+ end
995
+ =begin
996
+ #if(!(a&&b))
997
+ #=>
998
+ #if(!a||!b)
999
+ if st.cond.kind_of? ECMA262::ExpLogicalNot and st.cond.val.kind_of? ECMA262::ExpParen and
1000
+ st.cond.val.val.kind_of? ECMA262::ExpLogicalAnd
1001
+ a = ECMA262::ExpLogicalNot.new(st.cond.val.val.val)
1002
+ b = ECMA262::ExpLogicalNot.new(st.cond.val.val.val2)
1003
+ r = ECMA262::ExpLogicalOr.new(a,b).add_remove_paren
1004
+ if r.to_js.length <= st.cond.to_js.length
1005
+ st.replace(st.cond, r)
1006
+ end
1007
+ end
1008
+ #if(!(a||b))
1009
+ #=>
1010
+ #if(!a&&!b)
1011
+ if st.cond.kind_of? ECMA262::ExpLogicalNot and st.cond.val.kind_of? ECMA262::ExpParen and
1012
+ st.cond.val.val.kind_of? ECMA262::ExpLogicalOr
1013
+ a = ECMA262::ExpLogicalNot.new(st.cond.val.val.val)
1014
+ b = ECMA262::ExpLogicalNot.new(st.cond.val.val.val2)
1015
+ r = ECMA262::ExpLogicalAnd.new(a,b).add_remove_paren
1016
+ if r.to_js.length <= st.cond.to_js.length
1017
+ st.replace(st.cond, r)
1018
+ end
1019
+ end
1020
+ =end
1021
+ #if((a))
1022
+ if st.cond.kind_of? ECMA262::ExpParen
1023
+ st.replace(st.cond, st.cond.val)
1024
+ end
1025
+ #if(!!a)
1026
+ if st.cond.kind_of? ECMA262::ExpLogicalNot and st.cond.val.kind_of? ECMA262::ExpLogicalNot
1027
+ st.replace(st.cond, st.cond.val.val)
1028
+ end
1029
+ end
1030
+ }
1031
+ end
1032
+ block_to_statement
1033
+ self
1034
+ end
1035
+
1036
+ def rewrite_var(var_st, name, initializer)
1037
+ var_st.normalization
1038
+ i = 0
1039
+ var_st.vars.each do |_name, _initializer|
1040
+ if _name == name and _initializer.nil?
1041
+ var_st.vars[i] = [name, initializer]
1042
+ var_st.normalization
1043
+ return true
1044
+ end
1045
+ i += 1
1046
+ end
1047
+ false
1048
+ end
1049
+ private :rewrite_var
1050
+
1051
+ # Moves assignment expression to variable statement's initialiser
1052
+ # if possible.
1053
+ #
1054
+ # var a, b, c;
1055
+ # c = 1; a = 2;
1056
+ # =>
1057
+ # var c=1, a=2, b;
1058
+ #
1059
+ def assignment_after_var(node = @prog)
1060
+ retry_flag = true
1061
+ while retry_flag
1062
+ retry_flag = false
1063
+ node.traverse(nil) {|parent, st|
1064
+ if st.kind_of? ECMA262::StVar and parent.kind_of? ECMA262::SourceElements
1065
+ catch(:break){
1066
+ idx = parent.index(st) + 1
1067
+ while true
1068
+ st2 = parent[idx]
1069
+ if st2.kind_of? ECMA262::StEmpty or (st2.kind_of? ECMA262::StFunc and st2.decl?)
1070
+ idx +=1
1071
+ next
1072
+ elsif st2.kind_of? ECMA262::StExp and st2.exp.kind_of? ECMA262::ExpAssign
1073
+ if rewrite_var(st, st2.exp.val, st2.exp.val2)
1074
+ parent.replace(st2, ECMA262::StEmpty.new())
1075
+ retry_flag = true
1076
+ else
1077
+ throw :break
1078
+ end
1079
+ idx += 1
1080
+ next
1081
+ elsif st2.kind_of? ECMA262::StFor and st2.exp1.kind_of? ECMA262::ExpAssign
1082
+ if rewrite_var(st, st2.exp1.val, st2.exp1.val2)
1083
+ st2.replace(st2.exp1, nil)
1084
+ retry_flag = true
1085
+ else
1086
+ throw :break
1087
+ end
1088
+ throw :break
1089
+ elsif st2.kind_of? ECMA262::StExp and st2.exp.kind_of? ECMA262::ExpComma
1090
+ exp_parent = st2
1091
+ exp = st2.exp
1092
+
1093
+ while exp.val.kind_of? ECMA262::ExpComma
1094
+ exp_parent = exp
1095
+ exp = exp.val
1096
+ end
1097
+
1098
+ if exp.val.kind_of? ECMA262::ExpAssign
1099
+ if rewrite_var(st, exp.val.val, exp.val.val2)
1100
+ exp_parent.replace(exp, exp.val2)
1101
+ retry_flag = true
1102
+ else
1103
+ throw :break
1104
+ end
1105
+ else
1106
+ throw :break
1107
+ end
1108
+ else
1109
+ throw :break
1110
+ end
1111
+ end
1112
+ }
1113
+ end
1114
+ }
1115
+ end
1116
+ self
1117
+ end
1118
+ end
1119
+ end
1120
+ =begin
1121
+ if $0 == __FILE__
1122
+ argv = ARGV.dup
1123
+ f = []
1124
+ options = {}
1125
+ argv.each do |x|
1126
+ if x.match(/^--?version/)
1127
+ puts Minjs::VERSION
1128
+ exit(0)
1129
+ elsif x.match(/^--?/)
1130
+ opt = $'.gsub(/-/, '_').to_sym
1131
+ options[opt] = true
1132
+ else
1133
+ f.push(open(x.to_s).read())
1134
+ end
1135
+ end
1136
+
1137
+ js = f.join("\n")
1138
+
1139
+ comp = Minjs::Compressor::Compressor.new(:debug => false)
1140
+ comp.compress(js, options)
1141
+ comp_js = comp.to_js(options)
1142
+ #p comp_js.length
1143
+ js = comp_js
1144
+ puts js
1145
+ end
1146
+ =end