JS inline: added inlining context utilities
This commit is contained in:
committed by
Zalim Bashorov
parent
f29816f19c
commit
0151b7a2d1
@@ -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<JsInvocation, JsFunction?>()
|
||||||
|
|
||||||
|
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<JsExpression>) {
|
||||||
|
val calls = ContainerUtil.findAll<JsExpression, JsInvocation>(arguments, javaClass<JsInvocation>())
|
||||||
|
|
||||||
|
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<JsFunction>()
|
||||||
|
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<JsStatement>()
|
||||||
|
insertionPoint.insertAllBefore(declarations)
|
||||||
|
return renamingResult.renamed
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
<T extends JsNode> RenamingContext<T> getRenamingContext();
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
StatementContext getStatementContext();
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
FunctionContext getFunctionContext();
|
||||||
|
|
||||||
|
boolean isResultNeeded(JsInvocation call);
|
||||||
|
}
|
||||||
@@ -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<T extends JsNode> {
|
||||||
|
|
||||||
|
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<? extends T> nodes) {
|
||||||
|
for (T node : nodes) {
|
||||||
|
insertBefore(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void insertAllAfter(List<? extends T> nodes) {
|
||||||
|
for (T node : ContainerUtil.reverse(nodes)) {
|
||||||
|
insertAfter(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<T : JsNode>(val renamed: T, val declarations: Collection<JsVars>)
|
||||||
|
|
||||||
|
class RenamingContext<T : JsNode>(scope: JsScope) {
|
||||||
|
private val scope = scope
|
||||||
|
private val renamings = IdentityHashMap<JsName, JsExpression>()
|
||||||
|
private val declarations = ArrayList<JsVars>()
|
||||||
|
|
||||||
|
public fun applyRename(target : T): RenamingResult<T> {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<T : JsStatement>(): InsertionPoint<T> {
|
||||||
|
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
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user