Rewrite temporary variable elimination
This commit is contained in:
+357
-139
@@ -18,7 +18,6 @@ package org.jetbrains.kotlin.js.inline.clean
|
||||
|
||||
import com.google.dart.compiler.backend.js.ast.*
|
||||
import com.google.dart.compiler.backend.js.ast.metadata.synthetic
|
||||
import org.jetbrains.kotlin.js.inline.util.canHaveSideEffect
|
||||
import org.jetbrains.kotlin.js.inline.util.collectDefinedNames
|
||||
import org.jetbrains.kotlin.js.inline.util.collectFreeVariables
|
||||
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils
|
||||
@@ -26,196 +25,415 @@ import org.jetbrains.kotlin.js.translate.utils.JsAstUtils
|
||||
internal class TemporaryVariableElimination(private val root: JsStatement) {
|
||||
private val definitions = mutableMapOf<JsName, Int>()
|
||||
private val usages = mutableMapOf<JsName, Int>()
|
||||
private val inconsistent = mutableSetOf<JsName>()
|
||||
private val definedValues = mutableMapOf<JsName, JsExpression>()
|
||||
private val temporary = mutableSetOf<JsName>()
|
||||
private var hasChanges = false
|
||||
private val namesToProcess = mutableSetOf<JsName>()
|
||||
private val statementsToRemove = mutableSetOf<JsNode>()
|
||||
private val namesToSubstitute = mutableSetOf<JsName>()
|
||||
|
||||
fun apply(): Boolean {
|
||||
analyze()
|
||||
perform()
|
||||
cleanUp()
|
||||
return hasChanges
|
||||
}
|
||||
|
||||
private fun analyze() {
|
||||
namesToProcess.addAll(collectDefinedNames(root))
|
||||
namesToProcess += collectDefinedNames(root)
|
||||
|
||||
object : JsVisitorWithContextImpl() {
|
||||
val lastAssignedVars = mutableListOf<JsName>()
|
||||
object : RecursiveJsVisitor() {
|
||||
val currentScope = mutableSetOf<JsName>()
|
||||
var localVars = mutableSetOf<JsName>()
|
||||
|
||||
override fun visit(x: JsNameRef, ctx: JsContext<*>): Boolean {
|
||||
val name = x.name
|
||||
if (name != null && x.qualifier == null) {
|
||||
val expr = definedValues[name]
|
||||
useVariable(name)
|
||||
if (lastAssignedVars.lastOrNull() == name && name in namesToProcess) {
|
||||
lastAssignedVars.removeAt(lastAssignedVars.lastIndex)
|
||||
}
|
||||
else if (expr == null || expr.canHaveSideEffect()) {
|
||||
temporary -= name
|
||||
}
|
||||
}
|
||||
return super.visit(x, ctx)
|
||||
}
|
||||
|
||||
override fun visit(x: JsTry, ctx: JsContext<*>): Boolean {
|
||||
lastAssignedVars.clear()
|
||||
return super.visit(x, ctx)
|
||||
}
|
||||
|
||||
override fun visit(x: JsWhile, ctx: JsContext<*>): Boolean {
|
||||
lastAssignedVars.clear()
|
||||
return super.visit(x, ctx)
|
||||
}
|
||||
|
||||
override fun visit(x: JsIf, ctx: JsContext<*>): Boolean {
|
||||
accept(x.ifExpression)
|
||||
lastAssignedVars.clear()
|
||||
|
||||
accept(x.thenStatement)
|
||||
x.elseStatement?.let { accept(it) }
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
override fun visit(x: JsInvocation, ctx: JsContext<*>): Boolean {
|
||||
x.arguments.asReversed().forEach { accept(it) }
|
||||
accept(x.qualifier)
|
||||
return false
|
||||
}
|
||||
|
||||
override fun visit(x: JsArrayLiteral, ctx: JsContext<*>): Boolean {
|
||||
x.expressions.asReversed().forEach { accept(it) }
|
||||
return false
|
||||
}
|
||||
|
||||
override fun visit(x: JsBreak, ctx: JsContext<*>) = false
|
||||
|
||||
override fun visit(x: JsContinue, ctx: JsContext<*>) = false
|
||||
|
||||
override fun visit(x: JsExpressionStatement, ctx: JsContext<JsNode>): Boolean {
|
||||
override fun visitExpressionStatement(x: JsExpressionStatement) {
|
||||
val assignment = JsAstUtils.decomposeAssignmentToVariable(x.expression)
|
||||
if (assignment != null) {
|
||||
val (name, value) = assignment
|
||||
assignVariable(name, value)
|
||||
accept(value)
|
||||
if (x.synthetic && name in namesToProcess) {
|
||||
temporary += name
|
||||
}
|
||||
else if (value.canHaveSideEffect()) {
|
||||
lastAssignedVars.clear()
|
||||
}
|
||||
lastAssignedVars += name
|
||||
return false
|
||||
}
|
||||
|
||||
var statement = x
|
||||
val result = accept(x.expression)
|
||||
if (result != x.expression) {
|
||||
statement = JsExpressionStatement(result)
|
||||
ctx.replaceMe(statement)
|
||||
}
|
||||
|
||||
if (statement.expression.canHaveSideEffect()) {
|
||||
lastAssignedVars.clear()
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun visit(x: JsBinaryOperation, ctx: JsContext<*>): Boolean {
|
||||
accept(x.arg2)
|
||||
accept(x.arg1)
|
||||
return false
|
||||
}
|
||||
|
||||
override fun visit(x: JsVars, ctx: JsContext<*>): Boolean {
|
||||
for (v in x.vars) {
|
||||
val name = v.name
|
||||
val value = v.initExpression
|
||||
if (value != null) {
|
||||
if (name in namesToProcess) {
|
||||
assignVariable(name, value)
|
||||
addVar(name)
|
||||
accept(value)
|
||||
if (x.synthetic) {
|
||||
temporary += name
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
super.visitExpressionStatement(x)
|
||||
}
|
||||
|
||||
override fun visitVars(x: JsVars) {
|
||||
for (v in x.vars) {
|
||||
val name = v.name
|
||||
val value = v.initExpression
|
||||
if (value != null && name in namesToProcess) {
|
||||
assignVariable(name, value)
|
||||
addVar(name)
|
||||
accept(value)
|
||||
if (x.synthetic) {
|
||||
temporary += name
|
||||
}
|
||||
lastAssignedVars += name
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun visit(x: JsFunction, ctx: JsContext<*>): Boolean {
|
||||
x.collectFreeVariables().forEach { useVariable(it); useVariable(it) }
|
||||
return super.visit(x, ctx)
|
||||
override fun visitNameRef(nameRef: JsNameRef) {
|
||||
val name = nameRef.name
|
||||
if (name != null && nameRef.qualifier == null && name in namesToProcess) {
|
||||
useVariable(name)
|
||||
if (name !in currentScope) {
|
||||
inconsistent += name
|
||||
}
|
||||
return
|
||||
}
|
||||
super.visitNameRef(nameRef)
|
||||
}
|
||||
|
||||
override fun visit(x: JsConditional, ctx: JsContext<*>): Boolean {
|
||||
accept(x.testExpression)
|
||||
lastAssignedVars.clear()
|
||||
override fun visitBreak(x: JsBreak) { }
|
||||
|
||||
accept(x.thenExpression)
|
||||
accept(x.elseExpression)
|
||||
override fun visitContinue(x: JsContinue) { }
|
||||
|
||||
return false
|
||||
override fun visitObjectLiteral(x: JsObjectLiteral) {
|
||||
for (initializer in x.propertyInitializers) {
|
||||
accept(initializer.valueExpr)
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitFor(x: JsFor) = withNewScope { super.visitFor(x) }
|
||||
|
||||
override fun visitForIn(x: JsForIn) = withNewScope { super.visitForIn(x) }
|
||||
|
||||
override fun visitWhile(x: JsWhile) = withNewScope { super.visitWhile(x) }
|
||||
|
||||
override fun visitDoWhile(x: JsDoWhile) = withNewScope { super.visitDoWhile(x) }
|
||||
|
||||
override fun visitIf(x: JsIf) {
|
||||
accept(x.ifExpression)
|
||||
withNewScope { accept(x.thenStatement) }
|
||||
x.elseStatement?.let { withNewScope { accept(it) } }
|
||||
}
|
||||
|
||||
override fun visitCase(x: JsCase) = withNewScope { super.visitCase(x) }
|
||||
|
||||
override fun visitDefault(x: JsDefault) = withNewScope { super.visitDefault(x) }
|
||||
|
||||
override fun visitCatch(x: JsCatch) = withNewScope { super.visitCatch(x) }
|
||||
|
||||
override fun visitFunction(x: JsFunction) {
|
||||
for (freeVar in x.collectFreeVariables()) {
|
||||
useVariable(freeVar)
|
||||
useVariable(freeVar)
|
||||
}
|
||||
}
|
||||
|
||||
private inline fun <T> withNewScope(block: () -> T): T {
|
||||
val localVarsBackup = localVars
|
||||
try {
|
||||
localVars = mutableSetOf()
|
||||
return block()
|
||||
}
|
||||
finally {
|
||||
currentScope -= localVars
|
||||
localVars = localVarsBackup
|
||||
}
|
||||
}
|
||||
|
||||
private fun addVar(name: JsName) {
|
||||
currentScope += name
|
||||
localVars.add(name)
|
||||
}
|
||||
}.accept(root)
|
||||
}
|
||||
|
||||
private fun perform() {
|
||||
object : JsVisitorWithContextImpl() {
|
||||
override fun visit(x: JsExpressionStatement, ctx: JsContext<*>): Boolean {
|
||||
val assignment = JsAstUtils.decomposeAssignmentToVariable(x.expression)
|
||||
object : RecursiveJsVisitor() {
|
||||
val lastAssignedVars = mutableListOf<Pair<JsName, JsNode>>()
|
||||
var lastProperlyUsedIndex = -1
|
||||
var firstUsedIndex = 0
|
||||
var nextExpectedIndex = -1
|
||||
var sideEffectOccurred = false
|
||||
|
||||
override fun visitNameRef(nameRef: JsNameRef) {
|
||||
val name = nameRef.name
|
||||
if (name != null && nameRef.qualifier == null && shouldConsiderTemporary(name)) {
|
||||
val index = lastAssignedVars.indexOfFirst { it.first == name }
|
||||
if (index >= 0) {
|
||||
if (sideEffectOccurred) {
|
||||
lastProperlyUsedIndex = -1
|
||||
}
|
||||
if (index == nextExpectedIndex) {
|
||||
++nextExpectedIndex
|
||||
}
|
||||
else {
|
||||
lastProperlyUsedIndex = index
|
||||
nextExpectedIndex = index + 1
|
||||
}
|
||||
firstUsedIndex = Math.min(firstUsedIndex, index)
|
||||
}
|
||||
return
|
||||
}
|
||||
super.visitNameRef(nameRef)
|
||||
}
|
||||
|
||||
override fun visitInvocation(invocation: JsInvocation) {
|
||||
super.visitInvocation(invocation)
|
||||
sideEffectOccurred = true
|
||||
}
|
||||
|
||||
override fun visitNew(x: JsNew) {
|
||||
super.visitNew(x)
|
||||
sideEffectOccurred = true
|
||||
}
|
||||
|
||||
override fun visitPrefixOperation(x: JsPrefixOperation) {
|
||||
super.visitPrefixOperation(x)
|
||||
when (x.operator) {
|
||||
JsUnaryOperator.INC, JsUnaryOperator.DEC -> sideEffectOccurred = true
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitPostfixOperation(x: JsPostfixOperation) {
|
||||
super.visitPostfixOperation(x)
|
||||
when (x.operator) {
|
||||
JsUnaryOperator.INC, JsUnaryOperator.DEC -> sideEffectOccurred = true
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitBinaryExpression(x: JsBinaryOperation) {
|
||||
super.visitBinaryExpression(x)
|
||||
if (x.operator == JsBinaryOperator.ASG) {
|
||||
sideEffectOccurred = true
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitArray(x: JsArrayLiteral) {
|
||||
super.visitArray(x)
|
||||
sideEffectOccurred = true
|
||||
}
|
||||
|
||||
override fun visitExpressionStatement(x: JsExpressionStatement) {
|
||||
val expression = x.expression
|
||||
val assignment = JsAstUtils.decomposeAssignmentToVariable(expression)
|
||||
if (assignment != null) {
|
||||
if (shouldConsiderTemporary(assignment.first)) {
|
||||
hasChanges = true
|
||||
ctx.removeMe()
|
||||
return false
|
||||
val (name, value) = assignment
|
||||
if (shouldConsiderTemporary(name)) {
|
||||
handleTopLevel(value, false)
|
||||
if (isTrivial(value) && name !in inconsistent) {
|
||||
statementsToRemove += x
|
||||
namesToSubstitute += name
|
||||
}
|
||||
else {
|
||||
lastAssignedVars += Pair(name, x)
|
||||
sideEffectOccurred = false
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return super.visit(x, ctx)
|
||||
handleTopLevel(expression)
|
||||
}
|
||||
|
||||
override fun visit(x: JsVars, ctx: JsContext<*>): Boolean {
|
||||
val filteredVars = x.vars.filter { it.initExpression == null || !shouldConsiderTemporary(it.name) }
|
||||
if (x.vars.size != filteredVars.size) {
|
||||
hasChanges = true
|
||||
x.vars.clear()
|
||||
x.vars.addAll(filteredVars)
|
||||
override fun visitIf(x: JsIf) {
|
||||
handleTopLevel(x.ifExpression)
|
||||
flush()
|
||||
accept(x.thenStatement)
|
||||
flush()
|
||||
x.elseStatement?.let { accept(it); flush() }
|
||||
}
|
||||
|
||||
override fun visitConditional(x: JsConditional) {
|
||||
accept(x.testExpression)
|
||||
|
||||
val lastAssignedVarsBackup = lastAssignedVars.toMutableList()
|
||||
lastAssignedVars.clear()
|
||||
accept(x.thenExpression)
|
||||
accept(x.elseExpression)
|
||||
lastAssignedVars.clear()
|
||||
lastAssignedVars += lastAssignedVarsBackup
|
||||
|
||||
sideEffectOccurred = true
|
||||
}
|
||||
|
||||
override fun visitReturn(x: JsReturn) {
|
||||
x.expression?.let { handleTopLevel(it) }
|
||||
flush()
|
||||
}
|
||||
|
||||
override fun visitThrow(x: JsThrow) {
|
||||
handleTopLevel(x.expression)
|
||||
flush()
|
||||
}
|
||||
|
||||
override fun visit(x: JsSwitch) {
|
||||
handleTopLevel(x.expression)
|
||||
flush()
|
||||
x.cases.forEach { accept(it); flush() }
|
||||
}
|
||||
|
||||
override fun visitObjectLiteral(x: JsObjectLiteral) {
|
||||
for (initializer in x.propertyInitializers) {
|
||||
accept(initializer.valueExpr)
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitWhile(x: JsWhile) {
|
||||
flush()
|
||||
super.visitWhile(x)
|
||||
flush()
|
||||
}
|
||||
|
||||
override fun visitDoWhile(x: JsDoWhile) {
|
||||
flush()
|
||||
super.visitDoWhile(x)
|
||||
flush()
|
||||
}
|
||||
|
||||
override fun visitForIn(x: JsForIn) {
|
||||
handleTopLevel(x.objectExpression)
|
||||
flush()
|
||||
accept(x.body)
|
||||
flush()
|
||||
}
|
||||
|
||||
override fun visitFor(x: JsFor) {
|
||||
x.initVars?.let { accept(it) }
|
||||
x.initExpression?.let { handleTopLevel(it) }
|
||||
|
||||
flush()
|
||||
|
||||
x.condition?.let { accept(it) }
|
||||
x.incrementExpression?.let { accept(it) }
|
||||
|
||||
flush()
|
||||
accept(x.body)
|
||||
flush()
|
||||
}
|
||||
|
||||
override fun visitTry(x: JsTry) {
|
||||
flush()
|
||||
super.visitTry(x)
|
||||
flush()
|
||||
}
|
||||
|
||||
override fun visitCatch(x: JsCatch) {
|
||||
flush()
|
||||
super.visitCatch(x)
|
||||
flush()
|
||||
}
|
||||
|
||||
override fun visitLabel(x: JsLabel) {
|
||||
flush()
|
||||
super.visitLabel(x)
|
||||
flush()
|
||||
}
|
||||
|
||||
override fun visitVars(x: JsVars) {
|
||||
for (v in x.vars) {
|
||||
val initializer = v.initExpression
|
||||
if (initializer != null) {
|
||||
val name = v.name
|
||||
if (shouldConsiderTemporary(name)) {
|
||||
handleTopLevel(initializer, false)
|
||||
if (isTrivial(initializer) && name !in inconsistent) {
|
||||
statementsToRemove += v
|
||||
namesToSubstitute += name
|
||||
}
|
||||
else {
|
||||
lastAssignedVars += Pair(name, v)
|
||||
sideEffectOccurred = false
|
||||
}
|
||||
}
|
||||
else {
|
||||
handleTopLevel(initializer)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitFunction(x: JsFunction) { }
|
||||
|
||||
override fun visitBreak(x: JsBreak) { }
|
||||
|
||||
override fun visitContinue(x: JsContinue) { }
|
||||
|
||||
private fun flush() {
|
||||
lastAssignedVars.clear()
|
||||
}
|
||||
|
||||
private fun handleTopLevel(expression: JsExpression, respectSideEffects: Boolean = true) {
|
||||
lastProperlyUsedIndex = -1
|
||||
firstUsedIndex = lastAssignedVars.size
|
||||
nextExpectedIndex = -1
|
||||
sideEffectOccurred = false
|
||||
|
||||
accept(expression)
|
||||
|
||||
if (lastProperlyUsedIndex >= 0 && nextExpectedIndex == lastAssignedVars.size) {
|
||||
lastAssignedVars.asSequence()
|
||||
.drop(lastProperlyUsedIndex)
|
||||
.forEach {
|
||||
val (name, statement) = it
|
||||
statementsToRemove += statement
|
||||
namesToSubstitute += name
|
||||
}
|
||||
}
|
||||
|
||||
if (respectSideEffects && sideEffectOccurred) {
|
||||
lastAssignedVars.clear()
|
||||
}
|
||||
else {
|
||||
lastAssignedVars.subList(firstUsedIndex, lastAssignedVars.size).clear()
|
||||
}
|
||||
}
|
||||
}.accept(root)
|
||||
}
|
||||
|
||||
private fun cleanUp() {
|
||||
object : JsVisitorWithContextImpl() {
|
||||
override fun visit(x: JsVars, ctx: JsContext<*>): Boolean {
|
||||
x.vars.removeAll { it in statementsToRemove }
|
||||
if (x.vars.isEmpty()) {
|
||||
ctx.removeMe()
|
||||
}
|
||||
|
||||
return super.visit(x, ctx)
|
||||
}
|
||||
|
||||
override fun visit(x: JsNameRef, ctx: JsContext<JsNode>): Boolean {
|
||||
val name = x.name
|
||||
if (x.qualifier == null && name != null && shouldConsiderTemporary(name)) {
|
||||
hasChanges = true
|
||||
val newExpr = definedValues[name] ?: JsLiteral.UNDEFINED
|
||||
ctx.replaceMe(accept(newExpr))
|
||||
usages[name] = usages[name]!! - 1
|
||||
return false
|
||||
}
|
||||
return super.visit(x, ctx)
|
||||
}
|
||||
|
||||
override fun visit(x: JsBreak, ctx: JsContext<*>) = false
|
||||
|
||||
override fun visit(x: JsContinue, ctx: JsContext<*>) = false
|
||||
|
||||
override fun visit(x: JsReturn, ctx: JsContext<*>): Boolean {
|
||||
val returnValue = x.expression
|
||||
if (returnValue is JsNameRef) {
|
||||
val name = returnValue.name
|
||||
if (returnValue.qualifier == null && name != null && name in temporary && definitions[name] ?: 0 == 0) {
|
||||
x.expression = null
|
||||
}
|
||||
override fun visit(x: JsExpressionStatement, ctx: JsContext<*>): Boolean {
|
||||
if (x in statementsToRemove) {
|
||||
ctx.removeMe()
|
||||
hasChanges = true
|
||||
return false
|
||||
}
|
||||
return super.visit(x, ctx)
|
||||
}
|
||||
|
||||
override fun visit(x: JsObjectLiteral, ctx: JsContext<*>): Boolean {
|
||||
for (initializer in x.propertyInitializers) {
|
||||
accept(initializer.valueExpr)
|
||||
}
|
||||
return super.visit(x, ctx)
|
||||
}
|
||||
|
||||
override fun visit(x: JsNameRef, ctx: JsContext<JsNode>): Boolean {
|
||||
val name = x.name
|
||||
if (name != null && x.qualifier == null && name in namesToSubstitute) {
|
||||
val replacement = accept(definedValues[name]!!)
|
||||
ctx.replaceMe(replacement)
|
||||
return false
|
||||
}
|
||||
return super.visit(x, ctx)
|
||||
}
|
||||
|
||||
override fun visit(x: JsFunction, ctx: JsContext<*>) = false
|
||||
|
||||
override fun visit(x: JsBreak, ctx: JsContext<*>) = false
|
||||
|
||||
override fun visit(x: JsContinue, ctx: JsContext<*>) = false
|
||||
}.accept(root)
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ import java.util.*
|
||||
fun collectFunctionReferencesInside(scope: JsNode): List<JsName> =
|
||||
collectReferencedNames(scope).filter { it.staticRef is JsFunction }
|
||||
|
||||
fun collectReferencedNames(scope: JsNode): Set<JsName> {
|
||||
private fun collectReferencedNames(scope: JsNode): Set<JsName> {
|
||||
val references = IdentitySet<JsName>()
|
||||
|
||||
object : JsVisitorWithContextImpl() {
|
||||
@@ -98,13 +98,19 @@ fun collectDefinedNames(scope: JsNode): Set<JsName> {
|
||||
addNameIfNeeded(x.name)
|
||||
}
|
||||
|
||||
override fun visitFunction(x: JsFunction) {
|
||||
val name = x.name
|
||||
if (name != null) {
|
||||
addNameIfNeeded(x.name)
|
||||
override fun visitExpressionStatement(x: JsExpressionStatement) {
|
||||
val expression = x.expression
|
||||
if (expression is JsFunction) {
|
||||
val name = expression.name
|
||||
if (name != null) {
|
||||
addNameIfNeeded(name)
|
||||
}
|
||||
}
|
||||
super.visitExpressionStatement(x)
|
||||
}
|
||||
|
||||
override fun visitFunction(x: JsFunction) { }
|
||||
|
||||
private fun addNameIfNeeded(name: JsName) {
|
||||
val ident = name.ident
|
||||
val nameCollected = names[ident]
|
||||
|
||||
+8
@@ -24,4 +24,12 @@ class TemporaryVariableEliminationTest : BasicOptimizerTest("temporary-variable"
|
||||
@Test fun assignment() = box()
|
||||
|
||||
@Test fun skipsGlobalDeclarations() = box()
|
||||
|
||||
@Test fun methodCall() = box()
|
||||
|
||||
@Test fun ifBranch() = box()
|
||||
|
||||
@Test fun tryCatch() = box()
|
||||
|
||||
@Test fun nonSideEffect() = box()
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
function test1(n) {
|
||||
var $tmp;
|
||||
if (n > 0) {
|
||||
$tmp = 23;
|
||||
}
|
||||
return typeof $tmp;
|
||||
}
|
||||
|
||||
function box() {
|
||||
var result = test1(5);
|
||||
if (result != "number") return "fail1: " + result;
|
||||
|
||||
result = test1(-5);
|
||||
if (result != "undefined") return "fail2: " + result;
|
||||
|
||||
return "OK";
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
function test1(n) {
|
||||
var $tmp;
|
||||
if (n > 0) {
|
||||
$tmp = 23;
|
||||
}
|
||||
return typeof $tmp;
|
||||
}
|
||||
|
||||
function box() {
|
||||
var result = test1(5);
|
||||
if (result != "number") return "fail1: " + result;
|
||||
|
||||
result = test1(-5);
|
||||
if (result != "undefined") return "fail2: " + result;
|
||||
|
||||
return "OK";
|
||||
}
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
var log = "";
|
||||
|
||||
|
||||
function init() {
|
||||
log = "";
|
||||
}
|
||||
|
||||
function foo(n) {
|
||||
log += "{" + n + "}";
|
||||
return n;
|
||||
}
|
||||
|
||||
function test1() {
|
||||
init();
|
||||
|
||||
return foo(foo(1) + foo(2));
|
||||
}
|
||||
|
||||
function test2() {
|
||||
init();
|
||||
|
||||
var $tmp2 = foo(2);
|
||||
var $tmp1 = foo(1);
|
||||
|
||||
return foo($tmp1 + $tmp2);
|
||||
}
|
||||
|
||||
function test3() {
|
||||
init();
|
||||
|
||||
return foo(foo(1) + foo(2) + foo(foo(3)));
|
||||
}
|
||||
|
||||
function test4() {
|
||||
init();
|
||||
|
||||
var $tmp1 = foo(1);
|
||||
var $tmp2 = foo(2);
|
||||
var $tmp3 = foo(3);
|
||||
|
||||
return foo(foo($tmp1) + $tmp2 + $tmp3);
|
||||
}
|
||||
|
||||
function test5() {
|
||||
init();
|
||||
|
||||
var $tmp1 = foo(1);
|
||||
var $tmp2 = foo(2);
|
||||
var $tmp3 = foo(3);
|
||||
foo(4);
|
||||
|
||||
return $tmp1 + $tmp2 + $tmp3;
|
||||
}
|
||||
|
||||
function box() {
|
||||
var result = test1();
|
||||
if (result != 3) return "fail1a: " + result;
|
||||
if (log != "{1}{2}{3}") return "fail1b: " + log;
|
||||
|
||||
result = test2();
|
||||
if (result != 3) return "fail2a: " + result;
|
||||
if (log != "{2}{1}{3}") return "fail2b: " + log;
|
||||
|
||||
result = test3();
|
||||
if (result != 6) return "fail3a: " + result;
|
||||
if (log != "{1}{2}{3}{3}{6}") return "fail3b: " + log;
|
||||
|
||||
result = test4();
|
||||
if (result != 6) return "fail4a: " + result;
|
||||
if (log != "{1}{2}{3}{1}{6}") return "fail4b: " + log;
|
||||
|
||||
result = test5();
|
||||
if (result != 6) return "fail5a: " + result;
|
||||
if (log != "{1}{2}{3}{4}") return "fail5b: " + log;
|
||||
|
||||
return "OK";
|
||||
}
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
var log = "";
|
||||
|
||||
|
||||
function init() {
|
||||
log = "";
|
||||
}
|
||||
|
||||
function foo(n) {
|
||||
log += "{" + n + "}";
|
||||
return n;
|
||||
}
|
||||
|
||||
function test1() {
|
||||
init();
|
||||
|
||||
var $tmp1 = foo(1);
|
||||
var $tmp2 = foo(2);
|
||||
|
||||
return foo($tmp1 + $tmp2);
|
||||
}
|
||||
|
||||
function test2() {
|
||||
init();
|
||||
|
||||
var $tmp2 = foo(2);
|
||||
var $tmp1 = foo(1);
|
||||
|
||||
return foo($tmp1 + $tmp2);
|
||||
}
|
||||
|
||||
function test3() {
|
||||
init();
|
||||
|
||||
var $tmp1 = foo(1);
|
||||
var $tmp2 = foo(2);
|
||||
var $tmp3 = foo(3);
|
||||
|
||||
return foo($tmp1 + $tmp2 + foo($tmp3));
|
||||
}
|
||||
|
||||
function test4() {
|
||||
init();
|
||||
|
||||
var $tmp1 = foo(1);
|
||||
var $tmp2 = foo(2);
|
||||
var $tmp3 = foo(3);
|
||||
|
||||
return foo(foo($tmp1) + $tmp2 + $tmp3);
|
||||
}
|
||||
|
||||
function test5() {
|
||||
init();
|
||||
|
||||
var $tmp1 = foo(1);
|
||||
var $tmp2 = foo(2);
|
||||
var $tmp3 = foo(3);
|
||||
foo(4);
|
||||
|
||||
return $tmp1 + $tmp2 + $tmp3;
|
||||
}
|
||||
|
||||
function box() {
|
||||
var result = test1();
|
||||
if (result != 3) return "fail1a: " + result;
|
||||
if (log != "{1}{2}{3}") return "fail1b: " + log;
|
||||
|
||||
result = test2();
|
||||
if (result != 3) return "fail2a: " + result;
|
||||
if (log != "{2}{1}{3}") return "fail2b: " + log;
|
||||
|
||||
result = test3();
|
||||
if (result != 6) return "fail3a: " + result;
|
||||
if (log != "{1}{2}{3}{3}{6}") return "fail3b: " + log;
|
||||
|
||||
result = test4();
|
||||
if (result != 6) return "fail4a: " + result;
|
||||
if (log != "{1}{2}{3}{1}{6}") return "fail4b: " + log;
|
||||
|
||||
result = test5();
|
||||
if (result != 6) return "fail5a: " + result;
|
||||
if (log != "{1}{2}{3}{4}") return "fail5b: " + log;
|
||||
|
||||
return "OK";
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
function foo(x) {
|
||||
return x;
|
||||
}
|
||||
|
||||
function box() {
|
||||
var result = foo(2) + 1;
|
||||
if (result != 3) return "fail: " + result;
|
||||
|
||||
return "OK";
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
function foo(x) {
|
||||
return x;
|
||||
}
|
||||
|
||||
function box() {
|
||||
var $tmp1 = 1;
|
||||
var $tmp2 = foo(2);
|
||||
|
||||
var result = $tmp2 + $tmp1;
|
||||
if (result != 3) return "fail: " + result;
|
||||
|
||||
return "OK";
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
function foo() {
|
||||
throw "foo";
|
||||
}
|
||||
|
||||
function bar() {
|
||||
var $tmp = foo();
|
||||
try {
|
||||
return "result: " + $tmp;
|
||||
} catch (e) {
|
||||
return "error";
|
||||
}
|
||||
}
|
||||
|
||||
function box() {
|
||||
try {
|
||||
bar()
|
||||
} catch (e) {
|
||||
if (e == "foo") {
|
||||
return "OK"
|
||||
}
|
||||
return e;
|
||||
}
|
||||
return "Exception expected";
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
function foo() {
|
||||
throw "foo";
|
||||
}
|
||||
|
||||
function bar() {
|
||||
var $tmp = foo();
|
||||
try {
|
||||
return "result: " + $tmp;
|
||||
} catch (e) {
|
||||
return "error";
|
||||
}
|
||||
}
|
||||
|
||||
function box() {
|
||||
try {
|
||||
bar()
|
||||
} catch (e) {
|
||||
if (e == "foo") {
|
||||
return "OK"
|
||||
}
|
||||
return e;
|
||||
}
|
||||
return "Exception expected";
|
||||
}
|
||||
Reference in New Issue
Block a user