Skip to content

Commit

Permalink
Change representation of Placeholder annotations in StarLasu to disti…
Browse files Browse the repository at this point in the history
…nguish between missing and failing transformations
  • Loading branch information
ftomassetti committed Sep 14, 2024
1 parent 353a78f commit 2df2e32
Show file tree
Hide file tree
Showing 9 changed files with 146 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@ import java.io.File

const val UNKNOWN_SOURCE_ID = "UNKNOWN_SOURCE"

fun String.removeCharactersInvalidInLionWebIDs(): String {
return this.filter {
it in setOf('-', '_') || it in CharRange('0', '9') ||
it in CharRange('a', 'z') ||
it in CharRange('A', 'Z')
}.toString()
}

/**
* Given a Source (even null), it generates a corresponding identifier.
*/
Expand All @@ -22,7 +30,7 @@ abstract class AbstractSourceIdProvider : SourceIdProvider {
.replace('/', '-')
.replace('#', '-')
.replace(' ', '_')
.replace("@", "_at_")
.replace("@", "_at_").removeCharactersInvalidInLionWebIDs()
}

class SimpleSourceIdProvider(var acceptNullSource: Boolean = false) : AbstractSourceIdProvider() {
Expand Down
4 changes: 2 additions & 2 deletions core/src/main/kotlin/com/strumenta/kolasu/model/Errors.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ class GenericErrorNode(error: Exception? = null, message: String? = null) : Node

private fun Throwable.message(): String {
val cause = this.cause?.message()?.let { " -> $it" } ?: ""
val msg = if (this.message != null) ": " + this.message else ""
return "${this.javaClass.simpleName}$msg$cause"
val explanation = if (this.message != null) ": " + this.message else ""
return "${this.javaClass.simpleName}$explanation$cause"
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ import kotlin.reflect.full.isSubclassOf
import kotlin.reflect.full.primaryConstructor
import kotlin.reflect.jvm.javaType

/**
* This logic instantiate a node of the given class with dummy values.
* This is useful because it permits to add an element that "fit" and make the typesystem happy.
* 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 <T : Node> KClass<T>.dummyInstance(): T {
val kClassToInstantiate = this.toInstantiableType()
val emptyConstructor = kClassToInstantiate.constructors.find { it.parameters.isEmpty() }
Expand Down Expand Up @@ -77,6 +83,10 @@ private fun <T : Any> KClass<T>.toInstantiableType(): KClass<out T> {
}
}

/**
* We can only instantiate concrete classes or sealed classes, if one its subclasses is directly or
* indirectly instantiable. For interfaces this return false.
*/
fun <T : Any> KClass<T>.isDirectlyOrIndirectlyInstantiable(): Boolean {
return if (this.isSealed) {
this.sealedSubclasses.any { it.isDirectlyOrIndirectlyInstantiable() }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.strumenta.kolasu.transformation

import com.strumenta.kolasu.model.Node
import com.strumenta.kolasu.model.Origin
import com.strumenta.kolasu.model.Position
import kotlin.reflect.KClass

/**
* This is used to indicate that a Node represents some form of placeholders to be used in transformation.
*/
sealed class PlaceholderASTTransformation(val origin: Origin?, val message: String) : Origin {
override val position: Position?
get() = origin?.position
override val sourceText: String?
get() = origin?.sourceText
}

/**
* This is used to indicate that we do not know how to transform a certain node.
*/
class MissingASTTransformation(
origin: Origin?,
val transformationSource: Any?,
val expectedType: KClass<out Node>? = null,
message: String = "Translation of a node is not yet implemented: " +
"${if (transformationSource is Node) transformationSource.simpleNodeType else transformationSource}" +
if (expectedType != null) " into $expectedType" else ""
) :
PlaceholderASTTransformation(origin, message) {
constructor(transformationSource: Node, expectedType: KClass<out Node>? = null) : this(
transformationSource,
transformationSource,
expectedType
)
}

/**
* This is used to indicate that, while we had a transformation for a given node, that failed.
* This is typically the case because the transformation covers only certain case and we encountered
* one that was not covered.
*/
class FailingASTTransformation(origin: Origin?, message: String) : PlaceholderASTTransformation(origin, message)
Original file line number Diff line number Diff line change
Expand Up @@ -238,31 +238,6 @@ data class ChildNodeFactory<Source, Target, Child : Any>(
*/
private val NO_CHILD_NODE = ChildNodeFactory<Any, Any, Any>("", { x -> x }, { _, _ -> }, Node::class)

sealed class PlaceholderASTTransformation(val origin: Origin?, val message: String) : Origin {
override val position: Position?
get() = origin?.position
override val sourceText: String?
get() = origin?.sourceText
}

class MissingASTTransformation(
origin: Origin?,
val transformationSource: Any?,
val expectedType: KClass<out Node>? = null,
message: String = "Translation of a node is not yet implemented: " +
"${if (transformationSource is Node) transformationSource.simpleNodeType else transformationSource}" +
if (expectedType != null) " into $expectedType" else ""
) :
PlaceholderASTTransformation(origin, message) {
constructor(transformationSource: Node, expectedType: KClass<out Node>? = null) : this(
transformationSource,
transformationSource,
expectedType
)
}

class FailingASTTransformation(origin: Origin?, message: String) : PlaceholderASTTransformation(origin, message)

/**
* Implementation of a tree-to-tree transformation. For each source node type, we can register a factory that knows how
* to create a transformed node. Then, this transformer can read metadata in the transformed node to recursively
Expand All @@ -278,6 +253,11 @@ open class ASTTransformer(
@Deprecated("To be removed in Kolasu 1.6")
val allowGenericNode: Boolean = true,
val throwOnUnmappedNode: Boolean = false,
/**
* When the fault tollerant flag is set, in case a transformation fails we will add a node
* with the origin FailingASTTransformation. If the flag is not set, then the transformation will just
* fail.
*/
val faultTollerant: Boolean = !throwOnUnmappedNode
) {
/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ class KotlinPrinter : ASTCodeGenerator<KCompilationUnit>() {
override val placeholderNodePrinter: NodePrinter
get() = NodePrinter { output: PrinterOutput, ast: Node ->
val placeholder = ast.origin as PlaceholderASTTransformation
val origin = placeholder.origin
output.print("/* ${placeholder.message} */")
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.strumenta.kolasu.ids

import kotlin.test.Test
import kotlin.test.assertEquals

class SourceIdProviderTest {

@Test
fun testRemoveCharactersInvalidInLionWebIDs() {
assertEquals(
"funny933--_12aaAAAAZz",
"funny%à, è, é, ì, ò, ù =+933--_12aaAAAAZz".removeCharactersInvalidInLionWebIDs()
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import com.strumenta.kolasu.model.nodeOriginalProperties
import com.strumenta.kolasu.parsing.FirstStageParsingResult
import com.strumenta.kolasu.parsing.KolasuToken
import com.strumenta.kolasu.parsing.ParsingResult
import com.strumenta.kolasu.transformation.FailingASTTransformation
import com.strumenta.kolasu.transformation.MissingASTTransformation
import com.strumenta.kolasu.transformation.PlaceholderASTTransformation
import com.strumenta.kolasu.traversing.walk
Expand All @@ -32,11 +33,13 @@ import io.lionweb.lioncore.java.language.Classifier
import io.lionweb.lioncore.java.language.Concept
import io.lionweb.lioncore.java.language.Containment
import io.lionweb.lioncore.java.language.Enumeration
import io.lionweb.lioncore.java.language.EnumerationLiteral
import io.lionweb.lioncore.java.language.Language
import io.lionweb.lioncore.java.language.LionCoreBuiltins
import io.lionweb.lioncore.java.language.PrimitiveType
import io.lionweb.lioncore.java.language.Property
import io.lionweb.lioncore.java.language.Reference
import io.lionweb.lioncore.java.model.AnnotationInstance
import io.lionweb.lioncore.java.model.Node
import io.lionweb.lioncore.java.model.ReferenceValue
import io.lionweb.lioncore.java.model.impl.AbstractClassifierInstance
Expand Down Expand Up @@ -243,6 +246,7 @@ class LionWebModelConverter(
val targetID = myIDManager.nodeId(origin.origin as KNode)
setOriginalNode(lwNode, targetID)
}
setPlaceholderNodeType(instance, origin.javaClass.kotlin)
lwNode.addAnnotation(instance)
} else {
throw Exception(
Expand Down Expand Up @@ -357,6 +361,26 @@ class LionWebModelConverter(
)
}

private fun setPlaceholderNodeType(
placeholderAnnotation: AnnotationInstance,
kClass: KClass<out PlaceholderASTTransformation>
) {
val enumerationLiteral: EnumerationLiteral = when (kClass) {
MissingASTTransformation::class -> StarLasuLWLanguage.PlaceholderNodeType.literals.find {
it.name == "MissingASTTransformation"
}!!
FailingASTTransformation::class -> StarLasuLWLanguage.PlaceholderNodeType.literals.find {
it.name == "FailingASTTransformation"
}!!
else -> TODO()
}

placeholderAnnotation.setPropertyValue(
StarLasuLWLanguage.PlaceholderNodeTypeProperty,
EnumerationValueImpl(enumerationLiteral)
)
}

fun importModelFromLionWeb(lwTree: LWNode): Any {
val referencesPostponer = ReferencesPostponer()
lwTree.thisAndAllDescendants().reversed().forEach { lwNode ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import com.strumenta.kolasu.validation.IssueSeverity
import com.strumenta.kolasu.validation.IssueType
import io.lionweb.lioncore.java.language.Annotation
import io.lionweb.lioncore.java.language.Concept
import io.lionweb.lioncore.java.language.Enumeration
import io.lionweb.lioncore.java.language.EnumerationLiteral
import io.lionweb.lioncore.java.language.Interface
import io.lionweb.lioncore.java.language.Language
import io.lionweb.lioncore.java.language.LionCoreBuiltins
Expand Down Expand Up @@ -101,6 +103,29 @@ object StarLasuLWLanguage : Language("com.strumenta.StarLasu") {
keyForContainedElement(PLACEHOLDER_NODE)
)
placeholderNodeAnnotation.annotates = LionCore.getConcept()

val placeholderNodeAnnotationType = Enumeration(
this,
"PlaceholderNodeType"
).apply {
this.id = "${placeholderNodeAnnotation.id!!.removeSuffix("-id")}-$name-id"
this.key = "${placeholderNodeAnnotation.key!!.removeSuffix("-key")}-$name-key"
val enumeration = this
addLiteral(
EnumerationLiteral(this, "MissingASTTransformation").apply {
this.id = "${enumeration.id!!.removeSuffix("-id")}-$name-id"
this.key = "${enumeration.id!!.removeSuffix("-key")}-$name-key"
}
)
addLiteral(
EnumerationLiteral(this, "FailingASTTransformation").apply {
this.id = "${enumeration.id!!.removeSuffix("-id")}-$name-id"
this.key = "${enumeration.id!!.removeSuffix("-key")}-$name-key"
}
)
}
addElement(placeholderNodeAnnotationType)

val reference =
Reference().apply {
this.name = "originalNode"
Expand All @@ -111,6 +136,14 @@ object StarLasuLWLanguage : Language("com.strumenta.StarLasu") {
this.setMultiple(false)
}
placeholderNodeAnnotation.addFeature(reference)
val type = Property().apply {
this.name = "type"
this.id = "${placeholderNodeAnnotation.id!!.removeSuffix("-id")}-$name-id"
this.key = "${placeholderNodeAnnotation.key!!.removeSuffix("-key")}-$name-key"
this.type = placeholderNodeAnnotationType
this.setOptional(false)
}
placeholderNodeAnnotation.addFeature(type)
addElement(placeholderNodeAnnotation)
}

Expand Down Expand Up @@ -141,6 +174,12 @@ object StarLasuLWLanguage : Language("com.strumenta.StarLasu") {
val PlaceholderNodeOriginalNode: Reference
get() = PlaceholderNode.getReferenceByName("originalNode")!!

val PlaceholderNodeTypeProperty: Property
get() = PlaceholderNode.getPropertyByName("type")!!

val PlaceholderNodeType: Enumeration
get() = StarLasuLWLanguage.getEnumerationByName("PlaceholderNodeType")!!

val BehaviorDeclaration: Interface
get() = StarLasuLWLanguage.getInterfaceByName("BehaviorDeclaration")!!
val Documentation: Interface
Expand Down

0 comments on commit 2df2e32

Please sign in to comment.