From 4b8c7c52878d7721a98dda0e1ffd583d67c595dc Mon Sep 17 00:00:00 2001 From: Federico Tomassetti Date: Fri, 13 Sep 2024 12:18:47 +0200 Subject: [PATCH] Generalize printing of placeholders --- .../strumenta/kolasu/codegen/PrinterOutput.kt | 4 +- .../kolasu/transformation/Transformation.kt | 44 ++++++++++++++----- .../strumenta/kolasu/codegen/KotlinPrinter.kt | 3 +- .../kolasu/lionweb/LionWebModelConverter.kt | 3 +- 4 files changed, 41 insertions(+), 13 deletions(-) diff --git a/core/src/main/kotlin/com/strumenta/kolasu/codegen/PrinterOutput.kt b/core/src/main/kotlin/com/strumenta/kolasu/codegen/PrinterOutput.kt index cfcbe887..ea81c653 100644 --- a/core/src/main/kotlin/com/strumenta/kolasu/codegen/PrinterOutput.kt +++ b/core/src/main/kotlin/com/strumenta/kolasu/codegen/PrinterOutput.kt @@ -4,7 +4,9 @@ import com.strumenta.kolasu.model.Node import com.strumenta.kolasu.model.Position import com.strumenta.kolasu.model.START_POINT import com.strumenta.kolasu.model.TextFileDestination +import com.strumenta.kolasu.transformation.FailingASTTransformation import com.strumenta.kolasu.transformation.MissingASTTransformation +import com.strumenta.kolasu.transformation.PlaceholderASTTransformation import kotlin.reflect.KClass import kotlin.reflect.full.superclasses @@ -99,7 +101,7 @@ class PrinterOutput( if (overrider != null) { return overrider } - val properPrinter = if (ast.origin is MissingASTTransformation && placeholderNodePrinter != null) { + val properPrinter = if (ast.origin is PlaceholderASTTransformation && placeholderNodePrinter != null) { placeholderNodePrinter } else { nodePrinters[kclass] diff --git a/core/src/main/kotlin/com/strumenta/kolasu/transformation/Transformation.kt b/core/src/main/kotlin/com/strumenta/kolasu/transformation/Transformation.kt index 1bb96beb..d87fd23a 100644 --- a/core/src/main/kotlin/com/strumenta/kolasu/transformation/Transformation.kt +++ b/core/src/main/kotlin/com/strumenta/kolasu/transformation/Transformation.kt @@ -4,6 +4,7 @@ import com.strumenta.kolasu.model.* import com.strumenta.kolasu.validation.Issue import com.strumenta.kolasu.validation.IssueSeverity import java.lang.reflect.ParameterizedType +import kotlin.random.Random import org.antlr.v4.runtime.tree.ParseTree import kotlin.reflect.KClass import kotlin.reflect.KMutableProperty1 @@ -240,19 +241,15 @@ data class ChildNodeFactory( */ private val NO_CHILD_NODE = ChildNodeFactory("", { x -> x }, { _, _ -> }, Node::class) -class MissingASTTransformation(val origin: Origin?) : Origin { +sealed class PlaceholderASTTransformation(val origin: Origin?) : Origin { override val position: Position? get() = origin?.position override val sourceText: String? get() = origin?.sourceText } -class FailingASTTransformation(val origin: Origin?) : Origin { - override val position: Position? - get() = origin?.position - override val sourceText: String? - get() = origin?.sourceText -} +class MissingASTTransformation(origin: Origin?) : PlaceholderASTTransformation(origin) +class FailingASTTransformation(origin: Origin?) : PlaceholderASTTransformation(origin) /** * Implementation of a tree-to-tree transformation. For each source node type, we can register a factory that knows how @@ -469,7 +466,7 @@ open class ASTTransformer( inline fun registerNodeFactory(kclass: KClass, crossinline factory: (S) -> T?): NodeFactory = registerNodeFactory(kclass) { input, _, _ -> try { factory(input) - } catch (t: Throwable) { + } catch (t: NotImplementedError) { if (faultTollerant) { val node = T::class.dummyInstance() node.origin = FailingASTTransformation(asOrigin(input)) @@ -477,6 +474,14 @@ open class ASTTransformer( } else { throw RuntimeException("Failed to transform $input into $kclass", t) } + }catch (e: Exception) { + if (faultTollerant) { + val node = T::class.dummyInstance() + node.origin = FailingASTTransformation(asOrigin(input)) + node + } else { + throw RuntimeException("Failed to transform $input into $kclass", e) + } } } @@ -634,8 +639,24 @@ private fun KClass.isDirectlyOrIndirectlyInstantiable(): Boolean { private fun KClass.toInstantiableType() : KClass { return when { this.isSealed -> { - val subclass = this.sealedSubclasses.find { it.isDirectlyOrIndirectlyInstantiable() } - subclass?.toInstantiableType() ?: throw IllegalStateException("$this has no instantiable sealed subclasses") + val subclasses = this.sealedSubclasses.filter { it.isDirectlyOrIndirectlyInstantiable() } + if (subclasses.isEmpty()) { + throw IllegalStateException("$this has no instantiable sealed subclasses") + } + val subClassWithEmptyParam = subclasses.find { it.constructors.any { it.parameters.isEmpty() } } + if (subClassWithEmptyParam == null) { + if (subclasses.size == 1) { + subclasses.first().toInstantiableType() + } else { + // Some constructs are recursive (think of the ArrayType) + // We either find complex logic to find the ones that aren't or just pick one randomly. Eventually we will build + // a tree + val r = Random.Default + subclasses[r.nextInt(subclasses.size)].toInstantiableType() + } + } else { + subClassWithEmptyParam.toInstantiableType() + } } this.isAbstract -> { throw IllegalStateException("We cannot instantiate an abstract class (but we can handle sealed classes)") @@ -662,6 +683,9 @@ fun KClass.dummyInstance() : T { val value = when { param.type.isMarkedNullable -> null mt is ParameterizedType && mt.rawType == List::class.java -> mutableListOf() + (param.type.classifier as KClass<*>).isSubclassOf(Node::class) -> (param.type.classifier as KClass).dummyInstance() + param.type == String::class.createType() -> "DUMMY" + param.type.classifier == ReferenceByName::class -> ReferenceByName("UNKNOWN") else -> TODO() } params[param] = value diff --git a/core/src/test/kotlin/com/strumenta/kolasu/codegen/KotlinPrinter.kt b/core/src/test/kotlin/com/strumenta/kolasu/codegen/KotlinPrinter.kt index ad719839..8314af8e 100644 --- a/core/src/test/kotlin/com/strumenta/kolasu/codegen/KotlinPrinter.kt +++ b/core/src/test/kotlin/com/strumenta/kolasu/codegen/KotlinPrinter.kt @@ -2,12 +2,13 @@ package com.strumenta.kolasu.codegen import com.strumenta.kolasu.model.Node import com.strumenta.kolasu.transformation.MissingASTTransformation +import com.strumenta.kolasu.transformation.PlaceholderASTTransformation class KotlinPrinter : ASTCodeGenerator() { override val placeholderNodePrinter: NodePrinter get() = NodePrinter { output: PrinterOutput, ast: Node -> - val origin = (ast.origin as MissingASTTransformation).origin + val origin = (ast.origin as PlaceholderASTTransformation).origin val nodeType = if (origin is Node) { origin.nodeType } else { diff --git a/lionweb/src/main/kotlin/com/strumenta/kolasu/lionweb/LionWebModelConverter.kt b/lionweb/src/main/kotlin/com/strumenta/kolasu/lionweb/LionWebModelConverter.kt index bd17e0ab..3def183d 100644 --- a/lionweb/src/main/kotlin/com/strumenta/kolasu/lionweb/LionWebModelConverter.kt +++ b/lionweb/src/main/kotlin/com/strumenta/kolasu/lionweb/LionWebModelConverter.kt @@ -59,6 +59,7 @@ import kotlin.reflect.KMutableProperty import kotlin.reflect.KParameter import kotlin.reflect.full.primaryConstructor import io.lionweb.lioncore.java.language.Feature as LWFeature +import com.strumenta.kolasu.transformation.PlaceholderASTTransformation interface PrimitiveValueSerialization { fun serialize(value: E): String @@ -218,7 +219,7 @@ class LionWebModelConverter( if (origin is KNode) { val targetID = myIDManager.nodeId(origin) setOriginalNode(lwNode, targetID) - } else if (origin is MissingASTTransformation) { + } else if (origin is PlaceholderASTTransformation) { if (lwNode is AbstractClassifierInstance<*>) { val instance = DynamicAnnotationInstance( StarLasuLWLanguage.PlaceholderNode.id,