From 49b5578ba2ef84e6705cf855e4f2ef7806b3b70f Mon Sep 17 00:00:00 2001 From: Federico Tomassetti Date: Thu, 19 Sep 2024 08:37:29 +0200 Subject: [PATCH] Addressing comments on PR #383 --- .../kolasu/transformation/DummyNodes.kt | 28 +++++++++++-------- .../PlaceholderASTTransformation.kt | 20 +++++++++++++ .../kolasu/transformation/Transformation.kt | 7 +++++ .../symbol/resolver/SymbolResolver.kt | 10 ++++++- 4 files changed, 53 insertions(+), 12 deletions(-) diff --git a/core/src/main/kotlin/com/strumenta/kolasu/transformation/DummyNodes.kt b/core/src/main/kotlin/com/strumenta/kolasu/transformation/DummyNodes.kt index f0dca94ab..0e6d394fe 100644 --- a/core/src/main/kotlin/com/strumenta/kolasu/transformation/DummyNodes.kt +++ b/core/src/main/kotlin/com/strumenta/kolasu/transformation/DummyNodes.kt @@ -3,8 +3,9 @@ package com.strumenta.kolasu.transformation import com.strumenta.kolasu.model.Node import com.strumenta.kolasu.model.PossiblyNamed import com.strumenta.kolasu.model.ReferenceByName +import com.strumenta.kolasu.model.isContainment +import com.strumenta.kolasu.model.nodeProperties import java.lang.reflect.ParameterizedType -import kotlin.random.Random import kotlin.reflect.KClass import kotlin.reflect.KParameter import kotlin.reflect.full.createType @@ -18,8 +19,8 @@ import kotlin.reflect.jvm.javaType * Typically, the only goal of the element would be to hold some annotation that indicates that the element * is representing an error or a missing transformation or something of that sort. */ -fun KClass.dummyInstance(): T { - val kClassToInstantiate = this.toInstantiableType() +fun KClass.dummyInstance(levelOfDummyTree: Int = 0): T { + val kClassToInstantiate = this.toInstantiableType(levelOfDummyTree) val emptyConstructor = kClassToInstantiate.constructors.find { it.parameters.isEmpty() } if (emptyConstructor != null) { return emptyConstructor.call() @@ -32,7 +33,7 @@ fun KClass.dummyInstance(): T { 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.classifier as KClass).dummyInstance(levelOfDummyTree + 1) param.type == String::class.createType() -> "DUMMY" param.type.classifier == ReferenceByName::class -> ReferenceByName("UNKNOWN") param.type == Int::class.createType() -> 0 @@ -49,7 +50,7 @@ fun KClass.dummyInstance(): T { return constructor.callBy(params) } -private fun KClass.toInstantiableType(): KClass { +private fun KClass.toInstantiableType(levelOfDummyTree: Int = 0): KClass { return when { this.isSealed -> { val subclasses = this.sealedSubclasses.filter { it.isDirectlyOrIndirectlyInstantiable() } @@ -59,16 +60,21 @@ private fun KClass.toInstantiableType(): KClass { val subClassWithEmptyParam = subclasses.find { it.constructors.any { it.parameters.isEmpty() } } if (subClassWithEmptyParam == null) { if (subclasses.size == 1) { - subclasses.first().toInstantiableType() + subclasses.first().toInstantiableType(levelOfDummyTree + 1) } 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() + // So we want to avoid just using the same subclass, repeatedly as it would lead to an + // infinite loop. Therefore we sort subclasses by the order of containments, preferring the ones + // with no or few containments and we take them consider the level of depth in the dummy tree + val subclassesByNumberOfContainments = subclasses.map { + it to it.nodeProperties.count { + it.isContainment() + } + }.toList().sortedBy { it.second }.map { it.first } + subclasses[levelOfDummyTree].toInstantiableType(levelOfDummyTree + 1) } } else { - subClassWithEmptyParam.toInstantiableType() + subClassWithEmptyParam.toInstantiableType(levelOfDummyTree + 1) } } this.isAbstract -> { diff --git a/core/src/main/kotlin/com/strumenta/kolasu/transformation/PlaceholderASTTransformation.kt b/core/src/main/kotlin/com/strumenta/kolasu/transformation/PlaceholderASTTransformation.kt index cff213a00..72cc11163 100644 --- a/core/src/main/kotlin/com/strumenta/kolasu/transformation/PlaceholderASTTransformation.kt +++ b/core/src/main/kotlin/com/strumenta/kolasu/transformation/PlaceholderASTTransformation.kt @@ -3,8 +3,28 @@ package com.strumenta.kolasu.transformation import com.strumenta.kolasu.model.Node import com.strumenta.kolasu.model.Origin import com.strumenta.kolasu.model.Position +import com.strumenta.kolasu.traversing.walkAncestors import kotlin.reflect.KClass +/** + * This indicates if the Node itself is marked as a placeholder. Note that the Node could not be directly marked + * as such and still be the descendants of such type of Node. In other words it could be in a placeholder tree. + * This operation is not expensive to perform. + */ +val Node.isDirectlyPlaceholderASTTransformation: Boolean + get() = this.origin is PlaceholderASTTransformation + +/** + * This indicates if the Node itself is marked as a placeholder or if any of its ancestors are. Note that the Node + * could not be directly marked as such and still be the descendants of such type of Node. In other words it could be + * in a placeholder tree. + * This operation is expensive to perform. + */ +val Node.isDirectlyOrIndirectlyAPlaceholderASTTransformation: Boolean + get() = this.isDirectlyPlaceholderASTTransformation || this.walkAncestors().any { + it.isDirectlyPlaceholderASTTransformation + } + /** * This is used to indicate that a Node represents some form of placeholders to be used in transformation. */ 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 ea38ae8f3..b1caf7656 100644 --- a/core/src/main/kotlin/com/strumenta/kolasu/transformation/Transformation.kt +++ b/core/src/main/kotlin/com/strumenta/kolasu/transformation/Transformation.kt @@ -455,6 +455,9 @@ open class ASTTransformer( crossinline factory: S.(ASTTransformer) -> T? ): NodeFactory = registerNodeFactory(S::class) { source, transformer, _ -> source.factory(transformer) } + /** + * We need T to be reified because we may need to install dummy classes of T. + */ inline fun registerNodeFactory( kclass: KClass, crossinline factory: (S) -> T? @@ -607,6 +610,10 @@ open class ASTTransformer( return nodeFactory } + /** + * Here the method needs to be inlined and the type parameter reified as in the invoked + * registerNodeFactory we need to access the nodeClass + */ inline fun registerIdentityTransformation(nodeClass: KClass) = registerNodeFactory(nodeClass) { node -> node }.skipChildren() diff --git a/semantics/src/main/kotlin/com/strumenta/kolasu/semantics/symbol/resolver/SymbolResolver.kt b/semantics/src/main/kotlin/com/strumenta/kolasu/semantics/symbol/resolver/SymbolResolver.kt index 1dfcf2883..ae0082434 100644 --- a/semantics/src/main/kotlin/com/strumenta/kolasu/semantics/symbol/resolver/SymbolResolver.kt +++ b/semantics/src/main/kotlin/com/strumenta/kolasu/semantics/symbol/resolver/SymbolResolver.kt @@ -7,6 +7,7 @@ import com.strumenta.kolasu.model.children import com.strumenta.kolasu.model.kReferenceByNameType import com.strumenta.kolasu.model.nodeProperties import com.strumenta.kolasu.semantics.scope.provider.ScopeProvider +import com.strumenta.kolasu.transformation.isDirectlyPlaceholderASTTransformation import kotlin.reflect.KProperty1 import kotlin.reflect.full.isSubtypeOf @@ -52,8 +53,15 @@ open class SymbolResolver( node: Node, entireTree: Boolean = false ) { + if (node.isDirectlyPlaceholderASTTransformation) { + return + } node.references().forEach { reference -> this.resolve(node, reference) } - if (entireTree) node.children.forEach { this.resolve(it, entireTree) } + if (entireTree) { + node.children.filter { !it.isDirectlyPlaceholderASTTransformation }.forEach { + this.resolve(it, entireTree) + } + } } /**