lib/minjs/compressor.rb in minjs-0.1.10 vs lib/minjs/compressor.rb in minjs-0.2.0

- old
+ new

@@ -51,78 +51,58 @@ def compress(data, options = {}) @logger.info '* parse' parse(data) - @logger.info '* reorder_function_decl' - reorder_function_decl + if options[:only_parse] + return + end - @logger.info '* simple_replacement' - simple_replacement + algo = [ + :reorder_function_decl, + :simple_replacement, + :reorder_var, + :assignment_after_var, + :grouping_statement, + :reduce_if, + :block_to_statement, + :if_to_cond, + :optimize_if_return, + :compress_var, + :reduce_exp, + :grouping_statement, + :block_to_statement, + :if_to_cond, + :optimize_if_return2, + :remove_paren, + ] + algo.each do |a| + if (options.empty? || options[:all] || options[a]) && !options[("no_" + a.to_s).to_sym] + @logger.info "* #{a}" + __send__(a, @prog) + end + end - @logger.info '* reorder_var' - reorder_var - - @logger.info '* assignment_after_var' - assignment_after_var - - @logger.info '* grouping_statement' - grouping_statement - - @logger.info '* reduce_if' - reduce_if - - @logger.info '* block_to_statement' - block_to_statement - - @logger.info '* if_to_cond' - if_to_cond - - @logger.info '* optimize_if_return' - optimize_if_return - - @logger.info '* compress_var' - compress_var(@prog, :longer => true) - compress_var - - @logger.info '* reduce_exp' - reduce_exp - - grouping_statement - block_to_statement - if_to_cond - - #feature - optimize_if_return2 - - @logger.info '* remove_paren' - remove_paren - @heading_comments.reverse.each do |c| @prog.source_elements.source_elements.unshift(c) end - to_js(options) + self end def parse(data) @lex = Minjs::Lex.new(data, :logger => @logger) @global_context = ECMA262::Context.new - @heading_comments = [] @lex.eval_lit{ while a = @lex.ws_lit @heading_comments.push(a) end nil } @prog = source_elements(@lex, @global_context) - #a = @prog.deep_dup - #a == @prog - remove_empty_statement - #@prog self end def next_sym(s) def c2i(c) @@ -189,18 +169,22 @@ remove_paren self end def reorder_function_decl(node = @prog) + flist = [] node.traverse(nil) {|st, parent| if st.kind_of? ECMA262::StFunc and parent.kind_of? ECMA262::StatementList and st.decl? if parent.index(st) - parent.remove(st) - parent.source_elements.unshift(st) + flist.push([st, parent]) end end } + flist.reverse.each do |st, parent| + parent.remove(st) + parent.statement_list.unshift(st) + end self end def reorder_var(node = @prog) node.traverse(nil) {|st, parent| @@ -252,17 +236,22 @@ end ) idx = 0 elems.each do |e| + next if e.kind_of? ECMA262::StFunc and e.decl? found = false - e.traverse(nil){|ee, pp| - if ee.kind_of? ECMA262::IdentifierName and var_vars[ee.val.to_sym] - found = true - break - end - } + if e.kind_of? ECMA262::StFunc and e.decl? + ; + else + e.traverse(nil){|ee, pp| + if ee.kind_of? ECMA262::IdentifierName and var_vars[ee.val.to_sym] + found = true + break + end + } + end break if found idx += 1 end if idx == 0 @@ -273,39 +262,10 @@ st.source_elements.remove_empty_statement end end self } -=begin - st.traverse(parent) {|st2, parent2| - # - #if var statment has initializer, - # - # - if st2.kind_of? ECMA262::StVar and st2.context.var_env == @context.var_env - st2.instance_eval{ - blk = [] - @vars.each do |vl| - if vl[1] - blk.push(ECMA262::StExp.new(ECMA262::ExpAssign.new(vl[0], vl[1]))) - else - end - end - parent2.replace(st2, ECMA262::StBlock.new(ECMA262::StatementList.new(blk))) - } - elsif st2.kind_of? ECMA262::StForVar and st2.context.var_env == @context.var_env - parent2.replace(st2, st2.to_st_for) - elsif st2.kind_of? ECMA262::StForInVar and st2.context.var_env == @context.var_env - parent2.replace(st2, st2.to_st_for_in) - end - } - - } - end - } - remove_block_in_block -=end self end def remove_paren(node = @prog) node.traverse(nil) {|st, parent| @@ -507,80 +467,97 @@ end } self end - def compress_var(node = @prog, options = {}) - if options[:longer] - var_sym = :aaaaaaaaaa - end + def compress_var(node = @prog) + #compress_var_sub(@prog, :longer => true) + compress_var_sub + end + def compress_var_sub(node = @prog, options = {}) # #traverse all statemtns and expression # scopes = [] node.traverse(nil) {|st, parent| - if st.kind_of? ECMA262::StTry and st.catch - _context = st.catch_context - _parent = st - _st = st.catch[1] - elsif st.kind_of? ECMA262::StFunc + if st.kind_of? ECMA262::StFunc _context = st.context _parent = parent _st = st - else - _parent = nil - _context = nil - _st = nil - end - if _parent and _context and _st scopes.push([st, parent, _parent, _context, _st]) end } - #node.traverse(nil) {|st, parent| - scopes.reverse.each {|st, parent, _parent, _context, _st| - #p "*#{st.name.to_js}" - if !options[:longer] - var_sym = :a - end + scopes.reverse! + scopes.each {|st, parent, _parent, _context, _st| + var_sym = :a if _parent and _context and _st # # collect and counting all variables under this function/catch + # collect and counting all var-variables under this function/catch # - vars = {} - if st.kind_of? ECMA262::StTry - vars[st.catch[0].val.to_sym] = 1 - end + all_vars = {} + var_vars = {} + var_vars_list = [] + outer_vars = {} + nesting_vars = {} + nesting_vars_list = [] + _st.traverse(_parent) {|st2| + # + # In this function, + # + # 1. outer_vars: + # Variables which locate out of this function(or global variable) + # Them name cannot be renamed + # 2. nesting_vars: + # Variables which locate in the function of this function. + # Them name cannot be renamed + # 3. var_vars: + # Variables which have same scope in this function. + # Them name can be renamed under the following conditions + # + # a. If the new name is not used, the name can be renamed to it. + # b. If the new name belongs to var_vars, the name cannot be renamed. + # c. If the new name belongs to outer_vars the name cannot be renamed. + # d. If the new name belongs to nesting_vars, the name can be rename + # to it after rename nesting_vars's name to another name. + # if st2.kind_of? ECMA262::IdentifierName - vars[st2.val.to_sym] ||= 0 - vars[st2.val.to_sym] += 1 + var_name = st2.val.to_sym + st2_env = st2.binding_env + all_vars[var_name] ||= 0 + all_vars[var_name] += 1 + if st2_env == nil #global + outer_vars[var_name] ||= 0 + outer_vars[var_name] += 1 + elsif st2_env == @global_context.var_env #global + outer_vars[var_name] ||= 0 + outer_vars[var_name] += 1 + elsif st2_env == st.context.var_env + var_vars[var_name] ||= 0 + var_vars[var_name] += 1 + var_vars_list.push(st2) + else + e = st2.binding_env + while e + e = e.outer + if e == st.context.var_env + nesting_vars[var_name] ||= 0 + nesting_vars[var_name] += 1 + nesting_vars_list.push(st2) + break + end + if e.nil? + outer_vars[var_name] ||= 0 + outer_vars[var_name] += 1 + break + end + end + end end } - # - # collect all var variables under this function - # - var_vars = {} - if st.kind_of? ECMA262::StFunc - _context.var_env.record.binding.each do|k, v| - var_vars[k] = (vars[k] || 1) - end - end - # - # collect all lexical variables under this catch clause - # - # currently, only catch's args is lex_var - # - lex_vars = {} - if st.kind_of? ECMA262::StTry - _context.lex_env.record.binding.each do|k, v| - lex_vars[k] = (vars[k] || 1) - end - end - # - # check `eval' function is exist under this function/catch - # unless var_vars[:eval] eval_flag = false _st.traverse(_parent) {|st2| if st2.kind_of? ECMA262::ExpCall and st2.name.to_js({}) == "eval" eval_flag = true @@ -592,101 +569,87 @@ end end # # sort var_vars # - var_vars = var_vars.sort {|(k1,v1), (k2,v2)| v2 <=> v1} - #p var_vars + var_vars_array = var_vars.sort {|(k1,v1), (k2,v2)| v2 <=> v1} # - # check var_vars + # create renaming table # - var_vars.each {|name, count| + rename_table = {} + var_vars_array.each {|name, count| if name.nil? - next + next #bug? end - while(vars[var_sym]) + while outer_vars[var_sym] or var_vars[var_sym] var_sym = next_sym(var_sym) end - if (!options[:longer] && name.to_s.bytesize >= var_sym.to_s.bytesize) or - (options[:longer] && name.to_s.bytesize <= var_sym.to_s.bytesize) - # - # rename `name' to `var_sym' - # - func_name = nil - if _st.kind_of? ECMA262::StFunc and _st.decl? - func_name = _st.name + #rename nesting_vars + if nesting_vars[var_sym] + nesting_vars_list.each do |x| + raise 'error' if x.binding_env(:var).nil? + raise 'error' if x.binding_env(:lex).nil? end - _st.traverse(_parent){|st2| - if st2.kind_of? ECMA262::IdentifierName and st2.context.nil? - ;# this - elsif st2.kind_of? ECMA262::IdentifierName and st2.context.var_env.outer == nil # global scope - ; - elsif st2.kind_of? ECMA262::IdentifierName and st2.val == name - # scope of function's name is outer - if st2.eql?(func_name) - else - st2.instance_eval{ - @val = var_sym - } - end - elsif st2.kind_of? ECMA262::StFunc - if st2.context.var_env.record.binding[name] - st2.context.var_env.record.binding[var_sym] = st2.context.var_env.record.binding[name] - st2.context.var_env.record.binding.delete(name) - end - elsif st2.kind_of? ECMA262::StTry - if st2.catch_context.lex_env.record.binding[name] - st2.catch_context.lex_env.record.binding[var_sym] = st2.catch_context.lex_env.record.binding[name] - st2.catch_context.lex_env.record.binding.delete(name) - end + + var_sym2 = "abc#{var_sym.to_s}".to_sym + while all_vars[var_sym2] + var_sym2 = next_sym(var_sym2) + end + rl = {} + nesting_vars_list.each do |x| + if x.val.to_sym == var_sym + _var_env = x.binding_env(:var) + _lex_env = x.binding_env(:lex) + rl[_var_env] = true + rl[_lex_env] = true end - } - end - var_sym = next_sym(var_sym) - } - lex_vars.each {|name, count| - if name.nil? - next - end - while(vars[var_sym]) - var_sym = next_sym(var_sym) - end - if name.to_s.bytesize > var_sym.to_s.bytesize - # - # rename `name' to `var_sym' - # - _st.traverse(_parent){|st2| - if st2.kind_of? ECMA262::IdentifierName and st2.context.nil? - ;# TODO, currently special identifier such as 'this' has no context - elsif st2.kind_of? ECMA262::IdentifierName and st2.context.lex_env.outer == nil # global scope - ; - elsif st2.kind_of? ECMA262::IdentifierName and st2.val == name - st2.instance_eval{ - @val = var_sym - } + end + rl.keys.each do |_env| + if _env && _env.record.binding[var_sym] + _env.record.binding[var_sym2] = _env.record.binding[var_sym] + _env.record.binding.delete var_sym end - } - if st.kind_of? ECMA262::StTry - if st.catch[0].kind_of? ECMA262::IdentifierName - st.catch[0].instance_eval{ - @val = var_sym + end + + nesting_vars_list.each do |x| + if x.val.to_sym == var_sym + x.instance_eval{ + @val = var_sym2 } - elsif st2.kind_of? ECMA262::StFunc - if st2.context.var_env.record.binding[name] - st2.context.var_env.record.binding[var_sym] = st2.context.var_env.record.binding[name] - st2.context.var_env.record.binding.delete(name) - end - elsif st2.kind_of? ECMA262::StTry - if st2.catch_context.lex_env.record.binding[name] - st2.catch_context.lex_env.record.binding[var_sym] = st2.catch_context.lex_env.record.binding[name] - st2.catch_context.lex_env.record.binding.delete(name) - end end + raise 'error' if x.binding_env(:var).nil? + raise 'error' if x.binding_env(:lex).nil? end end + rename_table[name] = var_sym var_sym = next_sym(var_sym) } + var_vars_list.each {|st2| + raise 'error' if st2.binding_env(:var).nil? + raise 'error' if st2.binding_env(:lex).nil? + } + + rename_table.each do |name, new_name| + if name != new_name + if st.context.var_env.record.binding[name] + st.context.var_env.record.binding[new_name] = st.context.var_env.record.binding[name] + st.context.var_env.record.binding.delete(name) + end + if st.context.lex_env.record.binding[name] + st.context.lex_env.record.binding[new_name] = st.context.lex_env.record.binding[name] + st.context.lex_env.record.binding.delete(name) + end + end + end + + var_vars_list.each {|st2| + st2.instance_eval{ + @val = rename_table[@val] + } + raise 'error' if st2.binding_env(:var).nil? + raise 'error' if st2.binding_env(:lex).nil? + } end } self end @@ -698,10 +661,11 @@ } self end def simple_replacement(node = @prog) + retry_flag = false node.traverse(nil) {|st, parent| # #true => !0 #false => !1 # @@ -713,24 +677,11 @@ end # #if(true){<then>}else{<else>} => then # elsif st.kind_of? ECMA262::StIf - #if(a)z;else; - #if(a)z;else{} - # => {if(a)z;} - if st.else_st and st.else_st.empty? - st.replace(st.else_st, nil) - parent.replace(st, ECMA262::StBlock.new([st])) - end - #if(a); - # => a - #if(a){} - # => a - if st.then_st.empty? and st.else_st.nil? - parent.replace(st, ECMA262::StExp.new(st.cond)) - elsif st.cond.respond_to? :to_ecma262_boolean + if st.cond.respond_to? :to_ecma262_boolean if st.cond.to_ecma262_boolean parent.replace(st, st.then_st) elsif st.else_st parent.replace(st, st.else_st) else @@ -745,39 +696,72 @@ if st.exp.to_ecma262_boolean parent.replace(st, ECMA262::StFor.new(nil,nil,nil, st.statement)) else parent.replace(st, ECMA262::StEmpty.new) end + # + # new A() => (new A) + # + elsif st.kind_of? ECMA262::ExpNew and st.args and st.args.length == 0 + st.replace(st.args, nil) + parent.add_paren.remove_paren end } self end # # reduce_if # - # 1) rewrite nested "if" statemet such as: - # if(a) - # if(b) ...; - # - # to: - # - # if(a && b) ...; - # - # NOTE: - # both if must not have "else" clause - # def reduce_if(node = @prog) - node.traverse(nil) {|st, parent| - if st.kind_of? ECMA262::StIf - if st.else_st.nil? and - st.then_st.kind_of? ECMA262::StIf and st.then_st.else_st.nil? - st.replace(st.cond, ECMA262::ExpLogicalAnd.new(st.cond, st.then_st.cond)) - st.replace(st.then_st, st.then_st.then_st) + retry_flag = true + while(retry_flag) + retry_flag = false + node.traverse(nil) {|st, parent| + if st.kind_of? ECMA262::StIf + # if(a) + # if(b) ...; + # if(a && b) ...; + # + if st.else_st.nil? and + st.then_st.kind_of? ECMA262::StIf and st.then_st.else_st.nil? + st.replace(st.cond, ECMA262::ExpLogicalAnd.new(st.cond, st.then_st.cond)) + st.replace(st.then_st, st.then_st.then_st) + end + #if(a); + # => a + #if(a){} + # => a + if st.then_st.empty? and st.else_st.nil? + parent.replace(st, ECMA262::StExp.new(st.cond)) + retry_flag = true + end + #if(a)z;else; + #if(a)z;else{} + # => {if(a)z;} + if st.else_st and st.else_st.empty? + st.replace(st.else_st, nil) + parent.replace(st, ECMA262::StBlock.new([st])) + retry_flag = true + end + + #if(a);else z; + #=>if(!a)z; + #if(a){}else z; + #=>if(!a)z; + if st.then_st.empty? and st.else_st + st.replace(st.cond, ECMA262::ExpLogicalNot.new(st.cond)); + else_st = st.else_st + st.replace(st.else_st, nil) + st.replace(st.then_st, else_st) + parent.replace(st, ECMA262::StBlock.new([st])) + retry_flag = true + end end - end - } + } + block_to_statement if retry_flag + end self end def assignment_after_var(node = @prog) def rewrite_var(var_st, name, initializer) @@ -856,12 +840,18 @@ end if $0 == __FILE__ argv = ARGV.dup f = [] + options = {} argv.each do |x| - f.push(open(x.to_s).read()) + if x.match(/^--?/) + opt = $'.gsub(/-/, '_').to_sym + options[opt] = true + else + f.push(open(x.to_s).read()) + end end comp = Minjs::Compressor.new(:debug => false) - comp.compress(f.join("\n")) - puts comp.to_js({}) + comp.compress(f.join("\n"), options) + puts comp.to_js(options) end