From 4d3a86a1838b0c3c3d1cd518d3afb8169d808a07 Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Thu, 16 Feb 2017 15:08:45 +0300 Subject: [PATCH] JS: add AST serializer --- js/js.serializer/js.serializer.iml | 3 +- .../serialization/js/ast/JsAstSerializer.kt | 510 ++++++++++++++++++ .../kotlin/js/facade/K2JSTranslator.java | 19 + 3 files changed, 531 insertions(+), 1 deletion(-) create mode 100644 js/js.serializer/src/org/jetbrains/kotlin/serialization/js/ast/JsAstSerializer.kt diff --git a/js/js.serializer/js.serializer.iml b/js/js.serializer/js.serializer.iml index e388cce830b..568eb6175e4 100644 --- a/js/js.serializer/js.serializer.iml +++ b/js/js.serializer/js.serializer.iml @@ -4,7 +4,7 @@ - + @@ -12,5 +12,6 @@ + \ No newline at end of file diff --git a/js/js.serializer/src/org/jetbrains/kotlin/serialization/js/ast/JsAstSerializer.kt b/js/js.serializer/src/org/jetbrains/kotlin/serialization/js/ast/JsAstSerializer.kt new file mode 100644 index 00000000000..27cb240236f --- /dev/null +++ b/js/js.serializer/src/org/jetbrains/kotlin/serialization/js/ast/JsAstSerializer.kt @@ -0,0 +1,510 @@ +/* + * 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.BinaryOperation.Type.* +import org.jetbrains.kotlin.serialization.js.ast.JsAstProtoBuf.UnaryOperation.Type.* +import java.io.OutputStream + +class JsAstSerializer { + private val nameTableBuilder = NameTable.newBuilder() + private val stringTableBuilder = StringTable.newBuilder() + private val nameMap = mutableMapOf() + private val stringMap = mutableMapOf() + + fun serialize(fragment: JsProgramFragment, output: OutputStream) { + serialize(fragment).writeTo(output) + } + + fun serialize(fragment: JsProgramFragment): Chunk { + try { + val chunkBuilder = Chunk.newBuilder() + chunkBuilder.fragment = serializeFragment(fragment) + chunkBuilder.nameTable = nameTableBuilder.build() + chunkBuilder.stringTable = stringTableBuilder.build() + return chunkBuilder.build() + } + finally { + nameTableBuilder.clear() + stringTableBuilder.clear() + nameMap.clear() + stringMap.clear() + } + } + + private fun serializeFragment(fragment: JsProgramFragment): Fragment { + val fragmentBuilder = Fragment.newBuilder() + + for (importedModule in fragment.importedModules) { + val importedModuleBuilder = ImportedModule.newBuilder() + importedModuleBuilder.externalNameId = serialize(importedModule.externalName) + importedModuleBuilder.internalNameId = serialize(importedModule.internalName) + importedModule.plainReference?.let { importedModuleBuilder.plainReference = serialize(it) } + fragmentBuilder.addImportedModule(importedModuleBuilder) + } + + for ((signature, expression) in fragment.imports) { + val importBuilder = Import.newBuilder() + importBuilder.signatureId = serialize(signature) + importBuilder.expression = serialize(expression) + fragmentBuilder.addImportEntry(importBuilder) + } + + fragmentBuilder.declarationBlock = serializeBlock(fragment.declarationBlock) + fragmentBuilder.initializerBlock = serializeBlock(fragment.initializerBlock) + fragmentBuilder.exportBlock = serializeBlock(fragment.exportBlock) + + for (nameBinding in fragment.nameBindings) { + val nameBindingBuilder = NameBinding.newBuilder() + nameBindingBuilder.signatureId = serialize(nameBinding.key) + nameBindingBuilder.nameId = serialize(nameBinding.name) + fragmentBuilder.addNameBinding(nameBindingBuilder) + } + + fragment.classes.values.forEach { fragmentBuilder.addClassModel(serialize(it)) } + + val inlineModuleExprMap = mutableMapOf() + for ((signature, expression) in fragment.inlineModuleMap) { + val inlineModuleBuilder = InlineModule.newBuilder() + inlineModuleBuilder.signatureId = serialize(signature) + inlineModuleBuilder.expressionId = inlineModuleExprMap.getOrPut(expression) { + val result = fragmentBuilder.moduleExpressionCount + fragmentBuilder.addModuleExpression(serialize(expression)) + result + } + fragmentBuilder.addInlineModule(inlineModuleBuilder) + } + + return fragmentBuilder.build() + } + + private fun serialize(classModel: JsClassModel): ClassModel { + val builder = ClassModel.newBuilder() + builder.nameId = serialize(classModel.name) + classModel.superName?.let { builder.superNameId = serialize(it) } + if (classModel.postDeclarationBlock.statements.isNotEmpty()) { + builder.postDeclarationBlock = serializeBlock(classModel.postDeclarationBlock) + } + return builder.build() + } + + private fun serialize(statement: JsStatement): Statement { + val visitor = object : JsVisitor() { + val builder = Statement.newBuilder() + + override fun visitReturn(x: JsReturn) { + val returnBuilder = Return.newBuilder() + x.expression?.let { returnBuilder.value = serialize(it) } + builder.returnStatement = returnBuilder.build() + } + + override fun visitThrow(x: JsThrow) { + val throwBuilder = Throw.newBuilder() + throwBuilder.exception = serialize(x.expression) + builder.throwStatement = throwBuilder.build() + } + + override fun visitBreak(x: JsBreak) { + val breakBuilder = Break.newBuilder() + x.label?.let { breakBuilder.labelId = serialize(it.name!!) } + builder.breakStatement = breakBuilder.build() + } + + override fun visitContinue(x: JsContinue) { + val continueBuilder = Continue.newBuilder() + x.label?.let { continueBuilder.labelId = serialize(it.name!!) } + builder.continueStatement = continueBuilder.build() + } + + override fun visitDebugger(x: JsDebugger) { + builder.debugger = Debugger.newBuilder().build() + } + + override fun visitExpressionStatement(x: JsExpressionStatement) { + val statementBuilder = ExpressionStatement.newBuilder() + statementBuilder.expression = serialize(x.expression) + builder.expression = statementBuilder.build() + } + + override fun visitVars(x: JsVars) { + builder.vars = serializeVars(x) + } + + override fun visitBlock(x: JsBlock) { + if (x is JsGlobalBlock) { + builder.globalBlock = serializeBlock(x) + } + else { + val blockBuilder = Block.newBuilder() + for (part in x.statements) { + blockBuilder.addStatement(serialize(part)) + } + builder.block = blockBuilder.build() + } + } + + override fun visitLabel(x: JsLabel) { + val labelBuilder = Label.newBuilder() + labelBuilder.nameId = serialize(x.name) + labelBuilder.innerStatement = serialize(x.statement) + builder.label = labelBuilder.build() + } + + override fun visitIf(x: JsIf) { + val ifBuilder = If.newBuilder() + ifBuilder.condition = serialize(x.ifExpression) + ifBuilder.thenStatement = serialize(x.thenStatement) + x.elseStatement?.let { ifBuilder.elseStatement = serialize(it) } + builder.ifStatement = ifBuilder.build() + } + + override fun visit(x: JsSwitch) { + val switchBuilder = Switch.newBuilder() + switchBuilder.expression = serialize(x.expression) + for (case in x.cases) { + val entryBuilder = SwitchEntry.newBuilder() + if (case is JsCase) { + entryBuilder.label = serialize(case.caseExpression) + } + for (part in case.statements) { + entryBuilder.addStatement(serialize(part)) + } + switchBuilder.addEntry(entryBuilder) + } + builder.switchStatement = switchBuilder.build() + } + + override fun visitWhile(x: JsWhile) { + val whileBuilder = While.newBuilder() + whileBuilder.condition = serialize(x.condition) + whileBuilder.body = serialize(x.body) + builder.whileStatement = whileBuilder.build() + } + + override fun visitDoWhile(x: JsDoWhile) { + val doWhileBuilder = DoWhile.newBuilder() + doWhileBuilder.condition = serialize(x.condition) + doWhileBuilder.body = serialize(x.body) + builder.doWhileStatement = doWhileBuilder.build() + } + + override fun visitFor(x: JsFor) { + val forBuilder = For.newBuilder() + when { + x.initVars != null -> forBuilder.variables = serializeVars(x.initVars) + x.initExpression != null -> forBuilder.expression = serialize(x.initExpression) + else -> forBuilder.empty = EmptyInit.newBuilder().build() + } + x.condition?.let { forBuilder.condition = serialize(it) } + x.incrementExpression?.let { forBuilder.increment = serialize(it) } + forBuilder.body = serialize(x.body ?: JsEmpty) + builder.forStatement = forBuilder.build() + } + + override fun visitForIn(x: JsForIn) { + val forInBuilder = ForIn.newBuilder() + when { + x.iterVarName != null -> forInBuilder.nameId = serialize(x.iterVarName) + x.iterExpression != null -> forInBuilder.expression = serialize(x.iterExpression) + } + forInBuilder.iterable = serialize(x.objectExpression) + forInBuilder.body = serialize(x.body) + builder.forInStatement = forInBuilder.build() + } + + override fun visitTry(x: JsTry) { + val tryBuilder = Try.newBuilder() + tryBuilder.tryBlock = serialize(x.tryBlock) + x.catches.firstOrNull()?.let { c -> + val catchBuilder = Catch.newBuilder() + catchBuilder.parameter = serializeParameter(c.parameter) + catchBuilder.body = serialize(c.body) + tryBuilder.catchBlock = catchBuilder.build() + } + x.finallyBlock?.let { tryBuilder.finallyBlock = serialize(it) } + builder.tryStatement = tryBuilder.build() + } + + override fun visitEmpty(x: JsEmpty) { + builder.empty = Empty.newBuilder().build() + } + } + + statement.accept(visitor) + return visitor.builder.build() + } + + private fun serialize(expression: JsExpression): Expression { + val visitor = object : JsVisitor() { + val builder = Expression.newBuilder() + + override fun visitThis(x: JsLiteral.JsThisRef) { + builder.thisLiteral = ThisLiteral.newBuilder().build() + } + + override fun visitNull(x: JsNullLiteral) { + builder.nullLiteral = NullLiteral.newBuilder().build() + } + + override fun visitBoolean(x: JsLiteral.JsBooleanLiteral) { + if (x.value) { + builder.trueLiteral = TrueLiteral.newBuilder().build() + } + else { + builder.falseLiteral = FalseLiteral.newBuilder().build() + } + } + + override fun visitString(x: JsStringLiteral) { + val stringBuilder = StringLiteral.newBuilder() + stringBuilder.stringId = serialize(x.value) + builder.stringLiteral = stringBuilder.build() + } + + override fun visitRegExp(x: JsRegExp) { + val regExpBuilder = RegExpLiteral.newBuilder() + regExpBuilder.patternStringId = serialize(x.pattern) + x.flags?.let { regExpBuilder.flagsStringId = serialize(it) } + builder.regExpLiteral = regExpBuilder.build() + } + + override fun visitInt(x: JsNumberLiteral.JsIntLiteral) { + val intBuilder = IntLiteral.newBuilder() + intBuilder.value = x.value + builder.intLiteral = intBuilder.build() + } + + override fun visitDouble(x: JsNumberLiteral.JsDoubleLiteral) { + val doubleBuilder = DoubleLiteral.newBuilder() + doubleBuilder.value = x.value + builder.doubleLiteral = doubleBuilder.build() + } + + override fun visitArray(x: JsArrayLiteral) { + val arrayBuilder = ArrayLiteral.newBuilder() + x.expressions.forEach { arrayBuilder.addElement(serialize(it)) } + builder.arrayLiteral = arrayBuilder.build() + } + + override fun visitObjectLiteral(x: JsObjectLiteral) { + val objectBuilder = ObjectLiteral.newBuilder() + for (initializer in x.propertyInitializers) { + val entryBuilder = ObjectLiteralEntry.newBuilder() + entryBuilder.key = serialize(initializer.labelExpr) + entryBuilder.value = serialize(initializer.valueExpr) + objectBuilder.addEntry(entryBuilder) + } + objectBuilder.multiline = x.isMultiline + builder.objectLiteral = objectBuilder.build() + } + + override fun visitFunction(x: JsFunction) { + val functionBuilder = JsAstProtoBuf.Function.newBuilder() + x.parameters.forEach { functionBuilder.addParameter(serializeParameter(it)) } + x.name?.let { functionBuilder.nameId = serialize(it) } + functionBuilder.body = serialize(x.body) + builder.function = functionBuilder.build() + } + + override fun visitDocComment(comment: JsDocComment) { + val commentBuilder = DocComment.newBuilder() + for ((name, value) in comment.tags) { + val tagBuilder = DocCommentTag.newBuilder() + tagBuilder.nameId = serialize(name) + when (value) { + is JsNameRef -> tagBuilder.expression = serialize(value) + is String -> tagBuilder.valueStringId = serialize(value) + } + commentBuilder.addTag(tagBuilder) + } + builder.docComment = commentBuilder.build() + } + + override fun visitBinaryExpression(x: JsBinaryOperation) { + val binaryBuilder = BinaryOperation.newBuilder() + binaryBuilder.left = serialize(x.arg1) + binaryBuilder.right = serialize(x.arg2) + binaryBuilder.type = map(x.operator) + builder.binary = binaryBuilder.build() + } + + override fun visitPrefixOperation(x: JsPrefixOperation) { + builder.unary = serializeUnary(x, postfix = false) + } + + override fun visitPostfixOperation(x: JsPostfixOperation) { + builder.unary = serializeUnary(x, postfix = true) + } + + override fun visitConditional(x: JsConditional) { + val conditionalBuilder = Conditional.newBuilder() + conditionalBuilder.testExpression = serialize(x.testExpression) + conditionalBuilder.thenExpression = serialize(x.thenExpression) + conditionalBuilder.elseExpression = serialize(x.elseExpression) + builder.conditional = conditionalBuilder.build() + } + + override fun visitArrayAccess(x: JsArrayAccess) { + val arrayAccessBuilder = ArrayAccess.newBuilder() + arrayAccessBuilder.array = serialize(x.arrayExpression) + arrayAccessBuilder.index = serialize(x.indexExpression) + builder.arrayAccess = arrayAccessBuilder.build() + } + + override fun visitNameRef(nameRef: JsNameRef) { + val name = nameRef.name + val qualifier = nameRef.qualifier + if (name != null) { + if (qualifier != null) { + val nameRefBuilder = NameReference.newBuilder() + nameRefBuilder.nameId = serialize(name) + nameRefBuilder.qualifier = serialize(qualifier) + builder.nameReference = nameRefBuilder.build() + } + else { + builder.simpleNameReference = serialize(name) + } + } + else { + val propertyRefBuilder = PropertyReference.newBuilder() + propertyRefBuilder.stringId = serialize(nameRef.ident) + qualifier?.let { propertyRefBuilder.qualifier = serialize(it) } + builder.propertyReference = propertyRefBuilder.build() + } + } + + override fun visitInvocation(invocation: JsInvocation) { + val invocationBuilder = Invocation.newBuilder() + invocationBuilder.qualifier = serialize(invocation.qualifier) + invocation.arguments.forEach { invocationBuilder.addArgument(serialize(it)) } + builder.invocation = invocationBuilder.build() + } + + override fun visitNew(x: JsNew) { + val instantiationBuilder = Instantiation.newBuilder() + instantiationBuilder.qualifier = serialize(x.constructorExpression) + x.arguments.forEach { instantiationBuilder.addArgument(serialize(it)) } + builder.instantiation = instantiationBuilder.build() + } + } + + expression.accept(visitor) + return visitor.builder.build() + } + + private fun serializeParameter(parameter: JsParameter): Parameter { + val parameterBuilder = Parameter.newBuilder() + parameterBuilder.nameId = serialize(parameter.name) + return parameterBuilder.build() + } + + private fun serializeBlock(block: JsGlobalBlock): GlobalBlock { + val blockBuilder = GlobalBlock.newBuilder() + for (part in block.statements) { + blockBuilder.addStatement(serialize(part)) + } + return blockBuilder.build() + } + + private fun serializeVars(vars: JsVars): Vars { + val varsBuilder = Vars.newBuilder() + for (varDecl in vars.vars) { + val declBuilder = VarDeclaration.newBuilder() + declBuilder.nameId = serialize(varDecl.name) + varDecl.initExpression?.let { declBuilder.initialValue = serialize(it) } + varsBuilder.addDeclaration(declBuilder) + } + varsBuilder.multiline = vars.isMultiline + return varsBuilder.build() + } + + private fun serializeUnary(x: JsUnaryOperation, postfix: Boolean): UnaryOperation { + val unaryBuilder = UnaryOperation.newBuilder() + unaryBuilder.operand = serialize(x.arg) + unaryBuilder.type = map(x.operator) + unaryBuilder.postfix = postfix + return unaryBuilder.build() + } + + private fun map(op: JsBinaryOperator) = when (op) { + JsBinaryOperator.MUL -> MUL + JsBinaryOperator.DIV -> DIV + JsBinaryOperator.MOD -> MOD + JsBinaryOperator.ADD -> ADD + JsBinaryOperator.SUB -> SUB + JsBinaryOperator.SHL -> SHL + JsBinaryOperator.SHR -> SHR + JsBinaryOperator.SHRU -> SHRU + JsBinaryOperator.LT -> LT + JsBinaryOperator.LTE -> LTE + JsBinaryOperator.GT -> GT + JsBinaryOperator.GTE -> GTE + JsBinaryOperator.INSTANCEOF -> INSTANCEOF + JsBinaryOperator.INOP -> IN + JsBinaryOperator.EQ -> EQ + JsBinaryOperator.NEQ -> NEQ + JsBinaryOperator.REF_EQ -> REF_EQ + JsBinaryOperator.REF_NEQ -> REF_NEQ + JsBinaryOperator.BIT_AND -> BIT_AND + JsBinaryOperator.BIT_XOR -> BIT_XOR + JsBinaryOperator.BIT_OR -> BIT_OR + JsBinaryOperator.AND -> AND + JsBinaryOperator.OR -> OR + JsBinaryOperator.ASG -> ASG + JsBinaryOperator.ASG_ADD -> ASG_ADD + JsBinaryOperator.ASG_SUB -> ASG_SUB + JsBinaryOperator.ASG_MUL -> ASG_MUL + JsBinaryOperator.ASG_DIV -> ASG_DIV + JsBinaryOperator.ASG_MOD -> ASG_MOD + JsBinaryOperator.ASG_SHL -> ASG_SHL + JsBinaryOperator.ASG_SHR -> ASG_SHR + JsBinaryOperator.ASG_SHRU -> ASG_SHRU + JsBinaryOperator.ASG_BIT_AND -> ASG_BIT_AND + JsBinaryOperator.ASG_BIT_OR -> ASG_BIT_OR + JsBinaryOperator.ASG_BIT_XOR -> ASG_BIT_XOR + JsBinaryOperator.COMMA -> COMMA + } + + private fun map(op: JsUnaryOperator) = when (op) { + JsUnaryOperator.BIT_NOT -> BIT_NOT + JsUnaryOperator.DEC -> DEC + JsUnaryOperator.DELETE -> DELETE + JsUnaryOperator.INC -> INC + JsUnaryOperator.NEG -> NEG + JsUnaryOperator.POS -> POS + JsUnaryOperator.NOT -> NOT + JsUnaryOperator.TYPEOF -> TYPEOF + JsUnaryOperator.VOID -> VOID + } + + private fun serialize(name: JsName) = nameMap.getOrPut(name) { + val result = nameTableBuilder.entryCount + val builder = Name.newBuilder() + builder.identifier = serialize(name.ident) + builder.temporary = name.isTemporary + nameTableBuilder.addEntry(builder) + result + } + + private fun serialize(string: String) = stringMap.getOrPut(string) { + val result = stringTableBuilder.entryCount + stringTableBuilder.addEntry(string) + result + } +} \ No newline at end of file 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 bc750f3c9e2..2903644625f 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,12 +16,15 @@ 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; import org.jetbrains.kotlin.js.analyze.TopDownAnalyzerFacadeForJS; import org.jetbrains.kotlin.js.analyzer.JsAnalysisResult; import org.jetbrains.kotlin.js.backend.ast.JsImportedModule; +import org.jetbrains.kotlin.js.backend.ast.JsProgram; +import org.jetbrains.kotlin.js.backend.ast.JsProgramFragment; import org.jetbrains.kotlin.js.config.JsConfig; import org.jetbrains.kotlin.js.coroutine.CoroutineTransformer; import org.jetbrains.kotlin.js.facade.exceptions.TranslationException; @@ -35,7 +38,9 @@ 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.JsAstSerializer; +import java.io.ByteArrayOutputStream; import java.util.ArrayList; import java.util.List; @@ -84,6 +89,20 @@ public final class K2JSTranslator { JsInliner.process(config, analysisResult.getBindingTrace(), translationResult.getInnerModuleName(), translationResult.getFragments(), translationResult.getFragments()); + // TODO: temporary code for testing purposes, remove later + JsAstSerializer serializer = new JsAstSerializer(); + 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()); + } + + program.getGlobalBlock().getStatements().add(program.getNumberLiteral(bytesTotal).makeStmt()); + ResolveTemporaryNamesKt.resolveTemporaryNames(translationResult.getProgram()); ProgressIndicatorAndCompilationCanceledStatus.checkCanceled(); if (hasError(diagnostics)) return new TranslationResult.Fail(diagnostics);