diff --git a/core/src/main/kotlin/com/strumenta/starlasu/mapping/ParseTreeToASTTransformer.kt b/core/src/main/kotlin/com/strumenta/starlasu/mapping/ParseTreeToASTTransformer.kt index 47b46762..d9ed20d5 100644 --- a/core/src/main/kotlin/com/strumenta/starlasu/mapping/ParseTreeToASTTransformer.kt +++ b/core/src/main/kotlin/com/strumenta/starlasu/mapping/ParseTreeToASTTransformer.kt @@ -3,12 +3,13 @@ package com.strumenta.starlasu.mapping import com.strumenta.starlasu.model.ASTNode import com.strumenta.starlasu.model.Node import com.strumenta.starlasu.model.Origin -import com.strumenta.starlasu.model.Source import com.strumenta.starlasu.parsing.ParseTreeOrigin import com.strumenta.starlasu.parsing.withParseTreeNode import com.strumenta.starlasu.transformation.ASTTransformer -import com.strumenta.starlasu.transformation.NodeFactory -import com.strumenta.starlasu.validation.Issue +import com.strumenta.starlasu.transformation.FailingASTTransformation +import com.strumenta.starlasu.transformation.FaultTolerance +import com.strumenta.starlasu.transformation.TransformationContext +import com.strumenta.starlasu.transformation.TransformationRule import org.antlr.v4.runtime.ParserRuleContext import org.antlr.v4.runtime.tree.ParseTree import kotlin.reflect.KClass @@ -20,11 +21,8 @@ import kotlin.reflect.KClass open class ParseTreeToASTTransformer @JvmOverloads constructor( - issues: MutableList = mutableListOf(), - allowGenericNode: Boolean = true, - val source: Source? = null, - throwOnUnmappedNode: Boolean = true, - ) : ASTTransformer(issues, allowGenericNode, throwOnUnmappedNode) { + faultTolerance: FaultTolerance = FaultTolerance.THROW_ONLY_ON_UNMAPPED, + ) : ASTTransformer(faultTolerance) { /** * Performs the transformation of a node and, recursively, its descendants. In addition to the overridden method, * it also assigns the parseTreeNode to the AST node so that it can keep track of its position. @@ -32,17 +30,32 @@ open class ParseTreeToASTTransformer */ override fun transformIntoNodes( source: Any?, - parent: ASTNode?, + context: TransformationContext, expectedType: KClass, ): List { - val transformed = super.transformIntoNodes(source, parent, expectedType) + if (source is ParserRuleContext && source.exception != null) { + if (faultTolerance == FaultTolerance.STRICT) { + throw RuntimeException("Failed to transform $source into $expectedType", source.exception) + } + val origin = + FailingASTTransformation( + asOrigin(source, context), + "Failed to transform $source into $expectedType because of an error (${source.exception.message})", + ) + val nodes = defaultNodes(source, context, expectedType) + nodes.forEach { node -> + node.origin = origin + } + return nodes + } + val transformed = super.transformIntoNodes(source, context, expectedType) return transformed .map { node -> if (source is ParserRuleContext) { if (node.origin == null) { - node.withParseTreeNode(source, this.source) + node.withParseTreeNode(source, context.source) } else if (node.position != null && node.source == null) { - node.position!!.source = this.source + node.position!!.source = context.source } } return listOf(node) @@ -57,7 +70,15 @@ open class ParseTreeToASTTransformer return if (origin is ParseTreeOrigin) origin.parseTree else source } - override fun asOrigin(source: Any): Origin? = if (source is ParseTree) ParseTreeOrigin(source) else null + override fun asOrigin( + source: Any, + context: TransformationContext, + ): Origin? = + if (source is ParseTree) { + ParseTreeOrigin(source, context.source) + } else { + null + } override fun asString(source: Any): String? = if (source is ParseTree) { @@ -71,19 +92,19 @@ open class ParseTreeToASTTransformer * wrapper. When there is only a ParserRuleContext child we can transform * that child and return that result. */ - fun

registerNodeFactoryUnwrappingChild(kclass: KClass

): NodeFactory = - registerNodeFactory(kclass) { source, transformer, _ -> + fun

registerRuleUnwrappingChild(kclass: KClass

): TransformationRule = + registerRule(kclass) { source, context, _ -> val nodeChildren = source.children.filterIsInstance() require(nodeChildren.size == 1) { "Node $source (${source.javaClass}) has ${nodeChildren.size} " + "node children: $nodeChildren" } - transformer.transform(nodeChildren[0]) as Node + transform(nodeChildren[0], context) as Node } /** * Alternative to registerNodeFactoryUnwrappingChild(KClass) which is slightly more concise. */ - inline fun registerNodeFactoryUnwrappingChild(): NodeFactory = - registerNodeFactoryUnwrappingChild(P::class) + inline fun registerRuleUnwrappingChild(): TransformationRule = + registerRuleUnwrappingChild(P::class) } diff --git a/core/src/main/kotlin/com/strumenta/starlasu/mapping/Support.kt b/core/src/main/kotlin/com/strumenta/starlasu/mapping/Support.kt index 6ef43580..d4916c8d 100644 --- a/core/src/main/kotlin/com/strumenta/starlasu/mapping/Support.kt +++ b/core/src/main/kotlin/com/strumenta/starlasu/mapping/Support.kt @@ -3,6 +3,7 @@ package com.strumenta.starlasu.mapping import com.strumenta.starlasu.model.ASTNode import com.strumenta.starlasu.parsing.getOriginalText import com.strumenta.starlasu.transformation.ASTTransformer +import com.strumenta.starlasu.transformation.TransformationContext import org.antlr.v4.runtime.ParserRuleContext /** @@ -13,8 +14,11 @@ import org.antlr.v4.runtime.ParserRuleContext * JPostIncrementExpr(translateCasted(expression().first())) * ``` */ -inline fun ASTTransformer.translateCasted(original: Any): T { - val result = transform(original, expectedType = T::class) +inline fun ASTTransformer.translateCasted( + original: Any, + context: TransformationContext, +): T { + val result = transform(original, context, expectedType = T::class) if (result is Nothing) { throw IllegalStateException("Transformation produced Nothing") } @@ -30,8 +34,11 @@ inline fun ASTTransformer.translateCasted(original: Any): * JExtendsType(translateCasted(pt.typeType()), translateList(pt.annotation())) * ``` */ -inline fun ASTTransformer.translateList(original: Collection?): MutableList = - original?.map { transformIntoNodes(it, expectedType = T::class) as List }?.flatten()?.toMutableList() +inline fun ASTTransformer.translateList( + original: Collection?, + context: TransformationContext, +): MutableList = + original?.map { transformIntoNodes(it, context, expectedType = T::class) as List }?.flatten()?.toMutableList() ?: mutableListOf() /** @@ -47,9 +54,12 @@ inline fun ASTTransformer.translateList(original: Collecti * ) * ``` */ -inline fun ASTTransformer.translateOptional(original: Any?): T? { +inline fun ASTTransformer.translateOptional( + original: Any?, + context: TransformationContext, +): T? { return original?.let { - val transformed = transform(it, expectedType = T::class) + val transformed = transform(it, context, expectedType = T::class) if (transformed == null) { return null } else { @@ -69,7 +79,10 @@ inline fun ASTTransformer.translateOptional(original: Any? * } * ``` */ -fun ParseTreeToASTTransformer.translateOnlyChild(parent: ParserRuleContext): T = translateCasted(parent.onlyChild) +fun ParseTreeToASTTransformer.translateOnlyChild( + parent: ParserRuleContext, + context: TransformationContext, +): T = translateCasted(parent.onlyChild, context) /** * It returns the only child (of type ParseRuleContext). If there is no children or more than diff --git a/core/src/main/kotlin/com/strumenta/starlasu/parsing/KolasuParser.kt b/core/src/main/kotlin/com/strumenta/starlasu/parsing/KolasuParser.kt index 638e5375..e1c0bf0b 100644 --- a/core/src/main/kotlin/com/strumenta/starlasu/parsing/KolasuParser.kt +++ b/core/src/main/kotlin/com/strumenta/starlasu/parsing/KolasuParser.kt @@ -6,6 +6,8 @@ import com.strumenta.starlasu.model.PropertyDescription import com.strumenta.starlasu.model.Source import com.strumenta.starlasu.model.assignParents import com.strumenta.starlasu.model.processProperties +import com.strumenta.starlasu.transformation.ASTTransformer +import com.strumenta.starlasu.transformation.TransformationContext import com.strumenta.starlasu.traversing.walk import com.strumenta.starlasu.validation.Issue import com.strumenta.starlasu.validation.IssueType @@ -164,14 +166,26 @@ abstract class KolasuParser, source: Source? = null, - ): R? + ): R? { + val transformer = setupASTTransformer() + if (transformer == null) { + throw IllegalStateException("No AST transformer available, and parseTreeToAst not overridden.") + } else { + return transformer.transform(parseTreeRoot, TransformationContext(issues = issues)) as R? + } + } + + protected open fun setupASTTransformer(): ASTTransformer? = null protected open fun attachListeners( parser: P, @@ -312,7 +326,6 @@ abstract class KolasuParser KClass.toInstantiableType(levelOfDummyTree: Int = 0): K } } + this == ASTNode::class -> { + // This happens when the target type is not known, e.g. + // registerTransform(X::class) { TODO("X not yet mapped") } + GenericErrorNode::class as KClass + } + this.isAbstract -> { - throw IllegalStateException("We cannot instantiate an abstract class (but we can handle sealed classes)") + throw IllegalStateException( + "We cannot instantiate the abstract class ${this.qualifiedName} (but we can handle sealed classes)", + ) } this.java.isInterface -> { - throw IllegalStateException("We cannot instantiate an interface") + throw IllegalStateException("We cannot instantiate the interface ${this.qualifiedName}") } else -> { diff --git a/core/src/main/kotlin/com/strumenta/starlasu/transformation/GenericNodes.kt b/core/src/main/kotlin/com/strumenta/starlasu/transformation/GenericNodes.kt deleted file mode 100644 index 3569fc83..00000000 --- a/core/src/main/kotlin/com/strumenta/starlasu/transformation/GenericNodes.kt +++ /dev/null @@ -1,27 +0,0 @@ -package com.strumenta.starlasu.transformation - -import com.strumenta.starlasu.model.ASTNode -import com.strumenta.starlasu.model.Node -import com.strumenta.starlasu.traversing.children - -/** - * A generic AST node. We use it to represent parts of a source tree that we don't know how to translate yet. - */ -@Deprecated("To be removed in Kolasu 2.0") -class GenericNode( - parent: ASTNode? = null, -) : Node() { - init { - this.parent = parent - } -} - -@Deprecated("To be removed in Kolasu 2.0") -fun ASTNode.findGenericNode(): GenericNode? = - if (this is GenericNode) { - this - } else { - this.children.firstNotNullOfOrNull { - it.findGenericNode() - } - } diff --git a/core/src/main/kotlin/com/strumenta/starlasu/transformation/IdentityTransformation.kt b/core/src/main/kotlin/com/strumenta/starlasu/transformation/IdentityTransformation.kt index 27e1b873..50767af5 100644 --- a/core/src/main/kotlin/com/strumenta/starlasu/transformation/IdentityTransformation.kt +++ b/core/src/main/kotlin/com/strumenta/starlasu/transformation/IdentityTransformation.kt @@ -12,12 +12,12 @@ import kotlin.reflect.jvm.javaType val IDENTTITY_TRANSFORMATION: ( source: Any?, - parent: ASTNode?, + context: TransformationContext, expectedType: KClass, astTransformer: ASTTransformer, ) -> List = { source: Any?, - parent: ASTNode?, + context: TransformationContext, expectedType: KClass, astTransformer: ASTTransformer, -> @@ -47,13 +47,14 @@ val IDENTTITY_TRANSFORMATION: ( // mt is ParameterizedType && mt.rawType == List::class.java -> mutableListOf() when { (parameter.type.classifier as KClass<*>).isSubclassOf(ASTNode::class) -> { - params[parameter] = astTransformer.transform(originalValue) + params[parameter] = astTransformer.transform(originalValue, context) } mt is ParameterizedType && mt.rawType == List::class.java && (mt.actualTypeArguments.first() as? Class<*>)?.kotlin?.isSubclassOf(ASTNode::class) == true -> { - params[parameter] = astTransformer.translateList(originalValue as List) + params[parameter] = + astTransformer.translateList(originalValue as List, context) } else -> params[parameter] = originalValue @@ -61,7 +62,7 @@ val IDENTTITY_TRANSFORMATION: ( } val newInstance = primaryConstructor.callBy(params) as ASTNode - newInstance.parent = parent + newInstance.parent = context.parent newInstance.origin = source listOf(newInstance) } diff --git a/core/src/main/kotlin/com/strumenta/starlasu/transformation/Transformation.kt b/core/src/main/kotlin/com/strumenta/starlasu/transformation/Transformation.kt index d0f6013a..932c6e6b 100644 --- a/core/src/main/kotlin/com/strumenta/starlasu/transformation/Transformation.kt +++ b/core/src/main/kotlin/com/strumenta/starlasu/transformation/Transformation.kt @@ -1,17 +1,12 @@ package com.strumenta.starlasu.transformation import com.strumenta.starlasu.model.ASTNode -import com.strumenta.starlasu.model.GenericErrorNode import com.strumenta.starlasu.model.Origin -import com.strumenta.starlasu.model.Position import com.strumenta.starlasu.model.PropertyDescription import com.strumenta.starlasu.model.asContainment import com.strumenta.starlasu.model.children import com.strumenta.starlasu.model.processProperties import com.strumenta.starlasu.model.withOrigin -import com.strumenta.starlasu.transformation.dummyInstance -import com.strumenta.starlasu.validation.Issue -import com.strumenta.starlasu.validation.IssueSeverity import kotlin.reflect.KClass import kotlin.reflect.KMutableProperty1 import kotlin.reflect.KParameter @@ -23,28 +18,63 @@ import kotlin.reflect.full.memberFunctions import kotlin.reflect.full.memberProperties import kotlin.reflect.full.superclasses +class ConfigurationException( + message: String, + cause: Throwable? = null, +) : Exception(message, cause) + +/** + * Policy to adopt for handling child nodes in an AST transformer. + */ +enum class ChildrenPolicy { + /** + * Child nodes of an AST node are set upon creation, as constructor parameters. + */ + SET_AT_CONSTRUCTION, + + /** + * Child nodes of an AST node are set after the creation of the parent node, using a setter method. The parent node + * is either instantiated automatically by the AST transformer, or by user code. + */ + SET_AFTER_CONSTRUCTION, + + /** + * Child nodes of an AST node are not set by the AST transformer. + */ + SKIP, +} + /** - * Factory that, given a tree node, will instantiate the corresponding transformed node. + * Strategy to instantiate the corresponding transformed AST node given the source node. */ -class NodeFactory( - val constructor: (Source, ASTTransformer, NodeFactory) -> List, - var children: MutableMap?> = mutableMapOf(), - var finalizer: (Output) -> Unit = {}, - var skipChildren: Boolean = false, - var childrenSetAtConstruction: Boolean = false, +class TransformationRule( + val constructor: ( + Source, + TransformationContext, + ASTTransformer, + TransformationRule, + ) -> List, + var children: MutableMap?> = mutableMapOf(), + var finalizer: (Output, TransformationContext) -> Unit = { _, _ -> }, + var childrenPolicy: ChildrenPolicy = ChildrenPolicy.SET_AFTER_CONSTRUCTION, + var customConstructor: Boolean = true, ) { companion object { fun single( - singleConstructor: (Source, ASTTransformer, NodeFactory) -> Output?, - children: MutableMap?> = mutableMapOf(), - finalizer: (Output) -> Unit = {}, - skipChildren: Boolean = false, - childrenSetAtConstruction: Boolean = false, - ): NodeFactory = - NodeFactory({ source, at, nf -> - val result = singleConstructor(source, at, nf) + singleConstructor: ( + Source, + TransformationContext, + ASTTransformer, + TransformationRule, + ) -> Output?, + children: MutableMap?> = mutableMapOf(), + finalizer: (Output, TransformationContext) -> Unit = { _, _ -> }, + childrenPolicy: ChildrenPolicy = ChildrenPolicy.SET_AFTER_CONSTRUCTION, + ): TransformationRule = + TransformationRule({ source, ctx, at, nf -> + val result = singleConstructor(source, ctx, at, nf) if (result == null) emptyList() else listOf(result) - }, children, finalizer, skipChildren, childrenSetAtConstruction) + }, children, finalizer, childrenPolicy) } /** @@ -54,7 +84,7 @@ class NodeFactory( * * Example using the scopedToType parameter: * ``` - * on.registerNodeFactory(SASParser.DatasetOptionContext::class) { ctx -> + * on.registerRule(SASParser.DatasetOptionContext::class) { ctx -> * when { * ... * } @@ -73,7 +103,7 @@ class NodeFactory( targetProperty: KMutableProperty1<*, *>, sourceAccessor: Source.() -> Any?, scopedToType: KClass<*>, - ): NodeFactory = + ): TransformationRule = withChild( get = { source -> source.sourceAccessor() }, set = (targetProperty as KMutableProperty1)::set, @@ -99,7 +129,7 @@ class NodeFactory( fun withChild( targetProperty: KMutableProperty1, sourceAccessor: Source.() -> Any?, - ): NodeFactory = + ): TransformationRule = withChild( get = { source -> source.sourceAccessor() }, set = (targetProperty as KMutableProperty1)::set, @@ -116,7 +146,7 @@ class NodeFactory( fun withChild( targetProperty: KProperty1, sourceAccessor: Source.() -> Any?, - ): NodeFactory = + ): TransformationRule = withChild( get = { source -> source.sourceAccessor() }, null, @@ -137,7 +167,7 @@ class NodeFactory( targetProperty: KProperty1, sourceAccessor: Source.() -> Any?, scopedToType: KClass<*>, - ): NodeFactory = + ): TransformationRule = withChild( get = { source -> source.sourceAccessor() }, null, @@ -158,22 +188,34 @@ class NodeFactory( name: String, scopedToType: KClass<*>? = null, childType: KClass = ASTNode::class, - ): NodeFactory { + ): TransformationRule { + if (childrenPolicy == ChildrenPolicy.SKIP) { + throw ConfigurationException("Children are configured to be skipped, calling withChild is illegal") + } val prefix = if (scopedToType != null) scopedToType.qualifiedName + "#" else "" if (set == null) { - // given we have no setter we MUST set the children at construction - childrenSetAtConstruction = true + if (customConstructor) { + throw ConfigurationException("A custom constructor was provided, but child $name has no setter") + } else { + // given we have no setter we MUST set the children at construction + childrenPolicy = ChildrenPolicy.SET_AT_CONSTRUCTION + } } - children[prefix + name] = ChildNodeFactory(prefix + name, get, set, childType) + children[prefix + name] = ChildTransformationRule(prefix + name, get, set, childType) return this } - fun withFinalizer(finalizer: (Output) -> Unit): NodeFactory { + fun withFinalizer(finalizer: (Output, TransformationContext) -> Unit): TransformationRule { this.finalizer = finalizer return this } + fun withFinalizer(finalizer: (Output) -> Unit): TransformationRule { + this.finalizer = { n, _ -> finalizer(n) } + return this + } + /** * Tells the transformer whether this factory already takes care of the node's children and no further computation * is desired on that subtree. E.g., when we're mapping an ANTLR parse tree, and we have a context that is only a @@ -181,7 +223,7 @@ class NodeFactory( * we may configure the transformer as follows: * * ```kotlin - * transformer.registerNodeFactory(XYZContext::class) { ctx -> transformer.transform(ctx.children[0]) } + * transformer.registerRule(XYZContext::class) { ctx -> transformer.transform(ctx.children[0]) } * ``` * * However, if the result of `transformer.transform(ctx.children[0])` is an instance of a ASTNode with a child @@ -190,8 +232,12 @@ class NodeFactory( * be an instance of `XYZContext` that may not have a child with a corresponding name, and the transformation will * fail – or worse, it will map an unrelated node. */ - fun skipChildren(skip: Boolean = true): NodeFactory { - this.skipChildren = skip + fun skipChildren(skip: Boolean = true): TransformationRule { + if (skip) { + childrenPolicy = ChildrenPolicy.SKIP + } else if (childrenPolicy == ChildrenPolicy.SKIP) { + childrenPolicy = ChildrenPolicy.SET_AFTER_CONSTRUCTION + } return this } @@ -234,7 +280,7 @@ class NodeFactory( * * @param type the property type if single, the collection's element type if multiple */ -data class ChildNodeFactory( +data class ChildTransformationRule( val name: String, val get: (Source) -> Any?, val setter: ((Target, Child?) -> Unit)?, @@ -258,7 +304,24 @@ data class ChildNodeFactory( /** * Sentinel value used to represent the information that a given property is not a child node. */ -private val NO_CHILD_NODE = ChildNodeFactory("", { x -> x }, { _, _ -> }, ASTNode::class) +private val NO_CHILD_NODE = ChildTransformationRule("", { x -> x }, { _, _ -> }, ASTNode::class) + +enum class FaultTolerance { + /** + * If a transformation fails, we'll throw an exception. + */ + STRICT, + + /** + * Same as [STRICT] when the failure is due to a node not being mapped; otherwise, same as [LOOSE]. + */ + THROW_ONLY_ON_UNMAPPED, + + /** + * In case a transformation fails, we will add a node with the origin FailingASTTransformation. + */ + LOOSE, +} /** * Implementation of a tree-to-tree transformation. For each source node type, we can register a factory that knows how @@ -270,23 +333,11 @@ private val NO_CHILD_NODE = ChildNodeFactory("", { x -> x }, { _, open class ASTTransformer @JvmOverloads constructor( - /** - * Additional issues found during the transformation process. - */ - val issues: MutableList = mutableListOf(), - @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, + val faultTolerance: FaultTolerance = FaultTolerance.LOOSE, val defaultTransformation: ( ( source: Any?, - parent: ASTNode?, + context: TransformationContext, expectedType: KClass, astTransformer: ASTTransformer, ) -> List @@ -295,7 +346,7 @@ open class ASTTransformer /** * Factories that map from source tree node to target tree node. */ - val factories = mutableMapOf, NodeFactory<*, *>>() + val rules = mutableMapOf, TransformationRule<*, *>>() private val _knownClasses = mutableMapOf>>() val knownClasses: Map>> = _knownClasses @@ -306,10 +357,10 @@ open class ASTTransformer @JvmOverloads fun transform( source: Any?, - parent: ASTNode? = null, + context: TransformationContext = TransformationContext(), expectedType: KClass = ASTNode::class, ): ASTNode? { - val result = transformIntoNodes(source, parent, expectedType) + val result = transformIntoNodes(source, context, expectedType) return when (result.size) { 0 -> null 1 -> { @@ -330,7 +381,7 @@ open class ASTTransformer @JvmOverloads open fun transformIntoNodes( source: Any?, - parent: ASTNode? = null, + context: TransformationContext, expectedType: KClass = ASTNode::class, ): List { if (source == null) { @@ -339,40 +390,49 @@ open class ASTTransformer if (source is Collection<*>) { throw Error("Mapping error: received collection when value was expected") } - val factory = getNodeFactory(source::class as KClass) + val transform = getTransformationRule(source::class as KClass) val nodes: List - if (factory != null) { - nodes = makeNodes(factory, source, allowGenericNode = allowGenericNode) - if (!factory.skipChildren && !factory.childrenSetAtConstruction) { - nodes.forEach { node -> setChildren(factory, source, node) } + if (transform != null) { + nodes = makeNodes(transform, source, context) + val parent = context.parent + if (transform.childrenPolicy == ChildrenPolicy.SET_AFTER_CONSTRUCTION) { + nodes.forEach { node -> + context.parent = node + setChildren(transform, source, context) + } } + context.parent = parent nodes.forEach { node -> - factory.finalizer(node) + transform.finalizer(node, context) node.parent = parent } } else { - if (defaultTransformation != null) { - nodes = defaultTransformation.invoke(source, parent, expectedType, this) - } else if (allowGenericNode) { - val origin = asOrigin(source) - nodes = - listOf( - GenericNode( - parent, - ).withOrigin(origin), - ) - issues.add( - Issue.semantic( - "Source node not mapped: ${source::class.qualifiedName}", - IssueSeverity.WARNING, - origin?.position, - ), + if (defaultTransformation == null && faultTolerance != FaultTolerance.LOOSE) { + throw IllegalStateException( + "Unable to translate node $source (class ${source::class.qualifiedName})", ) - } else if (expectedType.isDirectlyOrIndirectlyInstantiable() && !throwOnUnmappedNode) { + } + nodes = defaultNodes(source, context, expectedType) + nodes.filter { it.origin == null }.forEach { node -> + node.origin = MissingASTTransformation(asOrigin(source, context), source, expectedType) + } + } + return nodes + } + + protected fun defaultNodes( + source: Any, + context: TransformationContext, + expectedType: KClass, + nullable: Boolean = false, + ): List = + defaultTransformation?.invoke(source, context, expectedType, this) + ?: if (nullable) { + listOf() + } else if (expectedType.isDirectlyOrIndirectlyInstantiable()) { try { val node = expectedType.dummyInstance() - node.origin = MissingASTTransformation(asOrigin(source), source, expectedType) - nodes = listOf(node) + listOf(node) } catch (e: Exception) { throw IllegalStateException( "Unable to instantiate desired node type ${expectedType.qualifiedName}", @@ -384,50 +444,52 @@ open class ASTTransformer "Unable to translate node $source (class ${source::class.qualifiedName})", ) } - } - return nodes - } protected open fun setChildren( - factory: NodeFactory, + rule: TransformationRule, source: Any, - node: ASTNode, + context: TransformationContext, ) { + val node = context.parent!! node.processProperties { pd -> - val childNodeFactory = factory.getChildNodeFactory(node, pd.name) - if (childNodeFactory != null) { - if (childNodeFactory != NO_CHILD_NODE) { - setChild(childNodeFactory, source, node, pd) + val childTransform = rule.getChildTransformationRule(node, pd.name) + if (childTransform != null) { + if (childTransform != NO_CHILD_NODE) { + setChild(childTransform, source, context, pd) } } else { - factory.children[getChildKey(node.nodeType, pd.name)] = NO_CHILD_NODE + rule.children[getChildKey(node.nodeType, pd.name)] = NO_CHILD_NODE } } } - open fun asOrigin(source: Any): Origin? = if (source is Origin) source else null + open fun asOrigin( + source: Any, + context: TransformationContext, + ): Origin? = source as? Origin protected open fun setChild( - childNodeFactory: ChildNodeFactory<*, *, *>, + childTransformationRule: ChildTransformationRule<*, *, *>, source: Any, - node: ASTNode, + context: TransformationContext, pd: PropertyDescription, ) { - val childFactory = childNodeFactory as ChildNodeFactory + val node = context.parent!! + val childFactory = childTransformationRule as ChildTransformationRule val childrenSource = childFactory.get(getSource(node, source)) val child: Any? = if (pd.multiple) { (childrenSource as List<*>?) ?.map { - transformIntoNodes(it, node, childFactory.type) + transformIntoNodes(it, context, childFactory.type) }?.flatten() ?: listOf() } else { - transform(childrenSource, node) + transform(childrenSource, context) } try { - childNodeFactory.set(node, child) + childTransformationRule.set(node, child) } catch (e: IllegalArgumentException) { - throw Error("Could not set child $childNodeFactory", e) + throw Error("Could not set child $childTransformationRule", e) } } @@ -437,93 +499,86 @@ open class ASTTransformer ): Any = source protected open fun makeNodes( - factory: NodeFactory, + rule: TransformationRule, source: S, - allowGenericNode: Boolean = true, + context: TransformationContext, ): List { - val nodes = - try { - factory.constructor(source, this, factory) - } catch (e: Exception) { - if (allowGenericNode) { - listOf( - GenericErrorNode( - e, - ), - ) - } else { - throw e - } - } + val nodes = rule.constructor(source, context, this, rule) nodes.forEach { node -> if (node.origin == null) { - node.withOrigin(asOrigin(source)) + node.withOrigin(asOrigin(source, context)) } } return nodes } - protected open fun getNodeFactory(kClass: KClass): NodeFactory? { - val factory = factories[kClass] - if (factory != null) { - return factory as NodeFactory + protected open fun getTransformationRule(kClass: KClass): TransformationRule? { + val rule = rules[kClass] + if (rule != null) { + return rule as TransformationRule } else { if (kClass == Any::class) { return null } for (superclass in kClass.superclasses) { - val nodeFactory = getNodeFactory(superclass as KClass) - if (nodeFactory != null) { - return nodeFactory + val rule = getTransformationRule(superclass as KClass) + if (rule != null) { + return rule } } } return null } - fun registerNodeFactory( + fun registerRule( kclass: KClass, - factory: (S, ASTTransformer, NodeFactory) -> T?, - ): NodeFactory { - val nodeFactory = NodeFactory.single(factory) - factories[kclass] = nodeFactory - return nodeFactory + factory: (S, TransformationContext, ASTTransformer, TransformationRule) -> T?, + ): TransformationRule { + val transformationRule = TransformationRule.single(factory) + rules[kclass] = transformationRule + return transformationRule } - fun registerMultipleNodeFactory( + fun registerMultipleTransform( kclass: KClass, - factory: (S, ASTTransformer, NodeFactory) -> List, - ): NodeFactory { - val nodeFactory = NodeFactory(factory) - factories[kclass] = nodeFactory - return nodeFactory + factory: (S, TransformationContext, ASTTransformer, TransformationRule) -> List, + ): TransformationRule { + val transformationRule = TransformationRule(factory) + rules[kclass] = transformationRule + return transformationRule } - fun registerNodeFactory( + fun registerRule( + kclass: KClass, + factory: (S, TransformationContext, ASTTransformer) -> T?, + ): TransformationRule = + registerRule(kclass) { source, context, transformer, _ -> factory(source, context, transformer) } + + fun registerRule( kclass: KClass, - factory: (S, ASTTransformer) -> T?, - ): NodeFactory = registerNodeFactory(kclass) { source, transformer, _ -> factory(source, transformer) } + factory: (S, TransformationContext) -> T?, + ): TransformationRule = registerRule(kclass) { source, context, _, _ -> factory(source, context) } - inline fun registerNodeFactory( - crossinline factory: S.(ASTTransformer) -> T?, - ): NodeFactory = registerNodeFactory(S::class) { source, transformer, _ -> source.factory(transformer) } + inline fun registerRule( + crossinline factory: S.(TransformationContext) -> T?, + ): TransformationRule = registerRule(S::class) { source, context, _, _ -> source.factory(context) } /** * We need T to be reified because we may need to install dummy classes of T. */ - inline fun registerNodeFactory( + inline fun registerRule( kclass: KClass, crossinline factory: (S) -> T?, - ): NodeFactory = - registerNodeFactory(kclass) { input, _, _ -> + ): TransformationRule = + registerRule(kclass) { input, context -> try { factory(input) } catch (t: NotImplementedError) { - if (faultTollerant) { + if (faultTolerance != FaultTolerance.STRICT) { val node = T::class.dummyInstance() node.origin = FailingASTTransformation( - asOrigin(input), + asOrigin(input, context), "Failed to transform $input into $kclass because the implementation is not complete " + "(${t.message}", ) @@ -532,11 +587,11 @@ open class ASTTransformer throw RuntimeException("Failed to transform $input into $kclass", t) } } catch (e: Exception) { - if (faultTollerant) { + if (faultTolerance != FaultTolerance.STRICT) { val node = T::class.dummyInstance() node.origin = FailingASTTransformation( - asOrigin(input), + asOrigin(input, context), "Failed to transform $input into $kclass because of an error (${e.message})", ) node @@ -546,16 +601,17 @@ open class ASTTransformer } } - fun registerMultipleNodeFactory( + fun registerMultipleTransform( kclass: KClass, factory: (S) -> List, - ): NodeFactory = registerMultipleNodeFactory(kclass) { input, _, _ -> factory(input) } + ): TransformationRule = registerMultipleTransform(kclass) { input, _, _, _ -> factory(input) } - inline fun registerNodeFactory(): NodeFactory = - registerNodeFactory(S::class, T::class) + inline fun registerRule(): TransformationRule = + registerRule(S::class, T::class) + .apply { customConstructor = false } - inline fun notTranslateDirectly(): NodeFactory = - registerNodeFactory { + inline fun notTranslateDirectly(): TransformationRule = + registerRule { throw java.lang.IllegalStateException( "A ASTNode of this type (${this.javaClass.canonicalName}) should never be translated directly. " + "It is expected that the container will not delegate the translation of this node but it " + @@ -566,9 +622,10 @@ open class ASTTransformer private fun parameterValue( kParameter: KParameter, source: S, - childNodeFactory: ChildNodeFactory, + childTransformationRule: ChildTransformationRule, + context: TransformationContext, ): ParameterValue = - when (val childSource = childNodeFactory.get.invoke(source)) { + when (val childSource = childTransformationRule.get.invoke(source)) { null -> { AbsentParameterValue } @@ -576,7 +633,7 @@ open class ASTTransformer is List<*> -> { PresentParameterValue( childSource - .map { transformIntoNodes(it) } + .map { transformIntoNodes(it, context) } .flatten() .toMutableList(), ) @@ -595,9 +652,9 @@ open class ASTTransformer AbsentParameterValue } } else if ((kParameter.type.classifier as? KClass<*>)?.isSubclassOf(Collection::class) == true) { - PresentParameterValue(transformIntoNodes(childSource)) + PresentParameterValue(transformIntoNodes(childSource, context)) } else { - PresentParameterValue(transform(childSource)) + PresentParameterValue(transform(childSource, context)) } } } @@ -612,30 +669,30 @@ open class ASTTransformer * @param nodeType the [ASTNode.nodeType] of the target node. Normally, the node type is the same as the class name, * however, [ASTNode] subclasses may want to override it, and in that case, the parameter must be provided explicitly. */ - fun registerNodeFactory( + fun registerRule( source: KClass, target: KClass, nodeType: String = target.qualifiedName!!, - ): NodeFactory { + ): TransformationRule { registerKnownClass(target) // We are looking for any constructor with does not take parameters or have default // values for all its parameters val emptyLikeConstructor = target.constructors.find { it.parameters.all { param -> param.isOptional } } - val nodeFactory = - NodeFactory.single( - { source: S, _, thisFactory -> + val transformationRule = + TransformationRule.single( + { source: S, context, _, thisTransform -> if (target.isSealed) { throw IllegalStateException("Unable to instantiate sealed class $target") } fun getConstructorParameterValue(kParameter: KParameter): ParameterValue { try { - val childNodeFactory = - thisFactory.getChildNodeFactory( + val childTransform = + thisTransform.getChildTransformationRule( nodeType, kParameter.name!!, ) - if (childNodeFactory == null) { + if (childTransform == null) { if (kParameter.isOptional) { return AbsentParameterValue } @@ -643,7 +700,7 @@ open class ASTTransformer "We do not know how to produce parameter ${kParameter.name!!} for $target", ) } else { - return parameterValue(kParameter, source, childNodeFactory) + return parameterValue(kParameter, source, childTransform, context) } } catch (t: Throwable) { throw RuntimeException( @@ -658,7 +715,7 @@ open class ASTTransformer // so we should really check the value that `childrenSetAtConstruction` time has when we actually invoke // the factory. val instance = - if (thisFactory.childrenSetAtConstruction) { + if (thisTransform.childrenPolicy == ChildrenPolicy.SET_AT_CONSTRUCTION) { val constructor = target.preferredConstructor() val constructorParamValues = constructor.parameters @@ -699,18 +756,24 @@ open class ASTTransformer // If I do not have an emptyLikeConstructor, then I am forced to invoke a constructor with parameters and // therefore setting the children at construction time. // Note that we are assuming that either we set no children at construction time or we set all of them - childrenSetAtConstruction = emptyLikeConstructor == null, + childrenPolicy = + if (emptyLikeConstructor == null) { + ChildrenPolicy.SET_AT_CONSTRUCTION + } else { + ChildrenPolicy.SET_AFTER_CONSTRUCTION + }, ) - factories[source] = nodeFactory - return nodeFactory + transformationRule.customConstructor = false + rules[source] = transformationRule + return transformationRule } /** * Here the method needs to be inlined and the type parameter reified as in the invoked - * registerNodeFactory we need to access the nodeClass + * registerRule we need to access the nodeClass */ inline fun registerIdentityTransformation(nodeClass: KClass) = - registerNodeFactory(nodeClass) { node -> node }.skipChildren() + registerRule(nodeClass) { node -> node }.skipChildren() private fun registerKnownClass(target: KClass<*>) { val qualifiedName = target.qualifiedName @@ -728,33 +791,23 @@ open class ASTTransformer val set = _knownClasses.computeIfAbsent(packageName) { mutableSetOf() } set.add(target) } - - fun addIssue( - message: String, - severity: IssueSeverity = IssueSeverity.ERROR, - position: Position? = null, - ): Issue { - val issue = Issue.semantic(message, severity, position) - issues.add(issue) - return issue - } } -private fun NodeFactory<*, *>.getChildNodeFactory( +private fun TransformationRule<*, *>.getChildTransformationRule( node: Target, parameterName: String, -): ChildNodeFactory? = getChildNodeFactory(node.nodeType, parameterName) +): ChildTransformationRule? = getChildTransformationRule(node.nodeType, parameterName) -private fun NodeFactory<*, *>.getChildNodeFactory( +private fun TransformationRule<*, *>.getChildTransformationRule( nodeType: String, parameterName: String, -): ChildNodeFactory? { +): ChildTransformationRule? { val childKey = getChildKey(nodeType, parameterName) - var childNodeFactory = this.children[childKey] - if (childNodeFactory == null) { - childNodeFactory = this.children[parameterName] + var childRule = this.children[childKey] + if (childRule == null) { + childRule = this.children[parameterName] } - return childNodeFactory as ChildNodeFactory? + return childRule as ChildTransformationRule? } private fun getChildKey( diff --git a/core/src/main/kotlin/com/strumenta/starlasu/transformation/TransformationContext.kt b/core/src/main/kotlin/com/strumenta/starlasu/transformation/TransformationContext.kt new file mode 100644 index 00000000..d5a50315 --- /dev/null +++ b/core/src/main/kotlin/com/strumenta/starlasu/transformation/TransformationContext.kt @@ -0,0 +1,48 @@ +package com.strumenta.starlasu.transformation + +import com.strumenta.starlasu.model.ASTNode +import com.strumenta.starlasu.model.Position +import com.strumenta.starlasu.model.Source +import com.strumenta.starlasu.validation.Issue +import com.strumenta.starlasu.validation.IssueSeverity + +/** + * A context holding metadata relevant to the transformation process, such as parent node reference, associated source, + * and issues discovered during transformation. + * + * This is an open class so that specialized AST transformers can extend it to track additional information. However, + * to keep type signatures reasonably simple, AST transformers are not generic on the context class. This is also + * because we expect that only few, if any, AST transformation rules ([TransformationRule] instances) will require a + * custom context; those few can just cast the context to the desired subclass. + * + * @constructor Creates an instance of the transformation context. + * @param issues A mutable list of issues encountered during the transformation. + * Defaults to an empty list if not provided. + * @param parent The parent [com.strumenta.starlasu.model.ASTNode] in the hierarchy, if available. Defaults to null. + * @param source The [com.strumenta.starlasu.model.Source] object associated with this context, if any. Defaults to null. + */ +open class TransformationContext + @JvmOverloads + constructor( + /** + * Additional issues found during the transformation process. + */ + val issues: MutableList = mutableListOf(), + var parent: ASTNode? = null, + var source: Source? = null, + ) { + fun addIssue( + message: String, + severity: IssueSeverity = IssueSeverity.ERROR, + position: Position? = null, + ): Issue { + val issue = + Issue.semantic( + message, + severity, + position?.apply { source = this@TransformationContext.source }, + ) + issues.add(issue) + return issue + } + } diff --git a/core/src/main/kotlin/com/strumenta/starlasu/transformation/TrivialFactoryOfParseTreeToASTNodeFactory.kt b/core/src/main/kotlin/com/strumenta/starlasu/transformation/TrivialFactoryOfParseTreeToASTTransform.kt similarity index 85% rename from core/src/main/kotlin/com/strumenta/starlasu/transformation/TrivialFactoryOfParseTreeToASTNodeFactory.kt rename to core/src/main/kotlin/com/strumenta/starlasu/transformation/TrivialFactoryOfParseTreeToASTTransform.kt index bcfa0ebb..7c8d4d16 100644 --- a/core/src/main/kotlin/com/strumenta/starlasu/transformation/TrivialFactoryOfParseTreeToASTNodeFactory.kt +++ b/core/src/main/kotlin/com/strumenta/starlasu/transformation/TrivialFactoryOfParseTreeToASTTransform.kt @@ -18,10 +18,9 @@ import kotlin.reflect.full.memberFunctions import kotlin.reflect.full.memberProperties import kotlin.reflect.full.primaryConstructor -object TrivialFactoryOfParseTreeToASTNodeFactory { +object TrivialFactoryOfParseTreeToASTTransform { fun convertString( text: String, - astTransformer: ASTTransformer, expectedType: KType, ): Any? = when (expectedType.classifier) { @@ -45,15 +44,16 @@ object TrivialFactoryOfParseTreeToASTNodeFactory { fun convert( value: Any?, astTransformer: ASTTransformer, + context: TransformationContext, expectedType: KType, ): Any? { when (value) { is Token -> { - return convertString(value.text, astTransformer, expectedType) + return convertString(value.text, expectedType) } is List<*> -> { - return value.map { convert(it, astTransformer, expectedType.arguments[0].type!!) } + return value.map { convert(it, astTransformer, context, expectedType.arguments[0].type!!) } } is ParserRuleContext -> { @@ -63,7 +63,7 @@ object TrivialFactoryOfParseTreeToASTNodeFactory { } else -> { - astTransformer.transform(value) + astTransformer.transform(value, context) } } } @@ -73,20 +73,21 @@ object TrivialFactoryOfParseTreeToASTNodeFactory { } is TerminalNode -> { - return convertString(value.text, astTransformer, expectedType) + return convertString(value.text, expectedType) } else -> TODO("value $value (${value.javaClass})") } } - inline fun trivialFactory( + inline fun trivialTransform( vararg nameConversions: Pair, ): ( S, + TransformationContext, ASTTransformer, ) -> T? = - { parseTreeNode, astTransformer -> + { parseTreeNode, context, astTransformer -> val constructor = T::class.preferredConstructor() val args: Array = constructor.parameters @@ -108,11 +109,11 @@ object TrivialFactoryOfParseTreeToASTNodeFactory { ) } else { val value = method.call(parseTreeNode) - convert(value, astTransformer, it.type) + convert(value, astTransformer, context, it.type) } } else { val value = parseTreeMember.get(parseTreeNode) - convert(value, astTransformer, it.type) + convert(value, astTransformer, context, it.type) } }.toTypedArray() try { @@ -132,9 +133,9 @@ object TrivialFactoryOfParseTreeToASTNodeFactory { inline fun ASTTransformer.registerTrivialPTtoASTConversion( vararg nameConversions: Pair, ) { - this.registerNodeFactory( + this.registerRule( S::class, - TrivialFactoryOfParseTreeToASTNodeFactory.trivialFactory(*nameConversions), + TrivialFactoryOfParseTreeToASTTransform.trivialTransform(*nameConversions), ) } @@ -147,9 +148,9 @@ inline fun ParseTreeToASTTransformer ) inline fun ParseTreeToASTTransformer.unwrap(wrappingMember: KCallable<*>) { - this.registerNodeFactory(S::class) { parseTreeNode, astTransformer -> + this.registerRule(S::class) { parseTreeNode, context -> val wrapped = wrappingMember.call(parseTreeNode) - astTransformer.transform(wrapped) as T? + transform(wrapped, context) as T? } } diff --git a/core/src/test/kotlin/com/strumenta/starlasu/mapping/ParseTreeToASTTransformerTest.kt b/core/src/test/kotlin/com/strumenta/starlasu/mapping/ParseTreeToASTTransformerTest.kt index 78709808..ab280e43 100644 --- a/core/src/test/kotlin/com/strumenta/starlasu/mapping/ParseTreeToASTTransformerTest.kt +++ b/core/src/test/kotlin/com/strumenta/starlasu/mapping/ParseTreeToASTTransformerTest.kt @@ -6,7 +6,6 @@ import com.strumenta.simplelang.AntlrScriptLexer import com.strumenta.simplelang.AntlrScriptParser import com.strumenta.simplelang.SimpleLangLexer import com.strumenta.simplelang.SimpleLangParser -import com.strumenta.starlasu.model.GenericErrorNode import com.strumenta.starlasu.model.Named import com.strumenta.starlasu.model.Node import com.strumenta.starlasu.model.Position @@ -18,12 +17,10 @@ import com.strumenta.starlasu.model.invalidPositions import com.strumenta.starlasu.parsing.withParseTreeNode import com.strumenta.starlasu.testing.assertASTsAreEqual import com.strumenta.starlasu.transformation.ASTTransformer -import com.strumenta.starlasu.transformation.GenericNode -import com.strumenta.starlasu.transformation.TrivialFactoryOfParseTreeToASTNodeFactory +import com.strumenta.starlasu.transformation.TrivialFactoryOfParseTreeToASTTransform import com.strumenta.starlasu.transformation.registerTrivialPTtoASTConversion import com.strumenta.starlasu.transformation.unwrap import com.strumenta.starlasu.traversing.walk -import com.strumenta.starlasu.validation.Issue import org.antlr.v4.runtime.ANTLRErrorListener import org.antlr.v4.runtime.CharStreams import org.antlr.v4.runtime.CommonTokenStream @@ -189,18 +186,8 @@ class ParseTreeToASTTransformerTest { CU( statements = listOf( - GenericErrorNode( - message = - "RuntimeException: Failed to transform [8] into " + - "class com.strumenta.simplelang.SimpleLangParser${'$'}SetStmtContext " + - "-> IllegalStateException: Parse error", - ).withParseTreeNode(pt.statement(0)), - GenericErrorNode( - message = - "RuntimeException: Failed to transform [8] into " + - "class com.strumenta.simplelang.SimpleLangParser${'$'}DisplayStmtContext " + - "-> IllegalStateException: Parse error", - ).withParseTreeNode(pt.statement(1)), + SetStatement(variable = "DUMMY").withParseTreeNode(pt.statement(0)), + DisplayIntStatement(value = 0).withParseTreeNode(pt.statement(1)), ), ).withParseTreeNode(pt) val transformedCU = transformer.transform(pt)!! as CU @@ -209,17 +196,6 @@ class ParseTreeToASTTransformerTest { assertNull(transformedCU.invalidPositions().firstOrNull()) } - @Test - fun testGenericNode() { - val code = "set foo = 123\ndisplay 456" - val lexer = SimpleLangLexer(CharStreams.fromString(code)) - val parser = SimpleLangParser(CommonTokenStream(lexer)) - val pt = parser.compilationUnit() - - val transformer = ParseTreeToASTTransformer() - assertASTsAreEqual(GenericNode(), transformer.transform(pt)!!) - } - @Test fun testGenericASTTransformer() { val code = "set foo = 123\ndisplay 456" @@ -247,9 +223,9 @@ class ParseTreeToASTTransformerTest { private fun configure(transformer: ASTTransformer) { transformer - .registerNodeFactory(SimpleLangParser.CompilationUnitContext::class, CU::class) + .registerRule(SimpleLangParser.CompilationUnitContext::class, CU::class) .withChild(CU::statements, SimpleLangParser.CompilationUnitContext::statement) - transformer.registerNodeFactory(SimpleLangParser.DisplayStmtContext::class) { ctx -> + transformer.registerRule(SimpleLangParser.DisplayStmtContext::class) { ctx -> if (ctx.exception != null || ctx.expression().exception != null) { // We throw a custom error so that we can check that it's recorded in the AST throw IllegalStateException("Parse error") @@ -263,7 +239,7 @@ class ParseTreeToASTTransformerTest { .toInt(), ) } - transformer.registerNodeFactory(SimpleLangParser.SetStmtContext::class) { ctx -> + transformer.registerRule(SimpleLangParser.SetStmtContext::class) { ctx -> if (ctx.exception != null || ctx.expression().exception != null) { // We throw a custom error so that we can check that it's recorded in the AST throw IllegalStateException("Parse error") @@ -351,7 +327,7 @@ class ParseTreeToASTTransformerTest { @Test fun testSimpleEntitiesTransformer() { - val transformer = ParseTreeToASTTransformer(allowGenericNode = false) + val transformer = ParseTreeToASTTransformer() transformer.registerTrivialPTtoASTConversion() transformer.registerTrivialPTtoASTConversion() val expectedAST = @@ -378,7 +354,7 @@ class ParseTreeToASTTransformerTest { @Test fun testEntitiesWithFeaturesTransformer() { - val transformer = ParseTreeToASTTransformer(allowGenericNode = false) + val transformer = ParseTreeToASTTransformer() transformer.registerTrivialPTtoASTConversion() transformer.registerTrivialPTtoASTConversion() transformer.registerTrivialPTtoASTConversion() @@ -428,7 +404,7 @@ class ParseTreeToASTTransformerTest { @Test fun testScriptTransformer() { - val transformer = ParseTreeToASTTransformer(allowGenericNode = false) + val transformer = ParseTreeToASTTransformer() transformer.registerTrivialPTtoASTConversion() transformer.registerTrivialPTtoASTConversion( AntlrScriptParser.Create_statementContext::var_name to SCreateStatement::name, @@ -440,29 +416,31 @@ class ParseTreeToASTTransformerTest { transformer.registerTrivialPTtoASTConversion( AntlrScriptParser.Int_literal_expressionContext::INT_VALUE to SIntegerLiteral::value, ) - transformer.registerNodeFactory(AntlrScriptParser.String_literal_expressionContext::class) { pt, t -> + transformer.registerRule(AntlrScriptParser.String_literal_expressionContext::class) { pt, t -> SStringLiteral(pt.text.removePrefix("'").removeSuffix("'")) } - transformer.registerNodeFactory(AntlrScriptParser.Div_mult_expressionContext::class) { pt, t -> + transformer.registerRule(AntlrScriptParser.Div_mult_expressionContext::class) { pt, c, t -> when (pt.op.text) { "/" -> { - TrivialFactoryOfParseTreeToASTNodeFactory.trivialFactory< + TrivialFactoryOfParseTreeToASTTransform.trivialTransform< AntlrScriptParser .Div_mult_expressionContext, SDivision, >()( pt, + c, t, ) } "*" -> { - TrivialFactoryOfParseTreeToASTNodeFactory.trivialFactory< + TrivialFactoryOfParseTreeToASTTransform.trivialTransform< AntlrScriptParser .Div_mult_expressionContext, SMultiplication, >()( pt, + c, t, ) } @@ -470,26 +448,28 @@ class ParseTreeToASTTransformerTest { else -> TODO() } } - transformer.registerNodeFactory(AntlrScriptParser.Sum_sub_expressionContext::class) { pt, t -> + transformer.registerRule(AntlrScriptParser.Sum_sub_expressionContext::class) { pt, c, t -> when (pt.op.text) { "+" -> { - TrivialFactoryOfParseTreeToASTNodeFactory.trivialFactory< + TrivialFactoryOfParseTreeToASTTransform.trivialTransform< AntlrScriptParser .Sum_sub_expressionContext, SSum, >()( pt, + c, t, ) } "-" -> { - TrivialFactoryOfParseTreeToASTNodeFactory.trivialFactory< + TrivialFactoryOfParseTreeToASTTransform.trivialTransform< AntlrScriptParser .Sum_sub_expressionContext, SSubtraction, >()( pt, + c, t, ) } @@ -577,15 +557,13 @@ class ParseTreeToASTTransformerTest { } } -class EntTransformer( - issues: MutableList = mutableListOf(), -) : ParseTreeToASTTransformer(issues, allowGenericNode = false) { +class EntTransformer : ParseTreeToASTTransformer() { init { - registerNodeFactory(EntCtx::class) { ctx -> Ent(ctx.name) } + registerRule(EntCtx::class) { ctx -> Ent(ctx.name) } .withChild(Ent::features, EntCtx::features) - registerNodeFactory(EntCtxFeature::class) { ctx -> EntFeature(name = ctx.name) } + registerRule(EntCtxFeature::class) { ctx -> EntFeature(name = ctx.name) } .withChild(EntFeature::type, EntCtxFeature::type) - this.registerNodeFactory(EntCtxStringType::class, EntStringType::class) + this.registerRule(EntCtxStringType::class, EntStringType::class) } } diff --git a/core/src/test/kotlin/com/strumenta/starlasu/transformation/ASTTransformerTest.kt b/core/src/test/kotlin/com/strumenta/starlasu/transformation/ASTTransformerTest.kt index 6dbf0ecf..7dfaaa50 100644 --- a/core/src/test/kotlin/com/strumenta/starlasu/transformation/ASTTransformerTest.kt +++ b/core/src/test/kotlin/com/strumenta/starlasu/transformation/ASTTransformerTest.kt @@ -2,6 +2,7 @@ package com.strumenta.starlasu.transformation import com.strumenta.starlasu.mapping.translateCasted import com.strumenta.starlasu.mapping.translateList +import com.strumenta.starlasu.model.BaseASTNode import com.strumenta.starlasu.model.Node import com.strumenta.starlasu.model.children import com.strumenta.starlasu.model.hasValidParents @@ -10,6 +11,7 @@ import com.strumenta.starlasu.testing.assertASTsAreEqual import com.strumenta.starlasu.traversing.walkDescendants import com.strumenta.starlasu.validation.Issue import com.strumenta.starlasu.validation.IssueSeverity +import org.junit.Assert.assertThrows import org.junit.Test import kotlin.test.assertEquals import kotlin.test.assertIs @@ -120,7 +122,7 @@ class ASTTransformerTest { fun testIdentitiyTransformer() { val transformer = ASTTransformer() transformer - .registerNodeFactory(CU::class, CU::class) + .registerRule(CU::class, CU::class) .withChild(CU::statements, CU::statements) transformer.registerIdentityTransformation(DisplayIntStatement::class) transformer.registerIdentityTransformation(SetStatement::class) @@ -136,7 +138,7 @@ class ASTTransformerTest { val transformedCU = transformer.transform(cu)!! assertASTsAreEqual(cu, transformedCU, considerPosition = true) assertTrue { transformedCU.hasValidParents() } - assertEquals(transformedCU.origin, cu) + assertEquals(cu, transformedCU.origin) } /** @@ -145,8 +147,8 @@ class ASTTransformerTest { @Test fun translateBinaryExpression() { val myTransformer = - ASTTransformer(allowGenericNode = false).apply { - registerNodeFactory(GenericBinaryExpression::class) { source: GenericBinaryExpression -> + ASTTransformer().apply { + registerRule(GenericBinaryExpression::class) { source: GenericBinaryExpression -> when (source.operator) { Operator.MULT -> Mult( @@ -179,15 +181,15 @@ class ASTTransformerTest { @Test fun translateAcrossLanguages() { val myTransformer = - ASTTransformer(allowGenericNode = false).apply { - registerNodeFactory(ALangIntLiteral::class) { source: ALangIntLiteral -> BLangIntLiteral(source.value) } - registerNodeFactory(ALangSum::class) { source: ALangSum -> + ASTTransformer().apply { + registerRule(ALangIntLiteral::class) { source: ALangIntLiteral -> BLangIntLiteral(source.value) } + registerRule(ALangSum::class) { source: ALangSum -> BLangSum( transform(source.left) as BLangExpression, transform(source.right) as BLangExpression, ) } - registerNodeFactory(ALangMult::class) { source: ALangMult -> + registerRule(ALangMult::class) { source -> BLangMult( transform(source.left) as BLangExpression, transform(source.right) as BLangExpression, @@ -220,12 +222,12 @@ class ASTTransformerTest { @Test fun computeTypes() { val myTransformer = - ASTTransformer(allowGenericNode = false).apply { - registerIdentityTransformation(TypedSum::class).withFinalizer { + ASTTransformer().apply { + registerIdentityTransformation(TypedSum::class).withFinalizer { it, c -> if (it.left.type == Type.INT && it.right.type == Type.INT) { it.type = Type.INT } else { - addIssue( + c.addIssue( "Illegal types for sum operation. Only integer values are allowed. " + "Found: (${it.left.type?.name ?: "null"}, ${it.right.type?.name ?: "null"})", IssueSeverity.ERROR, @@ -233,11 +235,11 @@ class ASTTransformerTest { ) } } - registerIdentityTransformation(TypedConcat::class).withFinalizer { + registerIdentityTransformation(TypedConcat::class).withFinalizer { it, c -> if (it.left.type == Type.STR && it.right.type == Type.STR) { it.type = Type.STR } else { - addIssue( + c.addIssue( "Illegal types for concat operation. Only string values are allowed. " + "Found: (${it.left.type?.name ?: "null"}, ${it.right.type?.name ?: "null"})", IssueSeverity.ERROR, @@ -248,6 +250,7 @@ class ASTTransformerTest { registerIdentityTransformation(TypedLiteral::class) } // sum - legal + val context = TransformationContext() assertASTsAreEqual( TypedSum( TypedLiteral("1", Type.INT), @@ -259,9 +262,10 @@ class ASTTransformerTest { TypedLiteral("1", Type.INT), TypedLiteral("1", Type.INT), ), + context, )!!, ) - assertEquals(0, myTransformer.issues.size) + assertEquals(0, context.issues.size) // concat - legal assertASTsAreEqual( TypedConcat( @@ -274,9 +278,10 @@ class ASTTransformerTest { TypedLiteral("test", Type.STR), TypedLiteral("test", Type.STR), ), + context, )!!, ) - assertEquals(0, myTransformer.issues.size) + assertEquals(0, context.issues.size) // sum - error assertASTsAreEqual( TypedSum( @@ -289,15 +294,16 @@ class ASTTransformerTest { TypedLiteral("1", Type.INT), TypedLiteral("test", Type.STR), ), + context, )!!, ) - assertEquals(1, myTransformer.issues.size) + assertEquals(1, context.issues.size) assertEquals( Issue.semantic( "Illegal types for sum operation. Only integer values are allowed. Found: (INT, STR)", IssueSeverity.ERROR, ), - myTransformer.issues[0], + context.issues[0], ) // concat - error assertASTsAreEqual( @@ -311,15 +317,16 @@ class ASTTransformerTest { TypedLiteral("1", Type.INT), TypedLiteral("test", Type.STR), ), + context, )!!, ) - assertEquals(2, myTransformer.issues.size) + assertEquals(2, context.issues.size) assertEquals( Issue.semantic( "Illegal types for concat operation. Only string values are allowed. Found: (INT, STR)", IssueSeverity.ERROR, ), - myTransformer.issues[1], + context.issues[1], ) } @@ -327,9 +334,9 @@ class ASTTransformerTest { fun testDroppingNodes() { val transformer = ASTTransformer() transformer - .registerNodeFactory(CU::class, CU::class) + .registerRule(CU::class, CU::class) .withChild(CU::statements, CU::statements) - transformer.registerNodeFactory(DisplayIntStatement::class) { _ -> null } + transformer.registerRule(DisplayIntStatement::class) { _ -> null } transformer.registerIdentityTransformation(SetStatement::class) val cu = @@ -349,11 +356,13 @@ class ASTTransformerTest { @Test fun testNestedOrigin() { + class GenericNode : BaseASTNode() + val transformer = ASTTransformer() transformer - .registerNodeFactory(CU::class, CU::class) + .registerRule(CU::class, CU::class) .withChild(CU::statements, CU::statements) - transformer.registerNodeFactory(DisplayIntStatement::class) { s -> + transformer.registerRule(DisplayIntStatement::class) { s -> s.withOrigin(GenericNode()) } @@ -366,7 +375,7 @@ class ASTTransformerTest { ) val transformedCU = transformer.transform(cu)!! as CU assertTrue { transformedCU.hasValidParents() } - assertEquals(transformedCU.origin, cu) + assertEquals(cu, transformedCU.origin) assertIs(transformedCU.statements[0].origin) } @@ -374,9 +383,9 @@ class ASTTransformerTest { fun testTransformingOneNodeToMany() { val transformer = ASTTransformer() transformer - .registerNodeFactory(BarRoot::class, BazRoot::class) + .registerRule(BarRoot::class, BazRoot::class) .withChild(BazRoot::stmts, BarRoot::stmts) - transformer.registerMultipleNodeFactory(BarStmt::class) { s -> + transformer.registerMultipleTransform(BarStmt::class) { s -> listOf(BazStmt("${s.desc}-1"), BazStmt("${s.desc}-2")) } @@ -406,9 +415,9 @@ class ASTTransformerTest { @Test fun testUnmappedNode() { - val transformer1 = ASTTransformer(allowGenericNode = false) + val transformer1 = ASTTransformer() transformer1 - .registerNodeFactory(BarRoot::class, BazRoot::class) + .registerRule(BarRoot::class, BazRoot::class) .withChild(BazRoot::stmts) { stmts } val original = BarRoot( @@ -453,10 +462,10 @@ class ASTTransformerTest { @Test fun testPartialIdentityTransformation() { val transformer1 = ASTTransformer(defaultTransformation = IDENTTITY_TRANSFORMATION) - transformer1.registerNodeFactory(BarRoot::class) { original: BarRoot, astTransformer: ASTTransformer, _ -> + transformer1.registerRule(BarRoot::class) { original: BarRoot, c -> FooRoot( desc = "#children = ${original.children.size}", - stmts = astTransformer.translateList(original.stmts), + stmts = transformer1.translateList(original.stmts, c), ) } val original = @@ -483,10 +492,10 @@ class ASTTransformerTest { @Test fun testIdentityTransformationOfIntermediateNodes() { val transformer1 = ASTTransformer(defaultTransformation = IDENTTITY_TRANSFORMATION) - transformer1.registerNodeFactory(BarRoot::class) { original: BarRoot, astTransformer: ASTTransformer, _ -> + transformer1.registerRule(BarRoot::class) { original: BarRoot, c -> FooRoot( desc = "#children = ${original.children.size}", - stmts = astTransformer.translateList(original.stmts), + stmts = transformer1.translateList(original.stmts, c), ) } val original = @@ -529,8 +538,8 @@ class ASTTransformerTest { transformer1.transform(original) as AA, ) // All identity besides AA - transformer1.registerNodeFactory(AA::class) { original, t, _ -> - BA("your_" + original.a.removePrefix("my_"), t.translateCasted(original.child)) + transformer1.registerRule(AA::class) { original, c -> + BA("your_" + original.a.removePrefix("my_"), transformer1.translateCasted(original.child, c)) } assertASTsAreEqual( BA( @@ -553,8 +562,8 @@ class ASTTransformerTest { transformer1.transform(original) as AA, ) // All identity besides AA and AB - transformer1.registerNodeFactory(AB::class) { original, t, _ -> - BB("your_" + original.b.removePrefix("my_"), t.translateCasted(original.child)) + transformer1.registerRule(AB::class) { original, c, _ -> + BB("your_" + original.b.removePrefix("my_"), transformer1.translateCasted(original.child, c)) } assertASTsAreEqual( BA( @@ -577,7 +586,7 @@ class ASTTransformerTest { transformer1.transform(original) as AA, ) // All identity besides AA and AB and AD - transformer1.registerNodeFactory(AD::class) { original, t, _ -> + transformer1.registerRule(AD::class) { original -> BD("your_" + original.d.removePrefix("my_")) } assertASTsAreEqual( @@ -605,8 +614,8 @@ class ASTTransformerTest { @Test fun testIdentityTransformationOfIntermediateNodesWithOrigin() { val transformer1 = ASTTransformer(defaultTransformation = IDENTTITY_TRANSFORMATION) - transformer1.registerNodeFactory(AA::class) { original, t, _ -> - BA("your_" + original.a.removePrefix("my_"), t.translateCasted(original.child)) + transformer1.registerRule(AA::class) { original, c -> + BA("your_" + original.a.removePrefix("my_"), transformer1.translateCasted(original.child, c)) } val original = AA( @@ -638,6 +647,59 @@ class ASTTransformerTest { original.walkDescendants(AC::class).first(), ) } + + @Test + fun `exception handling, root`() { + val transformer = ASTTransformer() + transformer.registerRule(AA::class) { aa -> + // if (false) needed to detect the target type as AA + if (false) aa else throw Exception("Something went wrong") + } + val original = AA(a = "my_a", child = AB(b = "my_b", child = AC(c = "my_c", children = mutableListOf()))) + val transformedAST = transformer.transform(original)!! + assertIs(transformedAST) + // verify that the origin is set correctly + assertIs(transformedAST.origin) + assertEquals(original, (transformedAST.origin as FailingASTTransformation).origin) + assertEquals( + "Failed to transform com.strumenta.starlasu.transformation.AA(a=my_a, " + + "child=com.strumenta.starlasu.transformation.AB(...)) into class " + + "com.strumenta.starlasu.transformation.AA because of an error (Something went wrong)", + (transformedAST.origin as FailingASTTransformation).message, + ) + } + + @Test + fun `exception handling, internal node`() { + val transformer = ASTTransformer(defaultTransformation = IDENTTITY_TRANSFORMATION) + transformer.registerRule(AB::class) { ab -> + // if (false) needed to detect the target type as AA + if (false) ab else throw Exception("Something went wrong") + } + val original = AA(a = "my_a", child = AB(b = "my_b", child = AC(c = "my_c", children = mutableListOf()))) + val transformedAST = transformer.transform(original)!! + assertIs(transformedAST) + // verify that the origin is set correctly + assertIs(transformedAST.origin) + assertIs(transformedAST.child.origin) + assertEquals(original.child, (transformedAST.child.origin as FailingASTTransformation).origin) + assertEquals( + "Failed to transform com.strumenta.starlasu.transformation.AB(b=my_b, " + + "child=com.strumenta.starlasu.transformation.AC(...)) into class " + + "com.strumenta.starlasu.transformation.AB because of an error (Something went wrong)", + (transformedAST.child.origin as FailingASTTransformation).message, + ) + } + + @Test + fun `children set at construction with lambda`() { + val transformer = ASTTransformer() + assertThrows(ConfigurationException::class.java) { + transformer + .registerRule(NoSetter::class) { _ -> NoSetter() } + .withChild(NoSetter::child, NoSetter::child) + } + } } data class BazRoot( @@ -693,3 +755,7 @@ class BB( class BD( d: String, ) : AD(d) + +class NoSetter( + val child: NoSetter? = null, +) : BaseASTNode() diff --git a/javalib/src/main/java/com/strumenta/starlasu/javalib/ASTTransformer.java b/javalib/src/main/java/com/strumenta/starlasu/javalib/ASTTransformer.java index 1429b1eb..01c4e586 100644 --- a/javalib/src/main/java/com/strumenta/starlasu/javalib/ASTTransformer.java +++ b/javalib/src/main/java/com/strumenta/starlasu/javalib/ASTTransformer.java @@ -1,12 +1,13 @@ package com.strumenta.starlasu.javalib; import com.strumenta.starlasu.model.ASTNode; -import com.strumenta.starlasu.model.BaseASTNode; -import com.strumenta.starlasu.transformation.NodeFactory; -import com.strumenta.starlasu.validation.Issue; +import com.strumenta.starlasu.transformation.FaultTolerance; +import com.strumenta.starlasu.transformation.TransformationRule; +import com.strumenta.starlasu.transformation.TransformationContext; import kotlin.Unit; import kotlin.jvm.functions.Function1; import kotlin.jvm.functions.Function2; +import kotlin.jvm.functions.Function3; import kotlin.jvm.functions.Function4; import kotlin.reflect.KClass; import org.jetbrains.annotations.NotNull; @@ -18,42 +19,37 @@ import static kotlin.jvm.JvmClassMappingKt.getKotlinClass; public class ASTTransformer extends com.strumenta.starlasu.transformation.ASTTransformer { - public ASTTransformer(@NotNull List issues) { - super(issues); + public ASTTransformer() { + super(); } - public ASTTransformer(@NotNull List issues, boolean allowGenericNode) { - super(issues, allowGenericNode); + public ASTTransformer(FaultTolerance faultTolerance) { + super(faultTolerance); } - public ASTTransformer(@NotNull List issues, boolean allowGenericNode, boolean throwOnUnmappedNode) { - super(issues, allowGenericNode, throwOnUnmappedNode); + public ASTTransformer(FaultTolerance faultTolerance, @Nullable Function4, ? super com.strumenta.starlasu.transformation.ASTTransformer, ? extends List> defaultTransformation) { + super(faultTolerance, defaultTransformation); } - public ASTTransformer(@NotNull List issues, boolean allowGenericNode, boolean throwOnUnmappedNode, boolean faultTollerant) { - super(issues, allowGenericNode, throwOnUnmappedNode, faultTollerant); - } - - public ASTTransformer(@NotNull List issues, boolean allowGenericNode, boolean throwOnUnmappedNode, boolean faultTollerant, @Nullable Function4, ? super com.strumenta.starlasu.transformation.ASTTransformer, ? extends List> defaultTransformation) { - super(issues, allowGenericNode, throwOnUnmappedNode, faultTollerant, defaultTransformation); - } - - protected @NotNull NodeFactory registerNodeFactory(Class source, Class target) { + protected @NotNull TransformationRule registerNodeFactory(Class source, Class target) { return registerNodeFactory(source, target, target.getName()); } - protected @NotNull NodeFactory registerNodeFactory( + protected @NotNull TransformationRule registerNodeFactory( Class source, Class target, String nodeType ) { - return registerNodeFactory(getKotlinClass(source), getKotlinClass(target), nodeType); + return registerRule(getKotlinClass(source), getKotlinClass(target), nodeType); } - protected NodeFactory registerNodeFactory(Class source, Function1 function) { - return registerNodeFactory(getKotlinClass(source), (s, t) -> function.invoke(s)); + protected TransformationRule registerNodeFactory(Class source, Function1 function) { + return registerRule(getKotlinClass(source), (s, t) -> function.invoke(s)); } - protected NodeFactory registerNodeFactory(Class source, Function2 function) { - return registerNodeFactory(getKotlinClass(source), function); + protected TransformationRule registerNodeFactory( + Class source, + Function3 function + ) { + return registerRule(getKotlinClass(source), function); } /** diff --git a/javalib/src/main/java/com/strumenta/starlasu/javalib/ParseTreeToASTTransformer.java b/javalib/src/main/java/com/strumenta/starlasu/javalib/ParseTreeToASTTransformer.java index 0e49f02b..bc0feefe 100644 --- a/javalib/src/main/java/com/strumenta/starlasu/javalib/ParseTreeToASTTransformer.java +++ b/javalib/src/main/java/com/strumenta/starlasu/javalib/ParseTreeToASTTransformer.java @@ -1,53 +1,44 @@ package com.strumenta.starlasu.javalib; import com.strumenta.starlasu.model.ASTNode; -import com.strumenta.starlasu.model.BaseASTNode; -import com.strumenta.starlasu.model.Source; import com.strumenta.starlasu.transformation.ASTTransformer; -import com.strumenta.starlasu.transformation.NodeFactory; -import com.strumenta.starlasu.validation.Issue; +import com.strumenta.starlasu.transformation.FaultTolerance; +import com.strumenta.starlasu.transformation.TransformationRule; +import com.strumenta.starlasu.transformation.TransformationContext; import kotlin.jvm.functions.Function1; -import kotlin.jvm.functions.Function2; +import kotlin.jvm.functions.Function3; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.List; import static kotlin.jvm.JvmClassMappingKt.getKotlinClass; public class ParseTreeToASTTransformer extends com.strumenta.starlasu.mapping.ParseTreeToASTTransformer { - public ParseTreeToASTTransformer(@NotNull List issues, boolean allowGenericNode, @Nullable Source source, boolean throwOnUnmappedNode) { - super(issues, allowGenericNode, source, throwOnUnmappedNode); - } - - public ParseTreeToASTTransformer(@NotNull List issues, boolean allowGenericNode, @Nullable Source source) { - super(issues, allowGenericNode, source); + public ParseTreeToASTTransformer() { + super(); } - public ParseTreeToASTTransformer(@NotNull List issues, boolean allowGenericNode) { - super(issues, allowGenericNode); + public ParseTreeToASTTransformer(FaultTolerance faultTolerance) { + super(faultTolerance); } - - public ParseTreeToASTTransformer(@NotNull List issues) { - super(issues); - } - - protected @NotNull NodeFactory registerNodeFactory(Class source, Class target) { + + protected @NotNull TransformationRule registerNodeFactory(Class source, Class target) { return registerNodeFactory(source, target, target.getName()); } - protected @NotNull NodeFactory registerNodeFactory( + protected @NotNull TransformationRule registerNodeFactory( Class source, Class target, String nodeType ) { - return registerNodeFactory(getKotlinClass(source), getKotlinClass(target), nodeType); + return registerRule(getKotlinClass(source), getKotlinClass(target), nodeType); } - protected NodeFactory registerNodeFactory(Class source, Function1 function) { - return registerNodeFactory(getKotlinClass(source), (s, t) -> function.invoke(s)); + protected TransformationRule registerNodeFactory(Class source, Function1 function) { + return registerRule(getKotlinClass(source), (s, t) -> function.invoke(s)); } - protected NodeFactory registerNodeFactory(Class source, Function2 function) { - return registerNodeFactory(getKotlinClass(source), function); + protected TransformationRule registerNodeFactory( + Class source, + Function3 function + ) { + return registerRule(getKotlinClass(source), function); } } diff --git a/javalib/src/test/java/com/strumenta/starlasu/javalib/TransformerTest.java b/javalib/src/test/java/com/strumenta/starlasu/javalib/TransformerTest.java index 79528ce1..14657d1d 100644 --- a/javalib/src/test/java/com/strumenta/starlasu/javalib/TransformerTest.java +++ b/javalib/src/test/java/com/strumenta/starlasu/javalib/TransformerTest.java @@ -1,6 +1,8 @@ package com.strumenta.starlasu.javalib; import com.strumenta.starlasu.model.*; +import com.strumenta.starlasu.transformation.FaultTolerance; +import com.strumenta.starlasu.transformation.TransformationContext; import com.strumenta.starlasu.validation.Issue; import org.junit.Test; @@ -14,13 +16,13 @@ public class TransformerTest { @Test public void testJavaNodes() { ArrayList issues = new ArrayList<>(); - ASTTransformer t = new ASTTransformer(issues, false); + ASTTransformer t = new ASTTransformer( FaultTolerance.STRICT); t.registerNodeFactory(Node1.class, Node1.class) .withChild(Node1::getNode2, setter(Node1::setNode2), "node2"); t.registerNodeFactory(Node2.class, Node2.class); Node1 node1 = new Node1(); node1.setNode2(new Node2()); - ASTNode transformed = t.transform(node1); + ASTNode transformed = t.transform(node1, new TransformationContext(issues)); assertTrue(issues.toString(), issues.isEmpty()); assertASTsAreEqual(node1, transformed); }