lib/minjs/compressor.rb in minjs-0.3.0 vs lib/minjs/compressor.rb in minjs-0.4.0

- old
+ new

@@ -1,1144 +1,7 @@ -#!/usr/bin/env ruby -# coding: utf-8 -require 'minjs/lex' -require 'minjs/ecma262' -require 'minjs/statement' -require 'minjs/expression' -require 'minjs/func' -require 'minjs/program' -require 'minjs/exceptions' -require 'minjs/version' -require 'logger' - module Minjs - class Compressor - include Statement - include Exp - include Func - include Program - include Ctype - - attr_reader :prog - - def initialize(options = {}) - @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 - puts @prog.to_js() - end - - def to_js(options = {}) - remove_empty_statement - @prog.to_js(options).sub(/;;\Z/, ";") - end - - def remove_empty_statement(node = @prog) - node.traverse(nil) {|st, parent| - if st.kind_of? ECMA262::StatementList - st.remove_empty_statement - end - } - self - end - - def compress(data, options = {}) - @logger.info '* parse' - parse(data) - - if options[:only_parse] - return - end - - algo = [ - :reorder_function_decl, - :simple_replacement, - :reorder_var, - :assignment_after_var, - :grouping_statement, - :block_to_statement, - :reduce_if, - :if_to_cond, - :optimize_if_return, - :compress_var, - :reduce_exp, - :grouping_statement, - :block_to_statement, - :if_to_cond, - :optimize_if_return2, - :block_to_statement, - :add_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 - - @heading_comments.reverse.each do |c| - @prog.source_elements.source_elements.unshift(c) - end - self - end - - def parse(data) - @lex = Minjs::Lex.new(data, :logger => @logger) - @global_context = ECMA262::Context.new - @heading_comments = [] - - while a = (@lex.comment || @lex.line_terminator || @lex.white_space) - @heading_comments.push(a) - end - while @heading_comments.last == ECMA262::LIT_LINE_FEED and - !(@heading_comments[-2].kind_of?(ECMA262::SingleLineComment)) - @heading_comments.pop - end - @prog = program(@lex, @global_context) - - remove_empty_statement - @lex.clear_cache - self - end - - def next_sym(s) - def c2i(c) - c = c.ord - if c >= 0x30 and c <= 0x39 - c = c - 0x30 - elsif c >= 0x61 and c <= 0x7a - c = c - 0x61 + 10 - elsif c >= 0x41 and c <= 0x5a - c = c - 0x41 + 10 + 26 - elsif c == 0x5f - c = 62 - elsif c == 0x24 - c = 63 - end - end - def i2c(c) - if c < 10 - c = "%c" % (0x30 + c) - elsif c < 10 + 26 - c = "%c" % (0x61 + c - 10) - elsif c < 10 + 26 + 26 - c = "%c" % (0x41 + c - 10 - 26) - elsif c < 63 - c = "_" - elsif c < 64 - c = "$" - end - end - - v = 0 - s.to_s.split("").each do |x| - v *= 64 - v += c2i(x) - end - - while true - v += 1 - ret = [] - vv = v - while vv > 0 - ret.unshift(i2c(vv % 64)) - vv /= 64 - end - ret = ret.join("") - if ECMA262::IdentifierName.reserved?(ret.to_sym) - ; - elsif ret.to_s.match(/^\d/) - ; - else - break - end - end - ret.to_sym - - end - - def grouping_statement(node = @prog) - node.traverse(nil) {|st, parent| - if st.kind_of? ECMA262::StatementList - st.grouping - end - } - add_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) - flist.push([st, parent]) - end - end - } - flist.reverse.each do |st, parent| - parent.remove(st) - sl = parent.statement_list - if sl[0].kind_of? ECMA262::StExp and sl[0].exp.kind_of? ECMA262::ECMA262String and sl[0].exp.val == "use strict" - sl[1,0] = st - else - sl.unshift(st) - end - end - self - end - - def reorder_var(node = @prog) - node.traverse(nil) {|st, parent| - if st.kind_of? ECMA262::Prog - vars = nil - context = st.context - # - # collect all of var variable in this function - # - var_vars = {} - context.var_env.record.binding.each do|k, v| - if v and v[:_parameter_list].nil? and !v[:value].kind_of?(ECMA262::StFunc) - var_vars[k] = true - end - end - # - # traverse block and convert var statement to assignment expression - # if variable has initializer - # - st.traverse(parent){|st2, parent2| - if st2.kind_of? ECMA262::StVar and st2.context.var_env == context.var_env - exp = nil - st2.vars.each do |name, initializer| - if initializer - if exp.nil? - exp = ECMA262::ExpAssign.new(name, initializer) - else - exp = ECMA262::ExpComma.new(exp, ECMA262::ExpAssign.new(name, initializer)) - end - end - end - if exp - parent2.replace(st2, ECMA262::StExp.new(exp)) - else - parent2.replace(st2, ECMA262::StEmpty.new()) - end - 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 - } - if var_vars.length > 0 - elems = st.source_elements.source_elements - v = ECMA262::StVar.new( - context, - var_vars.collect do |k, v| - [ECMA262::IdentifierName.new(context, k)] - end - ) - - idx = 0 - elems.each do |e| - found = false - if e.kind_of? ECMA262::StFunc and e.decl? - ; - elsif e.kind_of? ECMA262::StExp and e.exp.kind_of? ECMA262::ECMA262String and e.exp.val == "use strict" - ; - 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 - elems.unshift(v) - else - elems[idx..0] = v - end - st.source_elements.remove_empty_statement - end - end - self - } - self - end - - def add_remove_paren(node = @prog) - node.traverse(nil) {|st, parent| - if st.respond_to? :remove_paren - st.remove_paren - st.add_paren - end - } - self - end - - def remove_paren(node = @prog) - add_remove_paren(node) - end - - # - # To determine removing "block" is available or not is difficult. - # For example, next code's if-block must not be removed, because - # "else" cluase combined to second "if" statement. - # - # if(a){ //<= this block must not be removed - # while(true) - # if(b){ - # ; - # } - # } - # else{ - # ; - # } - # - # The next code's while-block must not be removed, because - # "else" cluase combined to second "if" statement. - # - # if(a) - # while(true){ //<= this block must not be removed - # if(b){ - # ; - # } - # } - # else{ - # ; - # } - # - # To solve this problem, first, every then-clause without block - # converts to block statement. After converted, all blocks - # except then-clause can be removed safety. - # - def then_to_block(node = @prog) - node.traverse(nil) {|st, parent| - if st.kind_of? ECMA262::StIf - if !st.then_st.kind_of?(ECMA262::StBlock) - st.replace(st.then_st, ECMA262::StBlock.new([st.then_st])) - end - end - } - end - - def block_to_statement(node = @prog) - remove_empty_statement - then_to_block - node.traverse(nil) {|st, parent| - if st.kind_of? ECMA262::StBlock and !parent.kind_of?(ECMA262::StTry) and !parent.kind_of?(ECMA262::StIf) - if st.to_statement? - parent.replace(st, st.to_statement) - end - end - } - if_block_to_statement - end - - def if_block_to_statement(node = @prog) - remove_empty_statement - # The "else" cluase's block can be removed always - node.traverse(nil) {|st, parent| - if st.kind_of? ECMA262::StIf - if st.else_st and st.else_st.kind_of? ECMA262::StBlock - st.else_st.remove_empty_statement - end - - if st.else_st and st.else_st.kind_of? ECMA262::StBlock and st.else_st.to_statement? - st.replace(st.else_st, st.else_st.to_statement) - end - end - } - node.traverse(nil) {|st, parent| - if st.kind_of? ECMA262::StIf - if st.then_st and st.then_st.kind_of? ECMA262::StBlock - st.then_st.remove_empty_statement - end - if !st.else_st and st.then_st.kind_of? ECMA262::StBlock and st.then_st.to_statement? - st.replace(st.then_st, st.then_st.to_statement) - elsif st.then_st.kind_of? ECMA262::StBlock and st.then_st.to_statement? - _st = st.then_st - st2 = st.then_st.to_statement - while true - if st2.kind_of? ECMA262::StVar or st2.kind_of? ECMA262::StEmpty or - st2.kind_of? ECMA262::StExp or st2.kind_of? ECMA262::StBlock or - st2.kind_of? ECMA262::StDoWhile or st2.kind_of? ECMA262::StSwitch or - st2.kind_of? ECMA262::StContinue or st2.kind_of? ECMA262::StBreak or - st2.kind_of? ECMA262::StReturn or st2.kind_of? ECMA262::StThrow or - st2.kind_of? ECMA262::StTry or st2.kind_of? ECMA262::StDebugger - st.replace(st.then_st, st.then_st.to_statement) - break; - elsif st2.kind_of? ECMA262::StWhile or - st2.kind_of? ECMA262::StFor or - st2.kind_of? ECMA262::StForIn or - st2.kind_of? ECMA262::StForVar or - st2.kind_of? ECMA262::StForInVar or - st2.kind_of? ECMA262::StWith or - st2.kind_of? ECMA262::StLabelled - st2 = st2.statement - elsif st2.kind_of? ECMA262::StIf - if st2.else_st - st2 = st2.else_st - else - break - end - else #? - break - end - end - end - end - } -=begin - node.traverse(nil) {|st0, parent| - st = st0.deep_dup - if st.kind_of? ECMA262::StIf - if st.then_st and st.then_st.kind_of? ECMA262::StBlock - st.then_st.remove_empty_statement - end - - if st.then_st and st.then_st.kind_of? ECMA262::StBlock and st.then_st.to_statement? - st.replace(st.then_st, st.then_st.to_statement) - end - - _lex = Minjs::Lex.new(st.to_js) - _context = ECMA262::Context.new - _if = if_statement(_lex, _context) - reduce_exp(_if) - if _if == st # - if st0.then_st and st0.then_st.kind_of? ECMA262::StBlock - st0.then_st.remove_empty_statement - end - if st0.then_st and st0.then_st.kind_of? ECMA262::StBlock and st0.then_st.to_statement? - st0.replace(st0.then_st, st0.then_st.to_statement) - end - else - p '!=' - puts st.to_js - puts _if.to_js - end - end - } -=end - self - end - - # - # if(a)b;else c; - # => - # a?b:c - # - # if(a)b - # => - # a&&b; or a?b:0; - # - # NOTE: - # Sometimes, "conditional operator" will be shorter than - # "logical and operator", because "conditional operator"'s - # priority is lower than almost all other expressions. - # - def if_to_cond(node = @prog) - node.traverse(nil) {|st, parent| - if st.kind_of? ECMA262::StIf - if st.to_exp? - t = ECMA262::StExp.new(st.to_exp({})) - t2 = ECMA262::StExp.new(st.to_exp({cond: true})) - if t2.to_js.length < t.to_js.length - t = t2 - end - add_remove_paren(t) - simple_replacement(t) - - if t.to_js.length <= st.to_js.length - parent.replace(st, t) - end - end - end - } - if_to_return(node) - self - end - # - # if(a)return b;else return c; - # => return a?b:c; - # - def if_to_return(node = @prog) - node.traverse(nil) {|st, parent| - if st.kind_of? ECMA262::StIf - if st.to_return? - t = st.to_return - add_remove_paren(t) - simple_replacement(t) - if t.to_js.length <= st.to_js.length - parent.replace(st, t) - end - end - end - } - self - end - - # - # if(a)return b; - # return c; - # - # => return a?b:c; - # - def optimize_if_return(node = @prog) - node.traverse(nil) {|st0, parent0| - if st0.kind_of? ECMA262::StatementList - st0.remove_empty_statement - st = st0.deep_dup - while true - #check last statement - ls = st.statement_list[-1] - ls2 = st.statement_list[-2] - if st.kind_of? ECMA262::SourceElements and !(ls.kind_of? ECMA262::StReturn) - ls2 = ls - ls = ECMA262::StReturn.new(ECMA262::ExpVoid.new(ECMA262::ECMA262Numeric.new(0))) - end - break if ls.nil? - break if ls2.nil? - break if !ls.to_return? - break if !ls2.kind_of?(ECMA262::StIf) - break if ls2.else_st - break if !ls2.then_st.to_return? - -# if !ls2.then_st.kind_of? ECMA262::StIf and !ls2.then_st.to_return? -# break -# end -# if ls2.then_st.kind_of? ECMA262::StIf and !ls2.then_to_return? -# break -# end - - then_exp = ls2.then_st.to_return.exp - else_exp = ls.to_return.exp - then_exp = ECMA262::ExpVoid.new(ECMA262::ECMA262Numeric.new(0)) if then_exp.nil? - else_exp = ECMA262::ExpVoid.new(ECMA262::ECMA262Numeric.new(0)) if else_exp.nil? - if ls2.cond.kind_of? ECMA262::ExpLogicalNot - cond = ECMA262::ExpCond.new(ls2.cond.val, else_exp, then_exp) - else - cond = ECMA262::ExpCond.new(ls2.cond, then_exp, else_exp) - end - ret = ECMA262::StReturn.new(cond) - #puts ret.to_js - #puts ls2.to_js - st.replace(ls2, ret) - st.remove(ls) - end - if st0.to_js.length > st.to_js.length - parent0.replace(st0, st) - end - end - } - self - end - - # - # if(a)return b;else c; - # => - # if(a)return b;c; - # - def optimize_if_return2(node = @prog) - node.traverse(nil) {|st, parent| - if st.kind_of? ECMA262::StIf and st.else_st and parent.kind_of? ECMA262::StatementList - st.remove_empty_statement - if (st.then_st.kind_of? ECMA262::StBlock and st.then_st[-1].kind_of? ECMA262::StReturn) or - st.then_st.kind_of? ECMA262::StReturn - idx = parent.index(st) - parent[idx+1..0] = st.else_st - st.replace(st.else_st, nil) - elsif (st.else_st.kind_of? ECMA262::StBlock and st.else_st[-1].kind_of? ECMA262::StReturn) or - st.else_st.kind_of? ECMA262::StReturn - idx = parent.index(st) - parent[idx+1..0] = st.then_st - st.instance_eval{ - @then_st = @else_st - @else_st = nil - @cond = ECMA262::ExpLogicalNot.new(@cond) - } - end - end - } - self - end - - def compress_var(node = @prog, options = {}) - func_scopes = [] - catch_scopes = [] - with_scopes = [] - # - # ECMA262 10.2: - # - # Usually a Lexical Environment is associated with some - # specific syntactic structure of ECMAScript code such as a - # FunctionDeclaration, a WithStatement, or a Catch clause of a - # TryStatement and a new Lexical Environment is created each - # time such code is evaluated. - # - node.traverse(nil) {|st, parent| - if st.kind_of? ECMA262::StFunc - func_scopes.push([st, parent]) - elsif st.kind_of? ECMA262::StTry - catch_scopes.push([st, parent]) - elsif st.kind_of? ECMA262::StWith - with_scopes.push([st, parent]) - end - } - # - # 10.2, 12.14 - # - #eee = 'global'; - #function test() - #{ - # /* - # "eee" is local variable(belongs to this function) - # because var declaration is exist in this function. - # (see also catch's scope comment) - # So, global variable 'eee' is not changed. - # */ - # eee = 'function'; - # try{ - # console.log(eee); //=>function - # throw "exception"; - # } - # catch(eee){ - # /* - # The catch's variable scope will be created at execution time. - # so next var declaration should belong to "test" function. - # */ - # var eee; - # /* - # In execution time, "eee" belongs to this - # catch-clause's scope. - # */ - # console.log(eee); //=>exception - # /* - # Next function has its own scope and 'eee' belongs to its. - # */ - # (function(){ - # var eee; - # console.log(eee); //=>undefined - # })(); - # } - #} - #console.log(eee); //=>global - #test(); - # - catch_scopes.each{|st, parent| - if st.catch - catch_context = ECMA262::Context.new - catch_context.lex_env = st.context.lex_env.new_declarative_env() - catch_context.var_env = st.context.var_env - catch_context.lex_env.record.create_mutable_binding(st.catch[0], nil) - catch_context.lex_env.record.set_mutable_binding(st.catch[0], :undefined, nil) - st.catch[0].context = catch_context - - st.catch[1].traverse(parent){|st2| - if st2.kind_of? ECMA262::IdentifierName and st2 == st.catch[0] and st2.binding_env == st.catch[0].binding_env - st2.context = catch_context - end - } - func_scopes.unshift([st, parent]) - end - } -# with_scopes.each{|st, parent| -# with_context = ECMA262::Context.new -# with_context.lex_env = st.context.lex_env.new_declarative_env() -# with_context.var_env = st.context.var_env -# st.statement.traverse(st) {|st2| -# if st2.kind_of? ECMA262::IdentifierName and st2.binding_env == st.context.var_env -# st2.context = with_context -# with_context.lex_env.record.create_mutable_binding(st2, nil) -# with_context.lex_env.record.set_mutable_binding(st2, :undefined, nil) -# end -# } -# } - func_scopes.reverse! - func_scopes.each {|st, parent| - if st.kind_of? ECMA262::StFunc - context = st.context - elsif st.kind_of? ECMA262::StTry - context = st.catch[0].context - end - var_sym = :a - # - # collect and counting all variables under this function - # - all_vars = {} - var_vars = {} - var_vars_list = [] - outer_vars = {} - nesting_vars = {} - nesting_vars_list = [] - - st.traverse(parent) {|st2| - # - # Next, tring to rename var_vars(see bellow) to - # new name. - # - # 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. - # 4. all_vars: - # All variables under this function. - # - # a. If the new name is not in all_vars, 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 renaming nesting_vars's name to another name. - # - if st2.kind_of? ECMA262::IdentifierName - var_name = st2.val.to_sym - #st2_var_env = st2.binding_env - st2_lex_env = st2.binding_env(:lex) - all_vars[var_name] ||= 0 - all_vars[var_name] += 1 - if st2_lex_env == nil #global - outer_vars[var_name] ||= 0 - outer_vars[var_name] += 1 - elsif st2_lex_env == @global_context.lex_env #global - outer_vars[var_name] ||= 0 - outer_vars[var_name] += 1 - elsif st2_lex_env == context.lex_env - var_vars[var_name] ||= 0 - var_vars[var_name] += 1 - var_vars_list.push(st2) - else - e = st2.binding_env(:lex) - while e - e = e.outer - if e == context.lex_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 - } - 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 - break - end - if st2.kind_of? ECMA262::StWith - eval_flag = true - break - end - } - if eval_flag - next - end - end - # - # sort var_vars - # - var_vars_array = var_vars.sort {|(k1,v1), (k2,v2)| v2 <=> v1} - # - # create renaming table - # - rename_table = {} - var_vars_array.each {|name, count| - if name.nil? - next #bug? - end - if name.length == 1 - #STDERR.puts "#{name}=>#{count}" - next - end - #STDERR.puts "trying to rename #{name}(#{count})" - while true - #condition b - if outer_vars[var_sym] - #STDERR.puts "outer_vars has #{var_sym}" - elsif var_vars[var_sym] - #STDERR.puts "var_vars has #{var_sym}(#{var_vars[var_sym]})" - #condigion c - else #condition a&d - #STDERR.puts "->#{var_sym}" - break - end - var_sym = next_sym(var_sym) - end - #rename nesting_vars - if nesting_vars[var_sym] - #STDERR.puts "nesting_vars has #{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 - - var_sym2 = "XXX#{var_sym.to_s}".to_sym - while all_vars[var_sym2] - var_sym2 = next_sym(var_sym2) - end - #STDERR.puts "#{var_sym}->#{var_sym2}" - 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 - 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 - end - - nesting_vars_list.each do |x| - if x.val.to_sym == var_sym - x.instance_eval{ - @val = var_sym2 - } - end - #raise 'error' if x.binding_env(:var).nil? - raise x.to_js 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 st2.to_js if st2.binding_env(:lex).nil? - } - - rename_table.each do |name, new_name| - if name != new_name - if context.var_env.record.binding[name] - context.var_env.record.binding[new_name] = context.var_env.record.binding[name] - context.var_env.record.binding.delete(name) - end - if context.lex_env.record.binding[name] - context.lex_env.record.binding[new_name] = context.lex_env.record.binding[name] - context.lex_env.record.binding.delete(name) - end - end - end - - var_vars_list.each {|st2| - st2.instance_eval{ - if rename_table[@val] - @val = rename_table[@val] - #raise 'error' if st2.binding_env(:var).nil? - raise st2.to_js if st2.binding_env(:lex).nil? - end - } - } - } - self - end - - def reduce_exp(node = @prog) - node.traverse(nil) {|st, parent| - if st.kind_of? ECMA262::Exp - st.reduce(parent) - end - } - self - end - - 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)))) - else - parent.replace(st, ECMA262::ExpParen.new(ECMA262::ExpLogicalNot.new(ECMA262::ECMA262Numeric.new(1)))) - end - # - #if(true){<then>}else{<else>} => <then> - #if(false){<then>}else{<else>} => <else> - # - elsif st.kind_of? ECMA262::StIf - if st.cond.respond_to? :to_ecma262_boolean - if st.cond.to_ecma262_boolean.nil? - ; - elsif st.cond.to_ecma262_boolean == true - parent.replace(st, st.then_st) - elsif st.cond.to_ecma262_boolean == false and st.else_st - parent.replace(st, st.else_st) - elsif st.cond.to_ecma262_boolean == false - parent.replace(st, ECMA262::StEmpty.new) - end - end - # - # while(true) => for(;;) - # while(false) => remove - # - elsif st.kind_of? ECMA262::StWhile and st.exp.respond_to? :to_ecma262_boolean - if st.exp.to_ecma262_boolean.nil? - ; - elsif 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 - # - # !c?a:b => c?b:a - # true?a:b => a - # false?a:b => b - # - elsif st.kind_of? ECMA262::ExpCond - if st.val.kind_of? ECMA262::ExpLogicalNot - st.instance_eval{ - @val = @val.val - t = @val2 - @val2 = @val3 - @val3 = t - } - simple_replacement(st) - end - - if st.val.respond_to? :to_ecma262_boolean - if st.val.to_ecma262_boolean.nil? - ; - elsif st.val.to_ecma262_boolean - parent.replace(st, st.val2) - else - parent.replace(st, st.val3) - end - end - # - # A["B"] => A.N - # - elsif st.kind_of? ECMA262::ExpPropBrac and st.val2.kind_of? ECMA262::ECMA262String - if idname?(st.val2.val) - parent.replace(st, ECMA262::ExpProp.new(st.val, st.val2)) - 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 - st.replace(st.val2, v) - end - end - } - self - end - - # - # reduce_if - # - def reduce_if(node = @prog) - 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)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 - break - 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 - break - 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)) - end -=begin - #if(!(a&&b)) - #=> - #if(!a||!b) - if st.cond.kind_of? ECMA262::ExpLogicalNot and st.cond.val.kind_of? ECMA262::ExpParen and - st.cond.val.val.kind_of? ECMA262::ExpLogicalAnd - a = ECMA262::ExpLogicalNot.new(st.cond.val.val.val) - b = ECMA262::ExpLogicalNot.new(st.cond.val.val.val2) - r = ECMA262::ExpLogicalOr.new(a,b).add_remove_paren - if r.to_js.length <= st.cond.to_js.length - st.replace(st.cond, r) - end - end - #if(!(a||b)) - #=> - #if(!a&&!b) - if st.cond.kind_of? ECMA262::ExpLogicalNot and st.cond.val.kind_of? ECMA262::ExpParen and - st.cond.val.val.kind_of? ECMA262::ExpLogicalOr - a = ECMA262::ExpLogicalNot.new(st.cond.val.val.val) - b = ECMA262::ExpLogicalNot.new(st.cond.val.val.val2) - r = ECMA262::ExpLogicalAnd.new(a,b).add_remove_paren - if r.to_js.length <= st.cond.to_js.length - st.replace(st.cond, r) - end - end -=end - #if((a)) - if st.cond.kind_of? ECMA262::ExpParen - st.replace(st.cond, st.cond.val) - end - #if(!!a) - if st.cond.kind_of? ECMA262::ExpLogicalNot and st.cond.val.kind_of? ECMA262::ExpLogicalNot - st.replace(st.cond, st.cond.val.val) - end - end - } - end - block_to_statement - self - end - - def assignment_after_var(node = @prog) - def rewrite_var(var_st, name, initializer) - var_st.normalization - i = 0 - var_st.vars.each do |_name, _initializer| - if _name == name and _initializer.nil? - var_st.vars[i] = [name, initializer] - var_st.normalization - return true - end - i += 1 - end - false - end - - retry_flag = true - while retry_flag - retry_flag = false - node.traverse(nil) {|st, parent| - if st.kind_of? ECMA262::StVar and parent.kind_of? ECMA262::SourceElements - catch(:break){ - idx = parent.index(st) + 1 - while true - st2 = parent[idx] - if st2.kind_of? ECMA262::StEmpty or (st2.kind_of? ECMA262::StFunc and st2.decl?) - idx +=1 - next - elsif st2.kind_of? ECMA262::StExp and st2.exp.kind_of? ECMA262::ExpAssign - if rewrite_var(st, st2.exp.val, st2.exp.val2) - parent.replace(st2, ECMA262::StEmpty.new()) - retry_flag = true - else - throw :break - end - idx += 1 - next - elsif st2.kind_of? ECMA262::StFor and st2.exp1.kind_of? ECMA262::ExpAssign - if rewrite_var(st, st2.exp1.val, st2.exp1.val2) - st2.replace(st2.exp1, nil) - retry_flag = true - else - throw :break - end - throw :break - elsif st2.kind_of? ECMA262::StExp and st2.exp.kind_of? ECMA262::ExpComma - exp_parent = st2 - exp = st2.exp - - while exp.val.kind_of? ECMA262::ExpComma - exp_parent = exp - exp = exp.val - end - - if exp.val.kind_of? ECMA262::ExpAssign - if rewrite_var(st, exp.val.val, exp.val.val2) - exp_parent.replace(exp, exp.val2) - retry_flag = true - else - throw :break - end - else - throw :break - end - else - throw :break - end - end - } - end - } - end - self - end + # Compressor + module Compressor end end -if $0 == __FILE__ - argv = ARGV.dup - f = [] - options = {} - argv.each do |x| - if x.match(/^--?version/) - puts Minjs::VERSION - exit(0) - elsif x.match(/^--?/) - opt = $'.gsub(/-/, '_').to_sym - options[opt] = true - else - f.push(open(x.to_s).read()) - end - end - - js = f.join("\n") - - comp = Minjs::Compressor.new(:debug => false) - comp.compress(js, options) - comp_js = comp.to_js(options) - #p comp_js.length - js = comp_js - puts js - -end +require 'minjs/compressor/compressor'