diff --git a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/FunctionReader.kt b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/FunctionReader.kt index 3cd6faad344..de59f25b555 100644 --- a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/FunctionReader.kt +++ b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/FunctionReader.kt @@ -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 { - 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() diff --git a/js/js.parser/src/com/google/gwt/dev/js/JsAstMapper.java b/js/js.parser/src/com/google/gwt/dev/js/JsAstMapper.java index ccf48c6807e..19f79f5b457 100644 --- a/js/js.parser/src/com/google/gwt/dev/js/JsAstMapper.java +++ b/js/js.parser/src/com/google/gwt/dev/js/JsAstMapper.java @@ -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 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 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 mapStatements(Node nodeStmts) + public List mapStatements(Node nodeStmts) throws JsParserException { List stmts = new ArrayList(); mapStatements(stmts, nodeStmts); diff --git a/js/js.parser/src/com/google/gwt/dev/js/rhino/Parser.java b/js/js.parser/src/com/google/gwt/dev/js/rhino/Parser.java index 3eb225defe2..7b2d877fd97 100644 --- a/js/js.parser/src/com/google/gwt/dev/js/rhino/Parser.java +++ b/js/js.parser/src/com/google/gwt/dev/js/rhino/Parser.java @@ -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; diff --git a/js/js.parser/src/org/jetbrains/kotlin/js/parser/ParserEvents.kt b/js/js.parser/src/org/jetbrains/kotlin/js/parser/ParserEvents.kt new file mode 100644 index 00000000000..ce648ae8b54 --- /dev/null +++ b/js/js.parser/src/org/jetbrains/kotlin/js/parser/ParserEvents.kt @@ -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) +} \ No newline at end of file diff --git a/js/js.parser/src/org/jetbrains/kotlin/js/parser/parserUtils.kt b/js/js.parser/src/org/jetbrains/kotlin/js/parser/parserUtils.kt new file mode 100644 index 00000000000..349070b6a20 --- /dev/null +++ b/js/js.parser/src/org/jetbrains/kotlin/js/parser/parserUtils.kt @@ -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 = + 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), "", FAKE_SOURCE_INFO.getLine()) + val parser = Parser(IRFactory(ts), insideFunction) + return parser.parseAction(ts) as Node + } finally { + Context.exit() + } +} + +inline +private fun Node.toJsAst(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("")) +} \ No newline at end of file diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/Namer.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/Namer.java index d4c3d902455..d2b57f69f70 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/Namer.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/Namer.java @@ -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 diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/InlineMetadata.kt b/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/InlineMetadata.kt index ccf15875156..185054080c2 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/InlineMetadata.kt +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/InlineMetadata.kt @@ -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): 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) } } \ No newline at end of file diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/reference/CallExpressionTranslator.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/reference/CallExpressionTranslator.java index 2b673b70446..ef712be20ca 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/reference/CallExpressionTranslator.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/reference/CallExpressionTranslator.java @@ -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 statements = new ArrayList(); - - 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); } } diff --git a/js/js.translator/testData/kotlin_lib_ecma5.js b/js/js.translator/testData/kotlin_lib_ecma5.js index b65df696e7e..e465e0a5b53 100644 --- a/js/js.translator/testData/kotlin_lib_ecma5.js +++ b/js/js.translator/testData/kotlin_lib_ecma5.js @@ -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; };