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 92dbbce9..35649f64 100644 --- a/lionweb/src/main/kotlin/com/strumenta/kolasu/lionweb/LionWebModelConverter.kt +++ b/lionweb/src/main/kotlin/com/strumenta/kolasu/lionweb/LionWebModelConverter.kt @@ -247,6 +247,10 @@ class LionWebModelConverter( setOriginalNode(lwNode, targetID) } setPlaceholderNodeType(instance, origin.javaClass.kotlin) + instance.setPropertyValue( + StarLasuLWLanguage.PlaceholderNodeMessageProperty, + origin.message + ) lwNode.addAnnotation(instance) } else { throw Exception( @@ -399,7 +403,7 @@ class LionWebModelConverter( throw RuntimeException("Issue instantiating $kClass from LionWeb node $lwNode", e) } } - val placeholderNodes = mutableListOf() + val placeholderNodes = mutableMapOf Unit>() lwTree.thisAndAllDescendants().forEach { lwNode -> val kNode = nodesMapping.byB(lwNode)!! if (kNode is KNode) { @@ -419,7 +423,34 @@ class LionWebModelConverter( it.classifier == StarLasuLWLanguage.PlaceholderNode } if (placeholderNodeAnnotation != null) { - placeholderNodes.add(kNode) + val placeholderType = ( + placeholderNodeAnnotation.getPropertyValue( + StarLasuLWLanguage.PlaceholderNodeTypeProperty + ) as EnumerationValue + ).enumerationLiteral + val placeholderMessage = placeholderNodeAnnotation.getPropertyValue( + StarLasuLWLanguage.PlaceholderNodeMessageProperty + ) as String + when (placeholderType.name) { + "MissingASTTransformation" -> { + placeholderNodes[kNode] = { kNode -> + kNode.origin = MissingASTTransformation( + origin = kNode.origin, + transformationSource = kNode.origin as? KNode, + expectedType = null + ) + } + } + "FailingASTTransformation" -> { + placeholderNodes[kNode] = { kNode -> + kNode.origin = FailingASTTransformation( + origin = kNode.origin, + message = placeholderMessage + ) + } + } + else -> TODO() + } } val transpiledNodes = lwNode.getReferenceValues(StarLasuLWLanguage.ASTNodeTranspiledNodes) if (transpiledNodes.isNotEmpty()) { @@ -429,12 +460,10 @@ class LionWebModelConverter( } } referencesPostponer.populateReferences(nodesMapping, externalNodeResolver) - placeholderNodes.forEach { - it.origin = MissingASTTransformation( - origin = it.origin, - transformationSource = it.origin as com.strumenta.kolasu.model.Node, - expectedType = null - ) + // We want to handle the origin for placeholder nodes AFTER references, to override the origins + // set during the population of references + placeholderNodes.entries.forEach { entry -> + entry.value.invoke(entry.key) } return nodesMapping.byB(lwTree)!! } diff --git a/lionweb/src/main/kotlin/com/strumenta/kolasu/lionweb/StarLasuLWLanguage.kt b/lionweb/src/main/kotlin/com/strumenta/kolasu/lionweb/StarLasuLWLanguage.kt index 30dbfcd5..1bf09be4 100644 --- a/lionweb/src/main/kotlin/com/strumenta/kolasu/lionweb/StarLasuLWLanguage.kt +++ b/lionweb/src/main/kotlin/com/strumenta/kolasu/lionweb/StarLasuLWLanguage.kt @@ -144,6 +144,14 @@ object StarLasuLWLanguage : Language("com.strumenta.StarLasu") { this.setOptional(false) } placeholderNodeAnnotation.addFeature(type) + val message = Property().apply { + this.name = "message" + this.id = "${placeholderNodeAnnotation.id!!.removeSuffix("-id")}-$name-id" + this.key = "${placeholderNodeAnnotation.key!!.removeSuffix("-key")}-$name-key" + this.type = LionCoreBuiltins.getString() + this.setOptional(false) + } + placeholderNodeAnnotation.addFeature(message) addElement(placeholderNodeAnnotation) } @@ -177,6 +185,9 @@ object StarLasuLWLanguage : Language("com.strumenta.StarLasu") { val PlaceholderNodeTypeProperty: Property get() = PlaceholderNode.getPropertyByName("type")!! + val PlaceholderNodeMessageProperty: Property + get() = PlaceholderNode.getPropertyByName("message")!! + val PlaceholderNodeType: Enumeration get() = StarLasuLWLanguage.getEnumerationByName("PlaceholderNodeType")!! diff --git a/lionweb/src/test/kotlin/com/strumenta/kolasu/lionweb/LionWebModelConverterTest.kt b/lionweb/src/test/kotlin/com/strumenta/kolasu/lionweb/LionWebModelConverterTest.kt index 1633b4cb..addb6250 100644 --- a/lionweb/src/test/kotlin/com/strumenta/kolasu/lionweb/LionWebModelConverterTest.kt +++ b/lionweb/src/test/kotlin/com/strumenta/kolasu/lionweb/LionWebModelConverterTest.kt @@ -17,7 +17,9 @@ import com.strumenta.kolasu.parsing.KolasuToken import com.strumenta.kolasu.parsing.ParsingResult import com.strumenta.kolasu.parsing.TokenCategory import com.strumenta.kolasu.testing.assertASTsAreEqual +import com.strumenta.kolasu.transformation.FailingASTTransformation import com.strumenta.kolasu.transformation.MissingASTTransformation +import com.strumenta.kolasu.transformation.dummyInstance import com.strumenta.kolasu.validation.Issue import com.strumenta.kolasu.validation.IssueSeverity import com.strumenta.kolasu.validation.IssueType @@ -974,6 +976,51 @@ class LionWebModelConverterTest { reimported.tokens.map { it.position } ) } + + @Test + fun serializeAndDeserializeNodeWithMissingTransformation() { + val myOriginalSource = LionWebSource("Some_dummy_original_source") + val myMigratedSource = LionWebSource("Some_dummy_migrated_source") + val myOriginalNode = NodeWithEnum::class.dummyInstance() + val myTransformedNode = NodeWithEnum::class.dummyInstance() + myOriginalNode.source = myOriginalSource + myTransformedNode.source = myMigratedSource + myTransformedNode.origin = MissingASTTransformation(myOriginalNode) + + val converter = LionWebModelConverter() + converter.exportLanguageToLionWeb( + KolasuLanguage("myLanguage").apply { + addClass(NodeWithEnum::class) + } + ) + converter.exportModelToLionWeb(myOriginalNode) + val lwNode = converter.exportModelToLionWeb(myTransformedNode) + val reimportedNode = converter.importModelFromLionWeb(lwNode) as Node + assert(reimportedNode.origin is MissingASTTransformation) + } + + @Test + fun serializeAndDeserializeNodeWithFailingTransformation() { + val myOriginalSource = LionWebSource("Some_dummy_original_source") + val myMigratedSource = LionWebSource("Some_dummy_migrated_source") + val myOriginalNode = NodeWithEnum::class.dummyInstance() + val myTransformedNode = NodeWithEnum::class.dummyInstance() + myOriginalNode.source = myOriginalSource + myTransformedNode.source = myMigratedSource + myTransformedNode.origin = FailingASTTransformation(myOriginalNode, "failing because...") + + val converter = LionWebModelConverter() + converter.exportLanguageToLionWeb( + KolasuLanguage("myLanguage").apply { + addClass(NodeWithEnum::class) + } + ) + converter.exportModelToLionWeb(myOriginalNode) + val lwNode = converter.exportModelToLionWeb(myTransformedNode) + val reimportedNode = converter.importModelFromLionWeb(lwNode) as Node + assert(reimportedNode.origin is FailingASTTransformation) + assertEquals("failing because...", (reimportedNode.origin as FailingASTTransformation).message) + } } @ASTRoot(canBeNotRoot = true)