diff --git a/js/js.ast/src/org/jetbrains/kotlin/js/backend/ast/JsCatch.java b/js/js.ast/src/org/jetbrains/kotlin/js/backend/ast/JsCatch.java index f55ecc25c5b..5631d87d1a6 100644 --- a/js/js.ast/src/org/jetbrains/kotlin/js/backend/ast/JsCatch.java +++ b/js/js.ast/src/org/jetbrains/kotlin/js/backend/ast/JsCatch.java @@ -17,6 +17,11 @@ public class JsCatch extends SourceInfoAwareJsNode implements HasCondition { private JsExpression condition; private JsParameter param; + public JsCatch(@NotNull JsName name) { + param = new JsParameter(name); + scope = null; + } + public JsCatch(JsScope parent, @NotNull String ident) { super(); assert (parent != null); diff --git a/js/js.serializer/src/org/jetbrains/kotlin/serialization/js/ast/JsAstDeserializer.kt b/js/js.serializer/src/org/jetbrains/kotlin/serialization/js/ast/JsAstDeserializer.kt new file mode 100644 index 00000000000..16a7778f6f8 --- /dev/null +++ b/js/js.serializer/src/org/jetbrains/kotlin/serialization/js/ast/JsAstDeserializer.kt @@ -0,0 +1,429 @@ +/* + * Copyright 2010-2017 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.serialization.js.ast + +import org.jetbrains.kotlin.js.backend.ast.* +import org.jetbrains.kotlin.serialization.js.ast.JsAstProtoBuf.* +import org.jetbrains.kotlin.serialization.js.ast.JsAstProtoBuf.Expression.ExpressionCase +import org.jetbrains.kotlin.serialization.js.ast.JsAstProtoBuf.Statement.StatementCase +import java.io.InputStream + +class JsAstDeserializer(private val program: JsProgram) { + private val scope = JsRootScope(program) + private val stringTable = mutableListOf() + private val nameTable = mutableListOf() + private val nameCache = mutableListOf() + + fun deserialize(input: InputStream): JsProgramFragment { + return deserialize(Chunk.parseFrom(input)) + } + + fun deserialize(proto: Chunk): JsProgramFragment { + stringTable += proto.stringTable.entryList + nameTable += proto.nameTable.entryList + nameCache += nameTable.map { null } + try { + return deserialize(proto.fragment) + } + finally { + stringTable.clear() + nameTable.clear() + nameCache.clear() + } + } + + private fun deserialize(proto: Fragment): JsProgramFragment { + val fragment = JsProgramFragment(scope) + + fragment.importedModules += proto.importedModuleList.map { importedModuleProto -> + JsImportedModule( + deserializeString(importedModuleProto.externalNameId), + deserializeName(importedModuleProto.internalNameId), + if (importedModuleProto.hasPlainReference()) deserialize(importedModuleProto.plainReference) else null + ) + } + + fragment.imports += proto.importEntryList.associate { importProto -> + deserializeString(importProto.signatureId) to deserialize(importProto.expression) + } + + if (proto.hasDeclarationBlock()) { + fragment.declarationBlock.statements += deserializeGlobalBlock(proto.declarationBlock).statements + } + if (proto.hasInitializerBlock()) { + fragment.initializerBlock.statements += deserializeGlobalBlock(proto.initializerBlock).statements + } + if (proto.hasExportBlock()) { + fragment.exportBlock.statements += deserializeGlobalBlock(proto.exportBlock).statements + } + + fragment.nameBindings += proto.nameBindingList.map { nameBindingProto -> + JsNameBinding(deserializeString(nameBindingProto.signatureId), deserializeName(nameBindingProto.nameId)) + } + + fragment.classes += proto.classModelList.associate { clsProto -> deserialize(clsProto).let { it.name to it } } + + val moduleExpressions = proto.moduleExpressionList.map { deserialize(it) } + fragment.inlineModuleMap += proto.inlineModuleList.associate { inlineModuleProto -> + deserializeString(inlineModuleProto.signatureId) to moduleExpressions[inlineModuleProto.expressionId] + } + + return fragment + } + + private fun deserialize(proto: ClassModel): JsClassModel { + val superName = if (proto.hasSuperNameId()) deserializeName(proto.superNameId) else null + return JsClassModel(deserializeName(proto.nameId), superName).apply { + if (proto.hasPostDeclarationBlock()) { + postDeclarationBlock.statements += deserializeGlobalBlock(proto.postDeclarationBlock).statements + } + } + } + + private fun deserialize(proto: Statement): JsStatement = when (proto.statementCase) { + StatementCase.RETURN_STATEMENT -> { + val returnProto = proto.returnStatement + JsReturn(if (returnProto.hasValue()) deserialize(returnProto.value) else null) + } + + StatementCase.THROW_STATEMENT -> { + val throwProto = proto.throwStatement + JsThrow(deserialize(throwProto.exception)) + } + + StatementCase.BREAK_STATEMENT -> { + val breakProto = proto.breakStatement + JsBreak(if (breakProto.hasLabelId()) JsNameRef(deserializeName(breakProto.labelId)) else null) + } + + StatementCase.CONTINUE_STATEMENT -> { + val continueProto = proto.continueStatement + JsContinue(if (continueProto.hasLabelId()) JsNameRef(deserializeName(continueProto.labelId)) else null) + } + + StatementCase.DEBUGGER -> { + JsDebugger() + } + + StatementCase.EXPRESSION -> { + val expressionProto = proto.expression + JsExpressionStatement(deserialize(expressionProto.expression)) + } + + StatementCase.VARS -> { + deserializeVars(proto.vars) + } + + StatementCase.BLOCK -> { + val blockProto = proto.block + val block = JsBlock() + block.statements += blockProto.statementList.map { deserialize(it) } + block + } + + StatementCase.GLOBAL_BLOCK -> { + deserializeGlobalBlock(proto.globalBlock) + } + + StatementCase.LABEL -> { + val labelProto = proto.label + JsLabel(deserializeName(labelProto.nameId), deserialize(labelProto.innerStatement)) + } + + StatementCase.IF_STATEMENT -> { + val ifProto = proto.ifStatement + JsIf( + deserialize(ifProto.condition), + deserialize(ifProto.thenStatement), + if (ifProto.hasElseStatement()) deserialize(ifProto.elseStatement) else null + ) + } + + StatementCase.SWITCH_STATEMENT -> { + val switchProto = proto.switchStatement + JsSwitch( + deserialize(switchProto.expression), + switchProto.entryList.map { entryProto -> + val member = if (entryProto.hasLabel()) { + JsCase().apply { caseExpression = deserialize(entryProto.label) } + } + else { + JsDefault() + } + member.statements += entryProto.statementList.map { deserialize(it) } + member + } + ) + } + + StatementCase.WHILE_STATEMENT -> { + val whileProto = proto.whileStatement + JsWhile(deserialize(whileProto.condition), deserialize(whileProto.body)) + } + + StatementCase.DO_WHILE_STATEMENT -> { + val doWhileProto = proto.doWhileStatement + JsDoWhile(deserialize(doWhileProto.condition), deserialize(doWhileProto.body)) + } + + StatementCase.FOR_STATEMENT -> { + val forProto = proto.forStatement + val initVars = if (forProto.hasVariables()) deserializeVars(forProto.variables) else null + val initExpr = if (forProto.hasExpression()) deserialize(forProto.expression) else null + val condition = if (forProto.hasCondition()) deserialize(forProto.condition) else null + val increment = if (forProto.hasIncrement()) deserialize(forProto.increment) else null + val body = deserialize(forProto.body) + if (initVars != null) { + JsFor(initVars, condition, increment, body) + } + else { + JsFor(initExpr!!, condition, increment, body) + } + } + + StatementCase.FOR_IN_STATEMENT -> { + val forInProto = proto.forInStatement + val iterName = if (forInProto.hasNameId()) deserializeName(forInProto.nameId) else null + val iterExpr = if (forInProto.hasExpression()) deserialize(forInProto.expression) else null + val iterable = deserialize(forInProto.iterable) + val body = deserialize(forInProto.body) + JsForIn(iterName, iterExpr, iterable, body) + } + + StatementCase.TRY_STATEMENT -> { + val tryProto = proto.tryStatement + val tryBlock = deserialize(tryProto.tryBlock) as JsBlock + val catchBlock = if (tryProto.hasCatchBlock()) { + val catchProto = tryProto.catchBlock + JsCatch(deserializeName(catchProto.parameter.nameId)).apply { + body = deserialize(catchProto.body) as JsBlock + } + } + else { + null + } + val finallyBlock = if (tryProto.hasFinallyBlock()) deserialize(tryProto.finallyBlock) as JsBlock else null + JsTry(tryBlock, catchBlock, finallyBlock) + } + + StatementCase.EMPTY -> JsEmpty + + StatementCase.STATEMENT_NOT_SET, + null -> error("Statement not set") + } + + private fun deserialize(proto: Expression): JsExpression = when(proto.expressionCase) { + ExpressionCase.THIS_LITERAL -> JsLiteral.THIS + ExpressionCase.NULL_LITERAL -> JsLiteral.NULL + ExpressionCase.TRUE_LITERAL -> JsLiteral.TRUE + ExpressionCase.FALSE_LITERAL -> JsLiteral.FALSE + ExpressionCase.STRING_LITERAL -> program.getStringLiteral(deserializeString(proto.stringLiteral.stringId)) + ExpressionCase.INT_LITERAL -> program.getNumberLiteral(proto.intLiteral.value) + ExpressionCase.DOUBLE_LITERAL -> program.getNumberLiteral(proto.doubleLiteral.value) + ExpressionCase.SIMPLE_NAME_REFERENCE -> JsNameRef(deserializeName(proto.simpleNameReference)) + + ExpressionCase.REG_EXP_LITERAL -> { + val regExpProto = proto.regExpLiteral + JsRegExp().apply { + pattern = deserializeString(regExpProto.patternStringId) + if (regExpProto.hasFlagsStringId()) { + flags = deserializeString(regExpProto.flagsStringId) + } + } + } + + ExpressionCase.ARRAY_LITERAL -> { + val arrayProto = proto.arrayLiteral + JsArrayLiteral(arrayProto.elementList.map { deserialize(it) }) + } + + ExpressionCase.OBJECT_LITERAL -> { + val objectProto = proto.objectLiteral + JsObjectLiteral( + objectProto.entryList.map { entryProto -> + JsPropertyInitializer(deserialize(entryProto.key), deserialize(entryProto.value)) + }, + objectProto.multiline + ) + } + + ExpressionCase.FUNCTION -> { + val functionProto = proto.function + JsFunction(scope, deserialize(functionProto.body) as JsBlock, "").apply { + parameters += functionProto.parameterList.map { deserializeParameter(it) } + if (functionProto.hasNameId()) { + name = deserializeName(functionProto.nameId) + } + } + } + + ExpressionCase.DOC_COMMENT -> { + val docCommentProto = proto.docComment + JsDocComment(docCommentProto.tagList.associate { tagProto -> + val name = deserializeString(tagProto.nameId) + val value: Any = if (tagProto.hasExpression()) { + deserialize(tagProto.expression) + } + else { + deserializeString(tagProto.valueStringId) + } + name to value + }) + } + + ExpressionCase.BINARY -> { + val binaryProto = proto.binary + JsBinaryOperation(map(binaryProto.type), deserialize(binaryProto.left), deserialize(binaryProto.right)) + } + + ExpressionCase.UNARY -> { + val unaryProto = proto.unary + val type = map(unaryProto.type) + val operand = deserialize(unaryProto.operand) + if (unaryProto.postfix) JsPostfixOperation(type, operand) else JsPrefixOperation(type, operand) + } + + ExpressionCase.CONDITIONAL -> { + val conditionalProto = proto.conditional + JsConditional( + deserialize(conditionalProto.testExpression), + deserialize(conditionalProto.thenExpression), + deserialize(conditionalProto.elseExpression) + ) + } + + ExpressionCase.ARRAY_ACCESS -> { + val arrayAccessProto = proto.arrayAccess + JsArrayAccess(deserialize(arrayAccessProto.array), deserialize(arrayAccessProto.index)) + } + + ExpressionCase.NAME_REFERENCE -> { + val nameRefProto = proto.nameReference + JsNameRef(deserializeName(nameRefProto.nameId), if (nameRefProto.hasQualifier()) deserialize(nameRefProto.qualifier) else null) + } + + ExpressionCase.PROPERTY_REFERENCE -> { + val propertyRefProto = proto.propertyReference + val qualifier = if (propertyRefProto.hasQualifier()) deserialize(propertyRefProto.qualifier) else null + JsNameRef(deserializeString(propertyRefProto.stringId), qualifier) + } + + ExpressionCase.INVOCATION -> { + val invocationProto = proto.invocation + JsInvocation( + deserialize(invocationProto.qualifier), + invocationProto.argumentList.map { deserialize(it) } + ) + } + + ExpressionCase.INSTANTIATION -> { + val instantiationProto = proto.instantiation + JsNew( + deserialize(instantiationProto.qualifier), + instantiationProto.argumentList.map { deserialize(it) } + ) + } + + null, + ExpressionCase.EXPRESSION_NOT_SET -> error("Unknown expression") + } + + private fun deserializeVars(proto: Vars): JsVars { + val vars = JsVars(proto.multiline) + for (declProto in proto.declarationList) { + val initialValue = if (declProto.hasInitialValue()) deserialize(declProto.initialValue) else null + vars.vars += JsVars.JsVar(deserializeName(declProto.nameId), initialValue) + } + return vars + } + + private fun deserializeGlobalBlock(proto: GlobalBlock): JsGlobalBlock { + return JsGlobalBlock().apply { statements += proto.statementList.map { deserialize(it) } } + } + + private fun deserializeParameter(proto: Parameter): JsParameter { + return JsParameter(deserializeName(proto.nameId)) + } + + private fun deserializeName(id: Int): JsName { + return nameCache[id] ?: let { + val nameProto = nameTable[id] + val identifier = deserializeString(nameProto.identifier) + val name = if (nameProto.temporary) { + JsScope.declareTemporaryName(identifier) + } + else { + JsDynamicScope.declareName(identifier) + } + nameCache[id] = name + name + } + } + + private fun deserializeString(id: Int): String = stringTable[id] + + private fun map(op: BinaryOperation.Type) = when (op) { + BinaryOperation.Type.MUL -> JsBinaryOperator.MUL + BinaryOperation.Type.DIV -> JsBinaryOperator.DIV + BinaryOperation.Type.MOD -> JsBinaryOperator.MOD + BinaryOperation.Type.ADD -> JsBinaryOperator.ADD + BinaryOperation.Type.SUB -> JsBinaryOperator.SUB + BinaryOperation.Type.SHL -> JsBinaryOperator.SHL + BinaryOperation.Type.SHR -> JsBinaryOperator.SHR + BinaryOperation.Type.SHRU -> JsBinaryOperator.SHRU + BinaryOperation.Type.LT -> JsBinaryOperator.LT + BinaryOperation.Type.LTE -> JsBinaryOperator.LTE + BinaryOperation.Type.GT -> JsBinaryOperator.GT + BinaryOperation.Type.GTE -> JsBinaryOperator.GTE + BinaryOperation.Type.INSTANCEOF -> JsBinaryOperator.INSTANCEOF + BinaryOperation.Type.IN -> JsBinaryOperator.INOP + BinaryOperation.Type.EQ -> JsBinaryOperator.EQ + BinaryOperation.Type.NEQ -> JsBinaryOperator.NEQ + BinaryOperation.Type.REF_EQ -> JsBinaryOperator.REF_EQ + BinaryOperation.Type.REF_NEQ -> JsBinaryOperator.REF_NEQ + BinaryOperation.Type.BIT_AND -> JsBinaryOperator.BIT_AND + BinaryOperation.Type.BIT_XOR -> JsBinaryOperator.BIT_XOR + BinaryOperation.Type.BIT_OR -> JsBinaryOperator.BIT_OR + BinaryOperation.Type.AND -> JsBinaryOperator.AND + BinaryOperation.Type.OR -> JsBinaryOperator.OR + BinaryOperation.Type.ASG -> JsBinaryOperator.ASG + BinaryOperation.Type.ASG_ADD -> JsBinaryOperator.ASG_ADD + BinaryOperation.Type.ASG_SUB -> JsBinaryOperator.ASG_SUB + BinaryOperation.Type.ASG_MUL -> JsBinaryOperator.ASG_MUL + BinaryOperation.Type.ASG_DIV -> JsBinaryOperator.ASG_DIV + BinaryOperation.Type.ASG_MOD -> JsBinaryOperator.ASG_MOD + BinaryOperation.Type.ASG_SHL -> JsBinaryOperator.ASG_SHL + BinaryOperation.Type.ASG_SHR -> JsBinaryOperator.ASG_SHR + BinaryOperation.Type.ASG_SHRU -> JsBinaryOperator.ASG_SHRU + BinaryOperation.Type.ASG_BIT_AND -> JsBinaryOperator.ASG_BIT_AND + BinaryOperation.Type.ASG_BIT_OR -> JsBinaryOperator.ASG_BIT_OR + BinaryOperation.Type.ASG_BIT_XOR -> JsBinaryOperator.ASG_BIT_XOR + BinaryOperation.Type.COMMA -> JsBinaryOperator.COMMA + } + + private fun map(op: UnaryOperation.Type) = when (op) { + UnaryOperation.Type.BIT_NOT -> JsUnaryOperator.BIT_NOT + UnaryOperation.Type.DEC -> JsUnaryOperator.DEC + UnaryOperation.Type.DELETE -> JsUnaryOperator.DELETE + UnaryOperation.Type.INC -> JsUnaryOperator.INC + UnaryOperation.Type.NEG -> JsUnaryOperator.NEG + UnaryOperation.Type.POS -> JsUnaryOperator.POS + UnaryOperation.Type.NOT -> JsUnaryOperator.NOT + UnaryOperation.Type.TYPEOF -> JsUnaryOperator.TYPEOF + UnaryOperation.Type.VOID -> JsUnaryOperator.VOID + } +} diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/facade/K2JSTranslator.java b/js/js.translator/src/org/jetbrains/kotlin/js/facade/K2JSTranslator.java index 2903644625f..b1bdbb0d683 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/facade/K2JSTranslator.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/facade/K2JSTranslator.java @@ -16,7 +16,6 @@ package org.jetbrains.kotlin.js.facade; -import com.intellij.util.Base64; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.kotlin.descriptors.ModuleDescriptor; @@ -38,8 +37,10 @@ import org.jetbrains.kotlin.progress.ProgressIndicatorAndCompilationCanceledStat import org.jetbrains.kotlin.psi.KtFile; import org.jetbrains.kotlin.resolve.BindingTrace; import org.jetbrains.kotlin.resolve.diagnostics.Diagnostics; +import org.jetbrains.kotlin.serialization.js.ast.JsAstDeserializer; import org.jetbrains.kotlin.serialization.js.ast.JsAstSerializer; +import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.util.ArrayList; import java.util.List; @@ -91,14 +92,20 @@ public final class K2JSTranslator { // TODO: temporary code for testing purposes, remove later JsAstSerializer serializer = new JsAstSerializer(); + JsAstDeserializer deserializer = new JsAstDeserializer(new JsProgram()); JsProgram program = translationResult.getProgram(); int bytesTotal = 0; for (JsProgramFragment fragment : translationResult.getFragments()) { ByteArrayOutputStream output = new ByteArrayOutputStream(); serializer.serialize(fragment, output); bytesTotal += output.size(); - String base64 = Base64.encode(output.toByteArray()); - program.getGlobalBlock().getStatements().add(program.getStringLiteral(base64).makeStmt()); + + ByteArrayInputStream input = new ByteArrayInputStream(output.toByteArray()); + JsProgramFragment deserializedFragment = deserializer.deserialize(input); + + String expected = fragmentToString(fragment); + String actual = fragmentToString(deserializedFragment); + assert expected.equals(actual) : "Deserialization: " + expected + "\n\nvs.\n\n" + actual; } program.getGlobalBlock().getStatements().add(program.getNumberLiteral(bytesTotal).makeStmt()); @@ -123,4 +130,9 @@ public final class K2JSTranslator { return new TranslationResult.Success(config, files, translationResult.getProgram(), diagnostics, importedModules, moduleDescriptor, bindingTrace.getBindingContext()); } + + private static String fragmentToString(JsProgramFragment fragment) { + return fragment.getDeclarationBlock().toString() + fragment.getInitializerBlock().toString() + + fragment.getExportBlock().toString(); + } }