Skip to content

Commit

Permalink
JS: improve compression of and/or expressions
Browse files Browse the repository at this point in the history
  • Loading branch information
tdewolff committed Apr 8, 2022
1 parent 09c93b1 commit ef36774
Show file tree
Hide file tree
Showing 2 changed files with 86 additions and 44 deletions.
18 changes: 10 additions & 8 deletions js/js_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ func TestJS(t *testing.T) {
{`if(a,b)b`, `a,b&&b`},
{`if(a,b)b;else d`, `a,b||d`},
{`if(a=b)a;else b`, `(a=b)||b`},
{`if(!a&&!b){return true}else if(!a||!b){return false}return c&&d`, `return!a&&!b||!(!a||!b)&&c&&d`},
{`if(!a&&!b){return true}else if(!a||!b){return false}return c&&d`, `return!a&&!b||!!a&&!!b&&c&&d`},
{`if(!a){if(b){throw c}else{return c}}else{return a}`, `if(a)return a;if(b)throw c;return c`},
{`if(!a){return y}else if(b){if(c){return x}}return z`, `return a?b&&c?x:z:y`},

Expand Down Expand Up @@ -545,7 +545,7 @@ func TestJS(t *testing.T) {
{`a?!0:!1`, `!!a`},
{`a?0:1`, `a?0:1`},
{`!!a?0:1`, `!!a?0:1`},
//{`a&&b?!1:!0`, `!a||!b`}, // TODO
{`a&&b?!1:!0`, `!a||!b`},
{`a&&b?!0:!1`, `!!(a&&b)`},
{`a?true:5`, `!!a||5`},
{`a?5:false`, `!!a&&5`},
Expand Down Expand Up @@ -600,13 +600,15 @@ func TestJS(t *testing.T) {
{`a??=b`, `a??=b`},
{`a==false`, `a==!1`},
{`a===false`, `a===!1`},
//{`!(a||b)`, `!a&&!b`}, // TODO
//{`!(a&&b)`, `!a||!b`}, // TODO
//{`!(!a||!b)`, `a&&b`}, // we don't know of a or b are booleans
//{`!(!a&&!b)`, `a||b`},
//{`!(!a&&b)&&c`, `(a||!b)&&c`},
{`!(a||b)`, `!a&&!b`},
{`!(a&&b)`, `!a||!b`},
{`!(a&&b)&&c`, `!(a&&b)&&c`},
//{`a===false||b===true?false:true`, `a!==!1&&b!==!0`}, // TODO
{`c&&!(a&&b===5)`, `c&&!(a&&b===5)`},
{`c&&!(!a&&b!==5)`, `c&&!(!a&&b!==5)`},
{`c&&!(a==3&&b!==5)`, `c&&(a!=3||b===5)`},
{`!(a>=0&&a<=1||a>=2&&a<=3)`, `!(a>=0&&a<=1||a>=2&&a<=3)`},
{`a===false||b===true?false:true`, `a!==!1&&b!==!0`},
//{`!(!(a>=0||a<=1)&&!(a>=2||a<=3))`, `!!(a>=0||a<=1||a>=2||a<=3)`}, // TODO

// other
{`async function g(){await x+y}`, `async function g(){await x+y}`},
Expand Down
112 changes: 76 additions & 36 deletions js/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -591,44 +591,84 @@ func optimizeBooleanExpr(expr js.IExpr, invert bool, prec js.OpPrec) js.IExpr {
}

func optimizeUnaryExpr(expr *js.UnaryExpr, prec js.OpPrec) js.IExpr {
//if group, ok := expr.X.(*js.GroupExpr); ok && expr.Op == js.NotToken {
// if binary, ok := group.X.(*js.BinaryExpr); ok && (binary.Op == js.AndToken || binary.Op == js.OrToken) {
// op := js.AndToken
// if binary.Op == js.AndToken {
// op = js.OrToken
// }
// precInside := binaryOpPrecMap[op]
return expr
if group, ok := expr.X.(*js.GroupExpr); ok && expr.Op == js.NotToken {
if binary, ok := group.X.(*js.BinaryExpr); ok && (binary.Op == js.AndToken || binary.Op == js.OrToken) {
op := js.AndToken
if binary.Op == js.AndToken {
op = js.OrToken
}
precInside := binaryOpPrecMap[op]
needsGroup := precInside < prec && (precInside != js.OpCoalesce || prec != js.OpBitOr)

// rewrite !(a||b) to !a&&!b
// rewrite !(a==0||b==0) to a!=0&&b!=0
score := 3 // savings if rewritten (group parentheses and not-token)
if needsGroup {
score -= 2
}
score -= 2 // add two not-tokens for left and right

// // rewrite !(a||b) to !a&&!b
// // rewrite !(a==0||b==0) to a!=0&&b!=0
// var isNotX, isNotY, isEqX, isEqY bool
// if unaryExpr, ok := binary.X.(*js.UnaryExpr); ok {
// isNotX = unaryExpr.Op == js.NotToken
// } else if binaryExpr, ok := binary.X.(*js.BinaryExpr); ok {
// isEqX = binaryOpPrecMap[binaryExpr.Op] == js.OpEquals
// }
// if unaryExpr, ok := binary.Y.(*js.UnaryExpr); ok {
// isNotY = unaryExpr.Op == js.NotToken
// } else if binaryExpr, ok := binary.Y.(*js.BinaryExpr); ok {
// isEqY = binaryOpPrecMap[binaryExpr.Op] == js.OpEquals
// }
// if !isNotX && !isNotY && (isEqX || isEqY || (prec <= precInside || precInside == js.OpCoalesce && prec == js.OpBitOr)) {
// binary.Op = op
// if isEqX {
// binary.X.(*js.BinaryExpr).Op = invertBooleanOp(binary.X.(*js.BinaryExpr).Op)
// } else {
// binary.X = &js.UnaryExpr{js.NotToken, binary.X}
// }
// if isEqY {
// == and === can become != and !==
var isEqX, isEqY bool
if binaryExpr, ok := binary.X.(*js.BinaryExpr); ok && binaryOpPrecMap[binaryExpr.Op] == js.OpEquals {
score += 1
isEqX = true
}
if binaryExpr, ok := binary.Y.(*js.BinaryExpr); ok && binaryOpPrecMap[binaryExpr.Op] == js.OpEquals {
score += 1
isEqY = true
}

// binary.Y.(*js.BinaryExpr).Op = invertBooleanOp(binary.Y.(*js.BinaryExpr).Op)
// } else {
// binary.Y = &js.UnaryExpr{js.NotToken, binary.Y}
// }
// return binary
// }
// }
//}
// left and right may need group or can remove group
var needsGroupX, needsGroupY bool
if op == js.OrToken {
// remove group
if exprPrec(binary.X) == js.OpOr {
score += 2
}
if exprPrec(binary.Y) == js.OpAnd {
score += 2
}
} else {
// add group
if !isEqX && exprPrec(binary.X) < js.OpUnary {
score -= 2
needsGroupX = true
}
if !isEqY && exprPrec(binary.Y) < js.OpUnary {
score -= 2
needsGroupY = true
}
}

if 0 < score {
binary.Op = op
if isEqX {
binary.X.(*js.BinaryExpr).Op = invertBooleanOp(binary.X.(*js.BinaryExpr).Op)
}
if isEqY {
binary.Y.(*js.BinaryExpr).Op = invertBooleanOp(binary.Y.(*js.BinaryExpr).Op)
}
if needsGroupX {
binary.X = &js.GroupExpr{binary.X}
}
if needsGroupY {
binary.Y = &js.GroupExpr{binary.Y}
}
if !isEqX {
binary.X = &js.UnaryExpr{js.NotToken, binary.X}
}
if !isEqY {
binary.Y = &js.UnaryExpr{js.NotToken, binary.Y}
}
if needsGroup {
return &js.GroupExpr{binary}
}
return binary
}
}
}
return expr
}

Expand Down

0 comments on commit ef36774

Please sign in to comment.