JS: use one tag for inlining
This commit is contained in:
@@ -44,6 +44,7 @@ import org.jetbrains.kotlin.utils.*
|
||||
import com.intellij.util.containers.SLRUCache
|
||||
import java.io.*
|
||||
import java.net.URL
|
||||
import org.jetbrains.kotlin.js.parser.*
|
||||
|
||||
// TODO: add hash checksum to defineModule?
|
||||
/**
|
||||
@@ -115,28 +116,17 @@ public class FunctionReader(private val context: TranslationContext) {
|
||||
}
|
||||
|
||||
private fun readFunctionFromSource(descriptor: CallableDescriptor, source: String): JsFunction? {
|
||||
val startTag = Namer.getInlineStartTag(descriptor)
|
||||
val endTag = Namer.getInlineEndTag(descriptor)
|
||||
val tag = Namer.getFunctionTag(descriptor)
|
||||
val index = source.indexOf(tag)
|
||||
if (index < 0) return null
|
||||
|
||||
val startIndex = source.indexOf(startTag)
|
||||
if (startIndex < 0) return null
|
||||
|
||||
val endIndex = source.indexOf(endTag, startIndex)
|
||||
if (endIndex < 0) return null
|
||||
|
||||
val metadataString = source.substring(startIndex - 1, endIndex + endTag.length() + 1)
|
||||
val statements = parseJavaScript(metadataString)
|
||||
val statement = statements.firstOrNull()
|
||||
|
||||
if (statement !is JsExpressionStatement) throw IllegalStateException("Expected JsExpressionStatement, got: $statement")
|
||||
val expression = statement.getExpression()
|
||||
|
||||
val metadata = InlineMetadata.decompose(expression)
|
||||
if (metadata == null) {
|
||||
throw IllegalStateException("Could not get inline metadata from expression: $expression")
|
||||
// + 1 for closing quote
|
||||
var offset = index + tag.length() + 1
|
||||
while (offset < source.length() && source.charAt(offset).isWhitespaceOrComma) {
|
||||
offset++
|
||||
}
|
||||
|
||||
val function = metadata.function
|
||||
val function = parseFunction(source, offset, ThrowExceptionOnErrorReporter)
|
||||
val moduleName = getExternalModuleName(descriptor)!!
|
||||
val moduleNameLiteral = context.program().getStringLiteral(moduleName)
|
||||
val moduleReference = context.namer().getModuleReference(moduleNameLiteral)
|
||||
@@ -146,20 +136,11 @@ public class FunctionReader(private val context: TranslationContext) {
|
||||
replaceExternalNames(function, replacements)
|
||||
return function
|
||||
}
|
||||
|
||||
private fun parseJavaScript(source: String): List<JsStatement> {
|
||||
try {
|
||||
val info = SourceInfoImpl(null, 0, 0, 0, 0)
|
||||
val scope = JsRootScope(context.program())
|
||||
val reader = StringReader(source)
|
||||
return JsAstMapper.parse(info, scope, reader, ThrowExceptionOnErrorReporter, /* insideFunction= */ false)
|
||||
}
|
||||
catch (e: Exception) {
|
||||
throw RuntimeException(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val Char.isWhitespaceOrComma: Boolean
|
||||
get() = this == ',' || this.isWhitespace()
|
||||
|
||||
private fun JsFunction.markInlineArguments(descriptor: CallableDescriptor) {
|
||||
val params = descriptor.getValueParameters()
|
||||
val paramsJs = getParameters()
|
||||
|
||||
@@ -17,68 +17,25 @@ package com.google.gwt.dev.js;
|
||||
|
||||
import com.google.dart.compiler.backend.js.ast.*;
|
||||
import com.google.dart.compiler.backend.js.ast.JsLiteral.JsBooleanLiteral;
|
||||
import com.google.dart.compiler.common.SourceInfo;
|
||||
import com.google.gwt.dev.js.parserExceptions.JsParserException;
|
||||
import com.google.gwt.dev.js.rhino.*;
|
||||
import com.intellij.util.SmartList;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static com.google.dart.compiler.util.AstUtil.toFunctionScope;
|
||||
|
||||
/**
|
||||
* Parses JavaScript source.
|
||||
*/
|
||||
public class JsAstMapper {
|
||||
|
||||
private final JsProgram program;
|
||||
private final ScopeContext scopeContext;
|
||||
|
||||
public static List<JsStatement> parse(
|
||||
SourceInfo rootSourceInfo,
|
||||
JsScope scope, Reader r,
|
||||
ErrorReporter errorReporter,
|
||||
boolean insideFunction
|
||||
) throws IOException, JsParserException {
|
||||
return new JsAstMapper(scope).parseImpl(rootSourceInfo, r, errorReporter, insideFunction);
|
||||
}
|
||||
|
||||
/**
|
||||
* since source maps are not mapped to kotlin source maps
|
||||
*/
|
||||
private static final String SOURCE_NAME_STUB = "jsCode";
|
||||
|
||||
private JsAstMapper(@NotNull JsScope scope) {
|
||||
public JsAstMapper(@NotNull JsScope scope) {
|
||||
scopeContext = new ScopeContext(scope);
|
||||
program = scope.getProgram();
|
||||
}
|
||||
|
||||
List<JsStatement> parseImpl(
|
||||
final SourceInfo rootSourceInfo,
|
||||
Reader r,
|
||||
ErrorReporter errorReporter,
|
||||
boolean insideFunction
|
||||
) throws JsParserException, IOException {
|
||||
// Create a custom error handler so that we can throw our own exceptions.
|
||||
Context.enter().setErrorReporter(errorReporter);
|
||||
try {
|
||||
// Parse using the Rhino parser.
|
||||
//
|
||||
TokenStream ts = new TokenStream(r, SOURCE_NAME_STUB, rootSourceInfo.getLine());
|
||||
Parser parser = new Parser(new IRFactory(ts), insideFunction);
|
||||
Node topNode = (Node) parser.parse(ts);
|
||||
return mapStatements(topNode);
|
||||
}
|
||||
finally {
|
||||
Context.exit();
|
||||
}
|
||||
}
|
||||
|
||||
private static JsParserException createParserException(String msg, Node offender) {
|
||||
CodePosition position = new CodePosition(offender.getLineno(), 0);
|
||||
return new JsParserException("Parser encountered internal error: " + msg, position);
|
||||
@@ -594,8 +551,9 @@ public class JsAstMapper {
|
||||
}
|
||||
}
|
||||
|
||||
private JsExpression mapFunction(Node fnNode) throws JsParserException {
|
||||
|
||||
public JsFunction mapFunction(Node fnNode) throws JsParserException {
|
||||
int nodeType = fnNode.getType();
|
||||
assert nodeType == TokenStream.FUNCTION: "Expected function node, got: " + TokenStream.tokenToName(nodeType);
|
||||
Node fromFnNameNode = fnNode.getFirstChild();
|
||||
Node fromParamNode = fnNode.getFirstChild().getNext().getFirstChild();
|
||||
Node fromBodyNode = fnNode.getFirstChild().getNext().getNext();
|
||||
@@ -954,7 +912,7 @@ public class JsAstMapper {
|
||||
}
|
||||
}
|
||||
|
||||
private List<JsStatement> mapStatements(Node nodeStmts)
|
||||
public List<JsStatement> mapStatements(Node nodeStmts)
|
||||
throws JsParserException {
|
||||
List<JsStatement> stmts = new ArrayList<JsStatement>();
|
||||
mapStatements(stmts, nodeStmts);
|
||||
|
||||
@@ -37,7 +37,10 @@
|
||||
|
||||
package com.google.gwt.dev.js.rhino;
|
||||
|
||||
import org.jetbrains.kotlin.js.parser.ParserEvents;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Observable;
|
||||
|
||||
/**
|
||||
* This class implements the JavaScript parser.
|
||||
@@ -48,7 +51,7 @@ import java.io.IOException;
|
||||
* @see TokenStream
|
||||
*/
|
||||
|
||||
public class Parser {
|
||||
public class Parser extends Observable {
|
||||
public Parser(IRFactory nf, boolean insideFunction) {
|
||||
this.nf = nf;
|
||||
this.insideFunction = insideFunction;
|
||||
@@ -165,8 +168,8 @@ public class Parser {
|
||||
return pn;
|
||||
}
|
||||
|
||||
private Object function(TokenStream ts, boolean isExpr) throws IOException,
|
||||
JavaScriptException {
|
||||
private Object function(TokenStream ts, boolean isExpr) throws IOException, JavaScriptException {
|
||||
notifyObservers(new ParserEvents.OnFunctionParsingStart());
|
||||
int baseLineno = ts.getLineno(); // line number where source starts
|
||||
|
||||
String name;
|
||||
@@ -243,6 +246,7 @@ public class Parser {
|
||||
wellTerminated(ts, ts.FUNCTION);
|
||||
}
|
||||
|
||||
notifyObservers(new ParserEvents.OnFunctionParsingEnd(ts));
|
||||
return pn;
|
||||
}
|
||||
|
||||
@@ -1021,7 +1025,7 @@ public class Parser {
|
||||
return pn;
|
||||
}
|
||||
|
||||
private Object primaryExpr(TokenStream ts) throws IOException,
|
||||
public Object primaryExpr(TokenStream ts) throws IOException,
|
||||
JavaScriptException {
|
||||
int tt;
|
||||
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright 2010-2015 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.kotlin.js.parser
|
||||
|
||||
import com.google.gwt.dev.js.rhino.*
|
||||
|
||||
public object ParserEvents {
|
||||
|
||||
public class OnFunctionParsingStart
|
||||
|
||||
public class OnFunctionParsingEnd(public val tokenStream: TokenStream)
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright 2010-2015 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.kotlin.js.parser
|
||||
|
||||
import com.google.dart.compiler.common.*
|
||||
import com.google.dart.compiler.backend.js.ast.*
|
||||
import com.google.gwt.dev.js.*
|
||||
import com.google.gwt.dev.js.rhino.*
|
||||
|
||||
import com.intellij.util.*
|
||||
import java.io.*
|
||||
import java.util.*
|
||||
|
||||
private val FAKE_SOURCE_INFO = SourceInfoImpl(null, 0, 0, 0, 0)
|
||||
|
||||
public fun parse(code: String, reporter: ErrorReporter, insideFunction: Boolean): List<JsStatement> =
|
||||
parse(code, 0, reporter, insideFunction, Parser::parse).toJsAst(JsAstMapper::mapStatements)
|
||||
|
||||
public fun parseFunction(code: String, offset: Int, reporter: ErrorReporter): JsFunction =
|
||||
parse(code, offset, reporter, insideFunction = false) {
|
||||
addObserver(FunctionParsingObserver())
|
||||
primaryExpr(it)
|
||||
}.toJsAst(JsAstMapper::mapFunction)
|
||||
|
||||
private class FunctionParsingObserver : Observer {
|
||||
var functionsStarted = 0
|
||||
|
||||
override fun update(o: Observable?, arg: Any?): Unit =
|
||||
when (arg) {
|
||||
is ParserEvents.OnFunctionParsingStart -> {
|
||||
functionsStarted++
|
||||
}
|
||||
is ParserEvents.OnFunctionParsingEnd -> {
|
||||
functionsStarted--
|
||||
|
||||
if (functionsStarted == 0) {
|
||||
arg.tokenStream.ungetToken(TokenStream.EOF)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline
|
||||
private fun parse(
|
||||
code: String,
|
||||
offset: Int,
|
||||
reporter: ErrorReporter,
|
||||
insideFunction: Boolean,
|
||||
parseAction: Parser.(TokenStream)->Any
|
||||
): Node {
|
||||
Context.enter().setErrorReporter(reporter)
|
||||
|
||||
try {
|
||||
val ts = TokenStream(StringReader(code, offset), "<parser>", FAKE_SOURCE_INFO.getLine())
|
||||
val parser = Parser(IRFactory(ts), insideFunction)
|
||||
return parser.parseAction(ts) as Node
|
||||
} finally {
|
||||
Context.exit()
|
||||
}
|
||||
}
|
||||
|
||||
inline
|
||||
private fun Node.toJsAst<T>(mapAction: JsAstMapper.(Node)->T): T =
|
||||
JsAstMapper(RootScope()).mapAction(this)
|
||||
|
||||
private fun StringReader(string: String, offset: Int): Reader {
|
||||
val reader = StringReader(string)
|
||||
reader.skip(offset.toLong())
|
||||
return reader
|
||||
}
|
||||
|
||||
private fun RootScope(): JsScope {
|
||||
return JsRootScope(JsProgram("<parser>"))
|
||||
}
|
||||
@@ -91,8 +91,6 @@ public final class Namer {
|
||||
public static final String CAPTURED_VAR_FIELD = "v";
|
||||
|
||||
public static final JsNameRef CREATE_INLINE_FUNCTION = new JsNameRef("defineInlineFunction", KOTLIN_OBJECT_REF);
|
||||
private static final String INLINE_START_TAG = "inlineStartTag";
|
||||
private static final String INLINE_END_TAG = "inlineEndTag";
|
||||
|
||||
@NotNull
|
||||
public static final JsExpression UNDEFINED_EXPRESSION = new JsPrefixOperation(JsUnaryOperator.VOID, JsNumberLiteral.ZERO);
|
||||
@@ -108,24 +106,11 @@ public final class Namer {
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static String getInlineStartTag(@NotNull CallableDescriptor functionDescriptor) {
|
||||
return formatInlineTag(functionDescriptor, INLINE_START_TAG);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static String getInlineEndTag(@NotNull CallableDescriptor functionDescriptor) {
|
||||
return formatInlineTag(functionDescriptor, INLINE_END_TAG);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static String formatInlineTag(
|
||||
@NotNull CallableDescriptor functionDescriptor,
|
||||
@NotNull String tag
|
||||
) {
|
||||
public static String getFunctionTag(@NotNull CallableDescriptor functionDescriptor) {
|
||||
FqName fqName = getFqNameSafe(functionDescriptor);
|
||||
String mangledName = getSuggestedName(functionDescriptor);
|
||||
String moduleName = getModuleName(functionDescriptor);
|
||||
return StringUtil.join(Arrays.asList(tag, moduleName, fqName, mangledName), ".");
|
||||
return StringUtil.join(Arrays.asList(moduleName, fqName, mangledName), ".");
|
||||
}
|
||||
|
||||
@NotNull
|
||||
|
||||
+9
-29
@@ -24,35 +24,17 @@ import org.jetbrains.kotlin.js.translate.utils.JsDescriptorUtils.*
|
||||
|
||||
import kotlin.platform.platformStatic
|
||||
|
||||
private val METADATA_PROPERTIES_COUNT = 3
|
||||
private val METADATA_PROPERTIES_COUNT = 2
|
||||
|
||||
public class InlineMetadata(
|
||||
val startTag: JsStringLiteral,
|
||||
val function: JsFunction,
|
||||
val endTag: JsStringLiteral
|
||||
) {
|
||||
class object {
|
||||
public class InlineMetadata(val tag: JsStringLiteral, val function: JsFunction) {
|
||||
companion object {
|
||||
platformStatic
|
||||
fun compose(function: JsFunction, descriptor: CallableDescriptor): InlineMetadata {
|
||||
val program = function.getScope().getProgram()
|
||||
val startTag = program.getStringLiteral(Namer.getInlineStartTag(descriptor))
|
||||
val endTag = program.getStringLiteral(Namer.getInlineEndTag(descriptor))
|
||||
return InlineMetadata(startTag, function, endTag)
|
||||
val tag = program.getStringLiteral(Namer.getFunctionTag(descriptor))
|
||||
return InlineMetadata(tag, function)
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads metadata from expression.
|
||||
*
|
||||
* To read metadata from source one needs to:
|
||||
* 1. find index of startTag and endTag in source;
|
||||
* 2. parse substring between startTagIndex - 1 (for opening quote)
|
||||
* and endTagIndex + endTag.length() + 1 (for closing quote)
|
||||
* 3. call InlineMetadata#decompose on resulting expression
|
||||
*
|
||||
* @see Namer#getInlineStartTag
|
||||
* @see Namer#getInlineEndTag
|
||||
* @see com.google.gwt.dev.js.JsParser
|
||||
*/
|
||||
platformStatic
|
||||
fun decompose(expression: JsExpression?): InlineMetadata? =
|
||||
when (expression) {
|
||||
@@ -89,19 +71,17 @@ public class InlineMetadata(
|
||||
private fun decomposePropertiesList(properties: List<JsExpression>): InlineMetadata? {
|
||||
if (properties.size() != METADATA_PROPERTIES_COUNT) return null
|
||||
|
||||
val startTag = properties[0] as? JsStringLiteral
|
||||
val tag = properties[0] as? JsStringLiteral
|
||||
val function = properties[1] as? JsFunction
|
||||
val endTag = properties[2] as? JsStringLiteral
|
||||
if (tag == null || function == null) return null
|
||||
|
||||
if (startTag == null || function == null || endTag == null) return null
|
||||
|
||||
return InlineMetadata(startTag, function, endTag)
|
||||
return InlineMetadata(tag, function)
|
||||
}
|
||||
}
|
||||
|
||||
public val functionWithMetadata: JsExpression
|
||||
get() {
|
||||
val propertiesList = listOf(startTag, function, endTag)
|
||||
val propertiesList = listOf(tag, function)
|
||||
return JsInvocation(Namer.CREATE_INLINE_FUNCTION, propertiesList)
|
||||
}
|
||||
}
|
||||
+2
-16
@@ -17,14 +17,13 @@
|
||||
package org.jetbrains.kotlin.js.translate.reference;
|
||||
|
||||
import com.google.dart.compiler.backend.js.ast.*;
|
||||
import com.google.dart.compiler.common.SourceInfoImpl;
|
||||
import com.google.gwt.dev.js.JsAstMapper;
|
||||
import com.google.gwt.dev.js.ThrowExceptionOnErrorReporter;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.kotlin.builtins.InlineUtil;
|
||||
import org.jetbrains.kotlin.builtins.KotlinBuiltIns;
|
||||
import org.jetbrains.kotlin.descriptors.*;
|
||||
import org.jetbrains.kotlin.js.parser.ParserPackage;
|
||||
import org.jetbrains.kotlin.js.translate.callTranslator.CallTranslator;
|
||||
import org.jetbrains.kotlin.js.translate.context.TranslationContext;
|
||||
import org.jetbrains.kotlin.psi.JetCallExpression;
|
||||
@@ -39,7 +38,6 @@ import org.jetbrains.kotlin.resolve.constants.evaluate.ConstantExpressionEvaluat
|
||||
import org.jetbrains.kotlin.types.JetType;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -137,18 +135,6 @@ public final class CallExpressionTranslator extends AbstractCallExpressionTransl
|
||||
assert constant != null: "jsCode must be compile time string " + jsCodeExpression;
|
||||
String jsCode = (String) constant.getValue();
|
||||
assert jsCode != null: jsCodeExpression.toString();
|
||||
|
||||
List<JsStatement> statements = new ArrayList<JsStatement>();
|
||||
|
||||
try {
|
||||
SourceInfoImpl info = new SourceInfoImpl(null, 0, 0, 0, 0);
|
||||
JsScope scope = context().scope();
|
||||
StringReader reader = new StringReader(jsCode);
|
||||
statements.addAll(JsAstMapper.parse(info, scope, reader, ThrowExceptionOnErrorReporter.INSTANCE$, /* insideFunction= */ true));
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
return statements;
|
||||
return ParserPackage.parse(jsCode, ThrowExceptionOnErrorReporter.INSTANCE$, /* insideFunction= */ true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -475,7 +475,7 @@ var Kotlin = {};
|
||||
Object.defineProperty(Kotlin.modules, id, {value: declaration});
|
||||
};
|
||||
|
||||
Kotlin.defineInlineFunction = function(startTag, fun, metadataArgs) {
|
||||
Kotlin.defineInlineFunction = function(tag, fun) {
|
||||
return fun;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user