Skip to content

Commit

Permalink
JS: improve compression for if/else statements with return/throw; and…
Browse files Browse the repository at this point in the history
… a bugfix
  • Loading branch information
tdewolff committed Apr 5, 2022
1 parent 4532c88 commit abab019
Show file tree
Hide file tree
Showing 3 changed files with 27 additions and 26 deletions.
3 changes: 2 additions & 1 deletion js/js_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ func TestJS(t *testing.T) {
{`if(a)a++;else b;if(b)b++;else c`, `a?a++:b,b?b++:c`},
{`if(a){while(b);}`, `if(a)for(;b;);`},
{`if(a){while(b);c}`, `if(a){for(;b;);c}`},
{`if(a){if(b){while(c);}}`, `if(a)if(b)for(;c;);`},
{`if(a){if(b){while(c);}}`, `if(a&&b)for(;c;);`},
{`if(a){}else{while(b);}`, `if(a);else for(;b;);`},
{`if(a){return b}else{while(c);}`, `if(a)return b;for(;c;);`},
{`if(a){return b}else{while(c);d}`, `if(a)return b;for(;c;);d`},
Expand All @@ -261,6 +261,7 @@ func TestJS(t *testing.T) {
{`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){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`},

// var declarations
{`var a;var b;a,b`, `var a,b;a,b`},
Expand Down
42 changes: 25 additions & 17 deletions js/stmtlist.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,44 +22,50 @@ func optimizeStmt(i js.IStmt) js.IStmt {
if unaryExpr, ok := ifStmt.Cond.(*js.UnaryExpr); ok && unaryExpr.Op == js.NotToken {
left := groupExpr(unaryExpr.X, binaryLeftPrecMap[js.OrToken])
right := groupExpr(X.Value, binaryRightPrecMap[js.OrToken])
return &js.ExprStmt{Value: &js.BinaryExpr{Op: js.OrToken, X: left, Y: right}}
return &js.ExprStmt{&js.BinaryExpr{js.OrToken, left, right}}
}
left := groupExpr(ifStmt.Cond, binaryLeftPrecMap[js.AndToken])
right := groupExpr(X.Value, binaryRightPrecMap[js.AndToken])
return &js.ExprStmt{Value: &js.BinaryExpr{Op: js.AndToken, X: left, Y: right}}
return &js.ExprStmt{&js.BinaryExpr{js.AndToken, left, right}}
} else if X, isIfStmt := ifStmt.Body.(*js.IfStmt); isIfStmt && isEmptyStmt(X.Else) {
left := groupExpr(ifStmt.Cond, binaryLeftPrecMap[js.AndToken])
right := groupExpr(X.Cond, binaryRightPrecMap[js.AndToken])
ifStmt.Cond = &js.BinaryExpr{js.AndToken, left, right}
ifStmt.Body = X.Body
return ifStmt
}
} else if !hasIf && hasElse {
ifStmt.Else = optimizeStmt(ifStmt.Else)
if X, isExprElse := ifStmt.Else.(*js.ExprStmt); isExprElse {
left := groupExpr(ifStmt.Cond, binaryLeftPrecMap[js.OrToken])
right := groupExpr(X.Value, binaryRightPrecMap[js.OrToken])
return &js.ExprStmt{Value: &js.BinaryExpr{Op: js.OrToken, X: left, Y: right}}
return &js.ExprStmt{&js.BinaryExpr{js.OrToken, left, right}}
}
} else if hasIf && hasElse {
ifStmt.Body = optimizeStmt(ifStmt.Body)
ifStmt.Else = optimizeStmt(ifStmt.Else)
XExpr, isExprBody := ifStmt.Body.(*js.ExprStmt)
YExpr, isExprElse := ifStmt.Else.(*js.ExprStmt)
if isExprBody && isExprElse {
return &js.ExprStmt{Value: condExpr(ifStmt.Cond, XExpr.Value, YExpr.Value)}
return &js.ExprStmt{condExpr(ifStmt.Cond, XExpr.Value, YExpr.Value)}
}
XReturn, isReturnBody := ifStmt.Body.(*js.ReturnStmt)
YReturn, isReturnElse := ifStmt.Else.(*js.ReturnStmt)
if isReturnBody && isReturnElse {
if XReturn.Value == nil && YReturn.Value == nil {
return &js.ReturnStmt{Value: commaExpr(ifStmt.Cond, &js.UnaryExpr{
return &js.ReturnStmt{commaExpr(ifStmt.Cond, &js.UnaryExpr{
Op: js.VoidToken,
X: &js.LiteralExpr{TokenType: js.NumericToken, Data: zeroBytes},
X: &js.LiteralExpr{js.NumericToken, zeroBytes},
})}
} else if XReturn.Value != nil && YReturn.Value != nil {
return &js.ReturnStmt{Value: condExpr(ifStmt.Cond, XReturn.Value, YReturn.Value)}
return &js.ReturnStmt{condExpr(ifStmt.Cond, XReturn.Value, YReturn.Value)}
}
return ifStmt
}
XThrow, isThrowBody := ifStmt.Body.(*js.ThrowStmt)
YThrow, isThrowElse := ifStmt.Else.(*js.ThrowStmt)
if isThrowBody && isThrowElse {
return &js.ThrowStmt{Value: condExpr(ifStmt.Cond, XThrow.Value, YThrow.Value)}
return &js.ThrowStmt{condExpr(ifStmt.Cond, XThrow.Value, YThrow.Value)}
}
}
} else if decl, ok := i.(*js.VarDecl); ok {
Expand Down Expand Up @@ -126,20 +132,22 @@ func optimizeStmtList(list []js.IStmt, blockType blockType) []js.IStmt {
}
j := 0 // write index
for i := 0; i < len(list); i++ { // read index
if ifStmt, ok := list[i].(*js.IfStmt); ok && !isEmptyStmt(ifStmt.Else) && isFlowStmt(lastStmt(ifStmt.Body)) {
if ifStmt, ok := list[i].(*js.IfStmt); ok && !isEmptyStmt(ifStmt.Else) {
// if(!a)b;else c => if(a)c; else b
if unary, ok := ifStmt.Cond.(*js.UnaryExpr); ok && unary.Op == js.NotToken {
if unary, ok := ifStmt.Cond.(*js.UnaryExpr); ok && unary.Op == js.NotToken && isFlowStmt(lastStmt(ifStmt.Else)) {
ifStmt.Cond = unary.X
ifStmt.Body, ifStmt.Else = ifStmt.Else, ifStmt.Body
}
// if body ends in flow statement (return, throw, break, continue), we can remove the else statement and put its body in the current scope
if blockStmt, ok := ifStmt.Else.(*js.BlockStmt); ok {
blockStmt.Scope.Unscope()
list = append(list[:i+1], append(blockStmt.List, list[i+1:]...)...)
} else {
list = append(list[:i+1], append([]js.IStmt{ifStmt.Else}, list[i+1:]...)...)
if isFlowStmt(lastStmt(ifStmt.Body)) {
// if body ends in flow statement (return, throw, break, continue), we can remove the else statement and put its body in the current scope
if blockStmt, ok := ifStmt.Else.(*js.BlockStmt); ok {
blockStmt.Scope.Unscope()
list = append(list[:i+1], append(blockStmt.List, list[i+1:]...)...)
} else {
list = append(list[:i+1], append([]js.IStmt{ifStmt.Else}, list[i+1:]...)...)
}
ifStmt.Else = nil
}
ifStmt.Else = nil
}

list[i] = optimizeStmt(list[i])
Expand Down
8 changes: 0 additions & 8 deletions js/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -367,14 +367,6 @@ func isFlowStmt(stmt js.IStmt) bool {
func lastStmt(stmt js.IStmt) js.IStmt {
if block, ok := stmt.(*js.BlockStmt); ok && 0 < len(block.List) {
return lastStmt(block.List[len(block.List)-1])
} else if ifStmt, ok := stmt.(*js.IfStmt); ok {
if isEmptyStmt(ifStmt.Else) {
if block, ok := ifStmt.Else.(*js.BlockStmt); !ok || len(block.List) == 1 {
return lastStmt(ifStmt.Else)
}
} else if block, ok := ifStmt.Body.(*js.BlockStmt); !ok || len(block.List) == 1 {
return lastStmt(ifStmt.Body)
}
}
return stmt
}
Expand Down

0 comments on commit abab019

Please sign in to comment.