diff --git a/js/js.inliner/src/org/jetbrains/k2js/inline/FunctionContext.kt b/js/js.inliner/src/org/jetbrains/k2js/inline/FunctionContext.kt new file mode 100644 index 00000000000..733a128a11a --- /dev/null +++ b/js/js.inliner/src/org/jetbrains/k2js/inline/FunctionContext.kt @@ -0,0 +1,185 @@ +/* + * Copyright 2010-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.k2js.inline + +import com.google.dart.compiler.backend.js.ast.* + +import com.intellij.util.containers.ContainerUtil + +import java.util.IdentityHashMap + +abstract class FunctionContext( + private val function: JsFunction, + private val inliningContext: InliningContext +) { + /** + * Caches function with captured arguments applied. + * + * @see getFunctionWithClosure + */ + private val functionsWithClosure = IdentityHashMap() + + protected abstract fun lookUpStaticFunction(functionName: JsName?): JsFunction? + + public fun getFunctionDefinition(call: JsInvocation): JsFunction { + return getFunctionDefinitionImpl(call)!! + } + + public fun hasFunctionDefinition(call: JsInvocation): Boolean { + return getFunctionDefinitionImpl(call) != null + } + + public fun getEmpty(): JsEmpty { + return getScope().getProgram()?.getEmptyStatement()!! + } + + public fun getScope(): JsScope { + return function.getScope() + } + + public fun declareFunctionConstructorCalls(arguments: List) { + val calls = ContainerUtil.findAll(arguments, javaClass()) + + for (call in calls) { + val callName = InvocationUtil.getName(call) + if (callName == null) continue + + val staticRef = callName.getStaticRef() + if (staticRef !is JsFunction) continue + + val functionCalled = staticRef as JsFunction + if (InvocationUtil.getInnerFunction(functionCalled) != null) { + declareFunctionConstructorCall(call) + } + } + } + + public fun declareFunctionConstructorCall(call: JsInvocation) { + functionsWithClosure.put(call, null) + } + + /** + * Gets function definition by invocation. + * + * Notes: + * 1. Qualifier -- [()/.call()] part of invocation. + * 2. Local functions are compiled like function literals, + * but called not directly, but through variable. + * + * For example, local `fun f(a, b) = a + b; f(1, 2)` becomes `var f = _.foo.f$; f(1, 2)` + * + * Invocation properties: + * 1. Ends with either [()/.call()]. + * + * 2. Qualifier can be JsNameRef with static ref to JsFunction + * in case of function literal without closure. + * + * For example, qualifier == _.foo.lambda$ + * + * 3. Qualifier can be JsInvocation with static ref to JsFunction + * in case of function literal with closure. In this case + * qualifier arguments are captured in closure. + * + * For example, qualifier == _.foo.lambda(captured_1) + * + * 4. Qualifier can be JsNameRef with static ref to case [2] + * in case of local function without closure. + * + * 5. Qualifier can be JsNameRef with ref to case [3] + * in case of local function with closure. + */ + private fun getFunctionDefinitionImpl(call: JsInvocation): JsFunction? { + /** remove ending `()` */ + var callQualifier = call.getQualifier() + + /** remove ending `.call()` */ + if (InvocationUtil.isCallInvocation(call)) { + callQualifier = (callQualifier as JsNameRef).getQualifier() + } + + /** in case 4, 5 get ref (reduce 4, 5 to 2, 3 accordingly) */ + if (callQualifier is JsNameRef) { + val staticRef = (callQualifier as JsNameRef).getName()?.getStaticRef() + + callQualifier = when (staticRef) { + is JsNameRef -> staticRef + is JsInvocation -> staticRef + is JsFunction, null -> callQualifier + else -> throw AssertionError("Unexpected static reference type ${staticRef.javaClass}") + } + } + + /** process cases 2, 3 */ + val qualifier = callQualifier + return when (qualifier) { + is JsInvocation -> getFunctionWithClosure(qualifier) + is JsNameRef -> lookUpStaticFunction(qualifier.getName()) + else -> null + } + } + + /** + * Gets function body with captured args applied, + * and stores in cache. + * + * Function literals and local functions with closure + * are translated as function, that returns function. + * + * For example, + * val a = 1 + * val f = { a * 2 } + * `f` becomes + * f: function (a) { + * return function () { return a * 2 } + * } + * + * @returns inner function with captured parameters, + * replaced by outer arguments + * + * For invocation `f(10)()` returns + * `function () { return 10 * 2 }` + */ + private fun getFunctionWithClosure(call: JsInvocation): JsFunction { + val constructed = functionsWithClosure.get(call) + + if (constructed is JsFunction) return constructed + + val name = InvocationUtil.getName(call)!! + val closureCreator = lookUpStaticFunction(name)!! + val innerFunction = InvocationUtil.getInnerFunction(closureCreator)!! + + val withCapturedArgs = applyCapturedArgs(call, innerFunction, closureCreator) + functionsWithClosure.put(call, withCapturedArgs) + + return withCapturedArgs + } + + private fun applyCapturedArgs(call: JsInvocation, inner: JsFunction, outer: JsFunction): JsFunction { + val innerClone = inner.deepCopy() + + val renamingContext = inliningContext.getRenamingContext() + val arguments = call.getArguments()!! + val parameters = outer.getParameters() + aliasArgumentsIfNeeded(renamingContext, arguments, parameters) + val renamingResult = renamingContext.applyRename(innerClone) + + val declarations = renamingResult.declarations + val insertionPoint = inliningContext.getStatementContext().getInsertionPoint() + insertionPoint.insertAllBefore(declarations) + return renamingResult.renamed + } +} \ No newline at end of file diff --git a/js/js.inliner/src/org/jetbrains/k2js/inline/InliningContext.java b/js/js.inliner/src/org/jetbrains/k2js/inline/InliningContext.java new file mode 100644 index 00000000000..b1be431d3d6 --- /dev/null +++ b/js/js.inliner/src/org/jetbrains/k2js/inline/InliningContext.java @@ -0,0 +1,33 @@ +/* + * Copyright 2010-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.k2js.inline; + +import com.google.dart.compiler.backend.js.ast.*; +import org.jetbrains.annotations.NotNull; + +interface InliningContext { + @NotNull + RenamingContext getRenamingContext(); + + @NotNull + StatementContext getStatementContext(); + + @NotNull + FunctionContext getFunctionContext(); + + boolean isResultNeeded(JsInvocation call); +} diff --git a/js/js.inliner/src/org/jetbrains/k2js/inline/InsertionPoint.java b/js/js.inliner/src/org/jetbrains/k2js/inline/InsertionPoint.java new file mode 100644 index 00000000000..60bc1fccd4d --- /dev/null +++ b/js/js.inliner/src/org/jetbrains/k2js/inline/InsertionPoint.java @@ -0,0 +1,53 @@ +/* + * Copyright 2010-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.k2js.inline; + +import com.google.dart.compiler.backend.js.ast.JsContext; +import com.google.dart.compiler.backend.js.ast.JsNode; +import com.intellij.util.containers.ContainerUtil; + +import java.util.Collection; +import java.util.List; + +class InsertionPoint { + + private final JsContext context; + + InsertionPoint(JsContext insertionContext) { + context = insertionContext; + } + + public void insertBefore(T node) { + context.insertBefore(node); + } + + public void insertAfter(T node) { + context.insertAfter(node); + } + + public void insertAllBefore(Collection nodes) { + for (T node : nodes) { + insertBefore(node); + } + } + + public void insertAllAfter(List nodes) { + for (T node : ContainerUtil.reverse(nodes)) { + insertAfter(node); + } + } +} diff --git a/js/js.inliner/src/org/jetbrains/k2js/inline/RenamingContext.kt b/js/js.inliner/src/org/jetbrains/k2js/inline/RenamingContext.kt new file mode 100644 index 00000000000..5d403d448ae --- /dev/null +++ b/js/js.inliner/src/org/jetbrains/k2js/inline/RenamingContext.kt @@ -0,0 +1,54 @@ +/* + * Copyright 2010-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.k2js.inline + +import com.google.dart.compiler.backend.js.ast.* +import com.google.dart.compiler.backend.js.ast.JsVars.JsVar + +import java.util.ArrayList +import java.util.IdentityHashMap + +import org.jetbrains.k2js.translate.utils.JsAstUtils + + +data class RenamingResult(val renamed: T, val declarations: Collection) + +class RenamingContext(scope: JsScope) { + private val scope = scope + private val renamings = IdentityHashMap() + private val declarations = ArrayList() + + public fun applyRename(target : T): RenamingResult { + val renamed = RenamingVisitor.rename(target, renamings) + return RenamingResult(renamed, declarations) + } + + public fun replaceName(name: JsName, replacement: JsExpression) { + assert(!renamings.containsKey(name)) { "$name has been renamed already" } + + renamings.put(name, replacement) + } + + public fun getFreshName(candidate: String?): JsName = scope.declareFreshName(candidate) + + public fun getFreshName(candidate: JsName?): JsName = getFreshName(candidate?.getIdent()) + + public fun newVar(name: JsName, value: JsExpression? = null) { + val vars = JsAstUtils.newVar(name, value) + declarations.add(vars) + } +} diff --git a/js/js.inliner/src/org/jetbrains/k2js/inline/StatementContext.kt b/js/js.inliner/src/org/jetbrains/k2js/inline/StatementContext.kt new file mode 100644 index 00000000000..9381309c5b8 --- /dev/null +++ b/js/js.inliner/src/org/jetbrains/k2js/inline/StatementContext.kt @@ -0,0 +1,48 @@ +/* + * Copyright 2010-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.k2js.inline + +import com.google.dart.compiler.backend.js.ast.JsContext +import com.google.dart.compiler.backend.js.ast.JsStatement +import com.google.dart.compiler.backend.js.ast.JsNode + +abstract class StatementContext { + public abstract fun getCurrentStatementContext(): JsContext + + public fun getInsertionPoint(): InsertionPoint { + return InsertionPoint(getCurrentStatementContext()) + } + + public fun removeCurrentStatement() { + val statementContext = getCurrentStatementContext() + statementContext.replaceMe(getEmptyStatement()) + } + + open public fun shiftCurrentStatementForward() { + val statementContext = getCurrentStatementContext() + val currentStatement = getCurrentStatement() + statementContext.insertAfter(currentStatement) + statementContext.replaceMe(getEmptyStatement()) + } + + public fun getCurrentStatement(): JsStatement { + val statementContext = getCurrentStatementContext() + return statementContext.getCurrentNode() as JsStatement + } + + protected abstract fun getEmptyStatement(): JsStatement +}