lib/minjs/compressor.rb in minjs-0.1.3 vs lib/minjs/compressor.rb in minjs-0.1.5

- old
+ new

@@ -1,14 +1,16 @@ #!/usr/bin/env ruby +# coding: utf-8 require 'minjs/lex' require 'minjs/ecma262' require 'minjs/literal' require 'minjs/statement' require 'minjs/expression' require 'minjs/func' require 'minjs/program' require 'minjs/exceptions' +require 'logger' module Minjs class Compressor include Literal include Statement @@ -17,49 +19,73 @@ include Program attr_reader :prog def initialize(options = {}) - @debug = false - if options[:debug] - @debug = true + @logger = options[:logger] + if !@logger + @logger = Logger.new(STDERR) + @logger.level = (options[:debug_level] || Logger::WARN) + @logger.formatter = proc{|severity, datetime, progname, message| + "#{message}\n" + } end end def debug - #@global_context.debug puts @prog.to_js() end def to_js(options = {}) @prog.to_js(options) end def compress(data, options = {}) + @logger.info '* parse' parse(data) + @logger.info '* reorder_function_decl' reorder_function_decl + + @logger.info '* simple_replacement' simple_replacement + + @logger.info '* reorder_var' reorder_var + + @logger.info '* assignment_after_var' assignment_after_var + + @logger.info '* grouping_statement' grouping_statement - block_to_exp + + @logger.info '* block_to_statement' + block_to_statement + + @logger.info '* if_to_cond' if_to_cond + + @logger.info '* compress_var' compress_var + + @logger.info '* reduce_exp' reduce_exp + + @logger.info '* remove_paren' remove_paren - #feature - #return_to_exp + + @logger.info '* return_to_exp' + return_to_exp + @heading_comments.reverse.each do |c| - @prog.source_elements.unshift(c) + @prog.source_elements.source_elements.unshift(c) end - to_js(options) end def parse(data) - @lex = Minjs::Lex.new(data) + @lex = Minjs::Lex.new(data, :logger => @logger) @global_context = ECMA262::Context.new @heading_comments = [] @lex.eval_lit{ while a = @lex.ws_lit @@ -69,13 +95,13 @@ } @prog = source_elements(@lex, @global_context) @prog end - def traverse(&block) - @prog.traverse(nil, &block) - end +# def traverse(&block) +# @prog.traverse(nil, &block) +# end def next_sym(s) def c2i(c) c = c.ord if c >= 0x30 and c <= 0x39 @@ -129,38 +155,41 @@ end ret.to_sym end - def grouping_statement - self.traverse {|st, parent| - if st.kind_of? ECMA262::Prog + def grouping_statement(node = @prog) + node.traverse(nil) {|st, parent| + if st.kind_of? ECMA262::StatementList st.grouping - elsif st.kind_of? ECMA262::StList - st.grouping end } + remove_paren + self end - def reorder_function_decl - self.traverse {|st, parent| - if st.kind_of? ECMA262::StFunc and parent.kind_of? ECMA262::Prog and st.decl? + def reorder_function_decl(node = @prog) + 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) end end } + self end - def reorder_var - #traverse all statemtns and expression - self.traverse {|st, parent| + def reorder_var(node = @prog) + node.traverse(nil) {|st, parent| if st.kind_of? ECMA262::Prog vars = nil context = nil st.instance_eval{ + # + # collect all of var variable in this function + # vars = @context.var_env.record.binding.find_all {|k, v| v and v[:_parameter_list].nil? and !v[:value].kind_of?(ECMA262::StFunc) }.collect{|x| [ ECMA262::IdentifierName.new(@context, x[0]) @@ -171,65 +200,45 @@ st2.instance_eval{ blk = [] @vars.each do |vl| if vl[1] blk.push(ECMA262::StExp.new(ECMA262::ExpAssign.new(vl[0], vl[1]))) - #parent2.replace(st2, ECMA262::StExp.new(ECMA262::ExpAssign.new(vl[0], vl[1]))) else - #parent2.replace(st2, ECMA262::StEmpty.new()) end end - parent2.replace(st2, ECMA262::StBlock.new(ECMA262::StList.new(blk))) + parent2.replace(st2, ECMA262::StBlock.new(ECMA262::StatementList.new(blk))) } elsif st2.kind_of? ECMA262::StForVar and st2.context == @context parent2.replace(st2, st2.to_st_for) elsif st2.kind_of? ECMA262::StForInVar and st2.context == @context parent2.replace(st2, st2.to_st_for_in) end } if vars.length > 0 - @source_elements.unshift ECMA262::StVar.new(@context, vars) + @source_elements.source_elements.unshift ECMA262::StVar.new(@context, vars) end } end } remove_block_in_block end - #feature - def remove_paren - self.traverse {|st, parent| - if st.kind_of? ECMA262::ExpParen - # - # ECMA262 say: - # expression statement cannot start with "function" - # - if parent.priority(st) > st.val.priority(nil) - if st.val.to_js.match(/^function/) - ; - elsif st.val.to_js.match(/^{/) - ; - else - parent.replace(st, st.val) - end - end + def remove_paren(node = @prog) + node.traverse(nil) {|st, parent| + if st.respond_to? :remove_paren + st.remove_paren end } self end - def remove_block_in_block + def remove_block_in_block(node = @prog) while true _retry = false - self.traverse {|st, parent| - if parent.kind_of? ECMA262::Prog and st.kind_of? ECMA262::StBlock + node.traverse(nil) {|st, parent| + if parent.kind_of? ECMA262::StatementList and st.kind_of? ECMA262::StBlock idx = parent.index(st) - parent.source_elements[idx..idx] = st.statement_list.statement_list - _retry = true - break - elsif parent.kind_of? ECMA262::StList and st.kind_of? ECMA262::StBlock - idx = parent.index(st) parent.statement_list[idx..idx] = st.statement_list.statement_list _retry = true break elsif st.kind_of? ECMA262::StBlock ; @@ -237,92 +246,96 @@ } break if !_retry end end - def block_to_exp - self.traverse {|st, parent| - if st.kind_of? ECMA262::StBlock and st.to_exp? - if parent.kind_of? ECMA262::StTry - else - t = st.to_exp({}) - parent.replace(st, ECMA262::StExp.new(t)) + def block_to_statement(node = @prog) + node.traverse(nil) {|st, parent| + if st.kind_of? ECMA262::StBlock and !parent.kind_of?(ECMA262::StTry) + if st.to_statement? + parent.replace(st, st.to_statement) end end } + self end - def block_to_statement - self.traverse {|st, parent| - if st.kind_of? ECMA262::StBlock and st.to_statement? - if parent.kind_of? ECMA262::StTry - else - parent.replace(st, st.to_statement) + def if_to_cond(node = nil) + node = @prog if node.nil? + node.traverse(nil) {|st, parent| + # + #feature + # + # if(a)return a; + # return b; + # => if(a)return a;else return b; + # + + if st.kind_of? ECMA262::StIf and parent.kind_of? ECMA262::StatementList + i = parent.index(st) + if parent[i+1].nil? and !parent.kind_of?(ECMA262::SourceElements) + next end + if parent[i+1].nil? or parent[i+1].to_return? + s = st + while s.kind_of? ECMA262::StIf and s.else_st and s.then_st.to_return? + s = s.else_st + end + if s and s.kind_of? ECMA262::StIf and s.then_st.to_return? + if parent[i+1] + s.replace(s.else_st, parent[i+1]) + parent.replace(parent[i+1], ECMA262::StEmpty.new) + else + s.replace(s.else_st, ECMA262::StReturn.new(ECMA262::ExpVoid.new(ECMA262::ECMA262Numeric.new(0)))) + end + end + end end } - end - - def if_to_cond - self.traverse {|st, parent| + node.traverse(nil) {|st, parent| if st.kind_of? ECMA262::StIf and st.to_exp? if t = ECMA262::StExp.new(st.to_exp({})) parent.replace(st, t) end - # feature... -=begin elsif st.kind_of? ECMA262::StIf and st.to_return? - # - # if(...) - # return a; - # return b; - # - # => if(...)return a;else return b; - # - if parent.kind_of? ECMA262::StList and st.else_st.nil? and (nxt = parent[parent.index(st) + 1]).kind_of? ECMA262::StReturn - st.replace(st.else_st, nxt) - parent.replace(nxt, ECMA262::StEmpty.new()) + t = st.to_return + if t.to_js().length < st.to_js().length parent.replace(st, st.to_return) - # - # if(...) - # return a; - # else - # return b; - # - else - parent.replace(st, st.to_return) end -=end end } + remove_paren end - def compress_var + def compress_var(node = @prog) + # #traverse all statemtns and expression - self.traverse {|st, parent| + # + node.traverse(nil) {|st, parent| if st.kind_of? ECMA262::StFunc and st.context.var_env.outer var_sym = :a # # collect all variables under this function # vars = {} st.traverse(parent) {|st2| if st2.kind_of? ECMA262::IdentifierName - vars[st2.val.to_sym] = true + vars[st2.val.to_sym] ||= 0 + vars[st2.val.to_sym] += 1 end } # # collect all var variables under this function # var_vars = {} st.context.var_env.record.binding.each do|k, v| - var_vars[k] = true + var_vars[k] = vars[k] end st.traverse(parent) {|st2| if st2.kind_of? ECMA262::StFunc st2.context.var_env.record.binding.each do|k, v| - var_vars[k] = true + var_vars[k] = vars[k] end end } # # check `eval' function is exist under this function @@ -338,13 +351,17 @@ if eval_flag next end end # + # sort var_vars + # + var_vars = var_vars.sort {|(k1,v1), (k2,v2)| v2 <=> v1} + # # check var_vars # - var_vars.each {|name, v| + var_vars.each {|name, count| if name.nil? next end while(vars[var_sym]) var_sym = next_sym(var_sym) @@ -355,33 +372,36 @@ # st.traverse(parent){|st2| if st2.kind_of? ECMA262::IdentifierName and st2.context and st2.context.var_env == st.context.var_env.outer ; elsif st2.kind_of? ECMA262::IdentifierName and st2.val == name - st2.val = var_sym + st2.instance_eval{ + @val = var_sym + } elsif st2.kind_of? ECMA262::StFunc st2.context.var_env.record.binding[var_sym] = st2.context.var_env.record.binding[name] st2.context.var_env.record.binding.delete(name) end } end var_sym = next_sym(var_sym) } end } + self end - def reduce_exp - self.traverse {|st, parent| + def reduce_exp(node = @prog) + node.traverse(nil) {|st, parent| if st.kind_of? ECMA262::Exp st.reduce(parent) end } end - def simple_replacement - self.traverse {|st, parent| + def simple_replacement(node = @prog) + node.traverse(nil) {|st, parent| #true => !0 #false => !1 if st.kind_of? ECMA262::Boolean if st.true? parent.replace(st, ECMA262::ExpParen.new(ECMA262::ExpLogicalNot.new(ECMA262::ECMA262Numeric.new(0)))) @@ -401,46 +421,35 @@ end end } end - def return_to_exp - self.traverse {|st, parent| + def return_to_exp(node = @prog) + node.traverse(nil) {|st, parent| if st.kind_of? ECMA262::StReturn - if parent.kind_of? ECMA262::Prog + if parent.kind_of? ECMA262::StatementList parent.remove_empty_statement - # - # a=1;return b; => return a=1,b; - # - # check statement: - # last one is return and its previous is expression or not - if parent.source_elements[-1] == st and (prev=parent.source_elements[-2]).class == ECMA262::StExp - if st.exp - st.replace(st.exp, ECMA262::ExpComma.new(prev.exp, st.exp)) - parent.replace(prev, ECMA262::StEmpty.new()) - end - end - elsif parent.kind_of? ECMA262::StList - parent.remove_empty_statement if parent.statement_list[-1] == st and (prev=parent.statement_list[-2]).class == ECMA262::StExp if st.exp st.replace(st.exp, ECMA262::ExpComma.new(prev.exp, st.exp)) parent.replace(prev, ECMA262::StEmpty.new()) end end + parent.remove_empty_statement end end } block_to_statement if_to_cond + self end # # var a; a=1 # => var a=1 # - def assignment_after_var - self.traverse {|st, parent| - if st.kind_of? ECMA262::StExp and parent.kind_of? ECMA262::Prog + def assignment_after_var(node = @prog) + node.traverse(nil) {|st, parent| + if st.kind_of? ECMA262::StExp and parent.kind_of? ECMA262::SourceElements if st.exp.kind_of? ECMA262::ExpAssign idx = parent.index(st) while idx > 0 idx -= 1 prevst = parent[idx]