JS: use one tag for inlining

This commit is contained in:
Alexey Tsvetkov
2015-03-10 15:51:20 +03:00
parent e83b253b3e
commit c7e8f52faf
9 changed files with 153 additions and 145 deletions
@@ -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
@@ -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)
}
}
@@ -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;
};