JS: add AST deserializer

This commit is contained in:
Alexey Andreev
2017-02-16 19:22:55 +03:00
parent 4d3a86a183
commit 8d2eb344ff
3 changed files with 449 additions and 3 deletions
@@ -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);
@@ -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<String>()
private val nameTable = mutableListOf<Name>()
private val nameCache = mutableListOf<JsName?>()
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
}
}
@@ -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();
}
}