resolve constant expressions
This commit is contained in:
271
lib/compress.js
271
lib/compress.js
@@ -60,6 +60,9 @@ function Compressor(options, false_by_default) {
|
||||
keep_comps : !false_by_default,
|
||||
drop_debugger : !false_by_default,
|
||||
unsafe : !false_by_default,
|
||||
ifs : !false_by_default,
|
||||
comparations : !false_by_default,
|
||||
evaluate : !false_by_default,
|
||||
|
||||
warnings : true
|
||||
});
|
||||
@@ -86,6 +89,7 @@ function Compressor(options, false_by_default) {
|
||||
});
|
||||
|
||||
function make_node(ctor, orig, props) {
|
||||
if (!props) props = {};
|
||||
if (!props.start) props.start = orig.start;
|
||||
if (!props.end) props.end = orig.end;
|
||||
return new ctor(props);
|
||||
@@ -106,23 +110,6 @@ function Compressor(options, false_by_default) {
|
||||
});
|
||||
};
|
||||
|
||||
SQUEEZE(AST_Debugger, function(self, compressor){
|
||||
if (compressor.option("drop_debugger"))
|
||||
return new AST_EmptyStatement(self);
|
||||
});
|
||||
|
||||
SQUEEZE(AST_LabeledStatement, function(self, compressor){
|
||||
self = self.clone();
|
||||
self.body = self.body.squeeze(compressor);
|
||||
return self.label.references.length == 0 ? self.body : self;
|
||||
});
|
||||
|
||||
SQUEEZE(AST_Statement, function(self, compressor){
|
||||
self = self.clone();
|
||||
self.body = self.body.squeeze(compressor);
|
||||
return self;
|
||||
});
|
||||
|
||||
function tighten_body(statements, compressor) {
|
||||
statements = do_list(statements, compressor);
|
||||
statements = eliminate_spurious_blocks(statements);
|
||||
@@ -219,6 +206,238 @@ function Compressor(options, false_by_default) {
|
||||
return statements;
|
||||
}
|
||||
|
||||
/* -----[ boolean/negation helpers ]----- */
|
||||
|
||||
// methods to determine whether an expression has a boolean result type
|
||||
(function (def){
|
||||
var unary_bool = [ "!", "delete" ];
|
||||
var binary_bool = [ "in", "instanceof", "==", "!=", "===", "!==", "<", "<=", ">=", ">" ];
|
||||
def(AST_Node, function(){ return false });
|
||||
def(AST_UnaryPrefix, function(){
|
||||
return member(this.operator, unary_bool);
|
||||
});
|
||||
def(AST_Binary, function(){
|
||||
return member(this.operator, binary_bool) ||
|
||||
( (this.operator == "&&" || this.operator == "||") &&
|
||||
this.left.is_boolean() && this.right.is_boolean() );
|
||||
});
|
||||
def(AST_Conditional, function(){
|
||||
return this.consequent.is_boolean() && this.alternative.is_boolean();
|
||||
});
|
||||
def(AST_Assign, function(){
|
||||
return this.operator == "=" && this.right.is_boolean();
|
||||
});
|
||||
def(AST_Seq, function(){
|
||||
return this.second.is_boolean();
|
||||
});
|
||||
def(AST_True, function(){ return true });
|
||||
def(AST_False, function(){ return true });
|
||||
})(function(node, func){
|
||||
node.DEFMETHOD("is_boolean", func);
|
||||
});
|
||||
|
||||
// methods to determine if an expression has a string result type
|
||||
(function (def){
|
||||
def(AST_Node, function(){ return false });
|
||||
def(AST_String, function(){ return true });
|
||||
def(AST_UnaryPrefix, function(){
|
||||
return this.operator == "typeof";
|
||||
});
|
||||
def(AST_Binary, function(){
|
||||
return this.operator == "+" &&
|
||||
(this.left.is_string() || this.right.is_string());
|
||||
});
|
||||
def(AST_Assign, function(){
|
||||
return this.operator == "=" && this.right.is_string();
|
||||
});
|
||||
})(function(node, func){
|
||||
node.DEFMETHOD("is_string", func);
|
||||
});
|
||||
|
||||
// function best_of(ast1, ast2) {
|
||||
// return ast1.print_to_string().length > ast2.print_to_string().length ? ast2 : ast1;
|
||||
// };
|
||||
|
||||
// methods to evaluate a constant expression
|
||||
(function (def){
|
||||
AST_Node.DEFMETHOD("evaluate", function(compressor, constant, not_constant){
|
||||
if (!compressor.option("evaluate")) return this;
|
||||
try {
|
||||
var val = this._eval(), ast;
|
||||
switch (typeof val) {
|
||||
case "string":
|
||||
ast = make_node(AST_String, this, {
|
||||
value: val
|
||||
});
|
||||
break;
|
||||
case "number":
|
||||
ast = make_node(AST_Number, this, {
|
||||
value: val
|
||||
});
|
||||
break;
|
||||
case "boolean":
|
||||
ast = make_node(val ? AST_True : AST_False, this);
|
||||
break;
|
||||
case "undefined":
|
||||
ast = make_node(AST_Undefined, this);
|
||||
break;
|
||||
default:
|
||||
if (val === null) {
|
||||
ast = make_node(AST_Null, this);
|
||||
break;
|
||||
}
|
||||
throw new Error(string_template("Can't handle constant of type: {type}", {
|
||||
type: typeof val
|
||||
}));
|
||||
}
|
||||
if (constant) return constant(ast, val);
|
||||
return ast;
|
||||
} catch(ex) {
|
||||
if (ex !== def) throw ex;
|
||||
if (not_constant) return not_constant(this);
|
||||
return this;
|
||||
}
|
||||
});
|
||||
function evaluate(node) {
|
||||
return node._eval();
|
||||
};
|
||||
def(AST_Node, function(){
|
||||
throw def; // not constant
|
||||
});
|
||||
def(AST_Constant, function(){
|
||||
return this.getValue();
|
||||
});
|
||||
def(AST_UnaryPrefix, function(){
|
||||
var e = this.expression;
|
||||
switch (this.operator) {
|
||||
case "!": return !evaluate(e);
|
||||
case "typeof": return typeof evaluate(e);
|
||||
case "~": return ~evaluate(e);
|
||||
case "-": return -evaluate(e);
|
||||
case "+": return +evaluate(e);
|
||||
}
|
||||
throw def;
|
||||
});
|
||||
def(AST_Binary, function(){
|
||||
var left = this.left, right = this.right;
|
||||
switch (this.operator) {
|
||||
case "&&" : return evaluate(left) && evaluate(right);
|
||||
case "||" : return evaluate(left) || evaluate(right);
|
||||
case "|" : return evaluate(left) | evaluate(right);
|
||||
case "&" : return evaluate(left) & evaluate(right);
|
||||
case "^" : return evaluate(left) ^ evaluate(right);
|
||||
case "+" : return evaluate(left) + evaluate(right);
|
||||
case "*" : return evaluate(left) * evaluate(right);
|
||||
case "/" : return evaluate(left) / evaluate(right);
|
||||
case "%" : return evaluate(left) % evaluate(right);
|
||||
case "-" : return evaluate(left) - evaluate(right);
|
||||
case "<<" : return evaluate(left) << evaluate(right);
|
||||
case ">>" : return evaluate(left) >> evaluate(right);
|
||||
case ">>>" : return evaluate(left) >>> evaluate(right);
|
||||
case "==" : return evaluate(left) == evaluate(right);
|
||||
case "===" : return evaluate(left) === evaluate(right);
|
||||
case "!=" : return evaluate(left) != evaluate(right);
|
||||
case "!==" : return evaluate(left) !== evaluate(right);
|
||||
case "<" : return evaluate(left) < evaluate(right);
|
||||
case "<=" : return evaluate(left) <= evaluate(right);
|
||||
case ">" : return evaluate(left) > evaluate(right);
|
||||
case ">=" : return evaluate(left) >= evaluate(right);
|
||||
case "in" : return evaluate(left) in evaluate(right);
|
||||
case "instanceof" : return evaluate(left) instanceof evaluate(right);
|
||||
}
|
||||
throw def;
|
||||
});
|
||||
def(AST_Conditional, function(){
|
||||
return evaluate(this.condition)
|
||||
? evaluate(this.consequent)
|
||||
: evaluate(this.alternative);
|
||||
});
|
||||
})(function(node, func){
|
||||
node.DEFMETHOD("_eval", func);
|
||||
});
|
||||
|
||||
// method to negate an expression
|
||||
(function(def){
|
||||
function basic_negation(exp) {
|
||||
return make_node(AST_UnaryPrefix, exp, {
|
||||
operator: "!",
|
||||
expression: exp
|
||||
});
|
||||
};
|
||||
def(AST_Node, function(){
|
||||
return basic_negation(this);
|
||||
});
|
||||
def(AST_Statement, function(){
|
||||
throw new Error("Cannot evaluate a statement");
|
||||
});
|
||||
def(AST_UnaryPrefix, function(){
|
||||
if (this.operator == "!" && this.expression.is_boolean())
|
||||
return this.expression();
|
||||
return basic_negation(this);
|
||||
});
|
||||
def(AST_Seq, function(compressor){
|
||||
var self = this.clone();
|
||||
self.second = self.second.negate(compressor);
|
||||
return self;
|
||||
});
|
||||
def(AST_Conditional, function(){
|
||||
var self = this.clone();
|
||||
self.consequent = self.consequent.negate(compressor);
|
||||
self.alternative = self.alternative.negate(compressor);
|
||||
//return best_of(basic_negation(this), self);
|
||||
return self;
|
||||
});
|
||||
def(AST_Binary, function(compressor){
|
||||
var self = this.clone(), op = this.operator;
|
||||
if (compressor.option("comparations")) switch (op) {
|
||||
case "<=" : self.operator = ">" ; return self;
|
||||
case "<" : self.operator = ">=" ; return self;
|
||||
case ">=" : self.operator = "<" ; return self;
|
||||
case ">" : self.operator = "<=" ; return self;
|
||||
}
|
||||
switch (op) {
|
||||
case "==" : self.operator = "!="; return self;
|
||||
case "!=" : self.operator = "=="; return self;
|
||||
case "===": self.operator = "!=="; return self;
|
||||
case "!==": self.operator = "==="; return self;
|
||||
case "&&":
|
||||
self.operator = "||";
|
||||
self.left = self.left.negate(compressor);
|
||||
self.right = self.right.negate(compressor);
|
||||
//return best_of(basic_negation(this), self);
|
||||
return self;
|
||||
case "||":
|
||||
self.operator = "&&";
|
||||
self.left = self.left.negate(compressor);
|
||||
self.right = self.right.negate(compressor);
|
||||
//return best_of(basic_negation(this), self);
|
||||
return self;
|
||||
}
|
||||
return basic_negation(this);
|
||||
});
|
||||
})(function(node, func){
|
||||
node.DEFMETHOD("negate", func);
|
||||
});
|
||||
|
||||
/* -----[ node squeezers ]----- */
|
||||
|
||||
SQUEEZE(AST_Debugger, function(self, compressor){
|
||||
if (compressor.option("drop_debugger"))
|
||||
return new AST_EmptyStatement(self);
|
||||
});
|
||||
|
||||
SQUEEZE(AST_LabeledStatement, function(self, compressor){
|
||||
self = self.clone();
|
||||
self.body = self.body.squeeze(compressor);
|
||||
return self.label.references.length == 0 ? self.body : self;
|
||||
});
|
||||
|
||||
SQUEEZE(AST_Statement, function(self, compressor){
|
||||
self = self.clone();
|
||||
self.body = self.body.squeeze(compressor);
|
||||
return self;
|
||||
});
|
||||
|
||||
SQUEEZE(AST_BlockStatement, function(self, compressor){
|
||||
self = self.clone();
|
||||
self.body = tighten_body(self.body, compressor);
|
||||
@@ -279,9 +498,14 @@ function Compressor(options, false_by_default) {
|
||||
self.body = self.body.squeeze(compressor);
|
||||
if (self.alternative)
|
||||
self.alternative = self.alternative.squeeze(compressor);
|
||||
if (!compressor.option("ifs")) return self;
|
||||
return self;
|
||||
});
|
||||
|
||||
AST_If.DEFMETHOD("switch_branches", function(){
|
||||
|
||||
});
|
||||
|
||||
SQUEEZE(AST_Switch, function(self, compressor){
|
||||
self = self.clone();
|
||||
self.expression = self.expression.squeeze(compressor);
|
||||
@@ -372,7 +596,20 @@ function Compressor(options, false_by_default) {
|
||||
return self;
|
||||
});
|
||||
|
||||
SQUEEZE(AST_UnaryPrefix, function(self, compressor){
|
||||
self = self.clone();
|
||||
self.expression = self.expression.squeeze(compressor);
|
||||
return self.evaluate(compressor);
|
||||
});
|
||||
|
||||
SQUEEZE(AST_Binary, function(self, compressor){
|
||||
self = self.clone();
|
||||
self.left = self.left.squeeze(compressor);
|
||||
self.right = self.right.squeeze(compressor);
|
||||
return self.evaluate(compressor);
|
||||
});
|
||||
|
||||
SQUEEZE(AST_Assign, function(self, compressor){
|
||||
self = self.clone();
|
||||
self.left = self.left.squeeze(compressor);
|
||||
self.right = self.right.squeeze(compressor);
|
||||
|
||||
Reference in New Issue
Block a user