Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -20,29 +21,41 @@ import kotlin.reflect.KClass
open class ParseTreeToASTTransformer
@JvmOverloads
constructor(
issues: MutableList<Issue> = mutableListOf(),
allowGenericNode: Boolean = true,
val source: Source? = null,
throwOnUnmappedNode: Boolean = true,
) : ASTTransformer(issues, allowGenericNode, throwOnUnmappedNode) {
faultTolerance: FaultTolerance = FaultTolerance.THROW_ONLY_ON_UNMAPPED,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!

) : 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.
* However, a node factory can override the parseTreeNode of the nodes it creates (but not the parent).
*/
override fun transformIntoNodes(
source: Any?,
parent: ASTNode?,
context: TransformationContext,
expectedType: KClass<out ASTNode>,
): List<ASTNode> {
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)
Expand All @@ -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) {
Expand All @@ -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 <P : ParserRuleContext> registerNodeFactoryUnwrappingChild(kclass: KClass<P>): NodeFactory<P, Node> =
registerNodeFactory(kclass) { source, transformer, _ ->
fun <P : ParserRuleContext> registerRuleUnwrappingChild(kclass: KClass<P>): TransformationRule<P, Node> =
registerRule(kclass) { source, context, _ ->
val nodeChildren = source.children.filterIsInstance<ParserRuleContext>()
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 <reified P : ParserRuleContext> registerNodeFactoryUnwrappingChild(): NodeFactory<P, Node> =
registerNodeFactoryUnwrappingChild(P::class)
inline fun <reified P : ParserRuleContext> registerRuleUnwrappingChild(): TransformationRule<P, Node> =
registerRuleUnwrappingChild(P::class)
}
27 changes: 20 additions & 7 deletions core/src/main/kotlin/com/strumenta/starlasu/mapping/Support.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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

/**
Expand All @@ -13,8 +14,11 @@ import org.antlr.v4.runtime.ParserRuleContext
* JPostIncrementExpr(translateCasted<JExpression>(expression().first()))
* ```
*/
inline fun <reified T : ASTNode> ASTTransformer.translateCasted(original: Any): T {
val result = transform(original, expectedType = T::class)
inline fun <reified T : ASTNode> ASTTransformer.translateCasted(
original: Any,
context: TransformationContext,
): T {
val result = transform(original, context, expectedType = T::class)
if (result is Nothing) {
throw IllegalStateException("Transformation produced Nothing")
}
Expand All @@ -30,8 +34,11 @@ inline fun <reified T : ASTNode> ASTTransformer.translateCasted(original: Any):
* JExtendsType(translateCasted(pt.typeType()), translateList(pt.annotation()))
* ```
*/
inline fun <reified T : ASTNode> ASTTransformer.translateList(original: Collection<out Any>?): MutableList<T> =
original?.map { transformIntoNodes(it, expectedType = T::class) as List<T> }?.flatten()?.toMutableList()
inline fun <reified T : ASTNode> ASTTransformer.translateList(
original: Collection<out Any>?,
context: TransformationContext,
): MutableList<T> =
original?.map { transformIntoNodes(it, context, expectedType = T::class) as List<T> }?.flatten()?.toMutableList()
?: mutableListOf()

/**
Expand All @@ -47,9 +54,12 @@ inline fun <reified T : ASTNode> ASTTransformer.translateList(original: Collecti
* )
* ```
*/
inline fun <reified T : ASTNode> ASTTransformer.translateOptional(original: Any?): T? {
inline fun <reified T : ASTNode> 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 {
Expand All @@ -69,7 +79,10 @@ inline fun <reified T : ASTNode> ASTTransformer.translateOptional(original: Any?
* }
* ```
*/
fun <T> ParseTreeToASTTransformer.translateOnlyChild(parent: ParserRuleContext): T = translateCasted(parent.onlyChild)
fun <T> 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -164,14 +166,26 @@ abstract class KolasuParser<R : Node, P : Parser, C : ParserRuleContext, T : Kol
}

/**
* Transforms a parse tree into an AST (second parsing stage).
* Transforms a parse tree into an AST (second parsing stage). By default, it uses the AST transformer obtained by
* calling [setupASTTransformer]. However, if you want to use a different transformation strategy, you can override
* this method. In that case, remember to ensure that the resulting AST has parent nodes properly set, or call
* [assignParents] on it.
*/
protected abstract fun parseTreeToAst(
protected open fun parseTreeToAst(
parseTreeRoot: C,
considerPosition: Boolean = true,
issues: MutableList<Issue>,
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,
Expand Down Expand Up @@ -312,7 +326,6 @@ abstract class KolasuParser<R : Node, P : Parser, C : ParserRuleContext, T : Kol
val firstStage = parseFirstStage(inputStream, measureLexingTime)
val myIssues = firstStage.issues.toMutableList()
var ast = parseTreeToAst(firstStage.root!!, considerPosition, myIssues, source)
assignParents(ast)
ast = if (ast == null) null else postProcessAst(ast, myIssues)
if (ast != null && !considerPosition) {
// Remove parseTreeNodes because they cause the position to be computed
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.strumenta.starlasu.transformation

import com.strumenta.starlasu.model.ASTNode
import com.strumenta.starlasu.model.GenericErrorNode
import com.strumenta.starlasu.model.Node
import com.strumenta.starlasu.model.PossiblyNamed
import com.strumenta.starlasu.model.ReferenceByName
Expand All @@ -15,7 +16,7 @@ import kotlin.reflect.full.primaryConstructor
import kotlin.reflect.jvm.javaType

/**
* This logic instantiate a node of the given class with dummy values.
* This logic instantiates 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.
Expand Down Expand Up @@ -87,12 +88,20 @@ private fun <T : Any> KClass<T>.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<out T>
}

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 -> {
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ import kotlin.reflect.jvm.javaType

val IDENTTITY_TRANSFORMATION: (
source: Any?,
parent: ASTNode?,
context: TransformationContext,
expectedType: KClass<out ASTNode>,
astTransformer: ASTTransformer,
) -> List<ASTNode> = {
source: Any?,
parent: ASTNode?,
context: TransformationContext,
expectedType: KClass<out ASTNode>,
astTransformer: ASTTransformer,
->
Expand Down Expand Up @@ -47,21 +47,22 @@ val IDENTTITY_TRANSFORMATION: (
// mt is ParameterizedType && mt.rawType == List::class.java -> mutableListOf<Any>()
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<ASTNode>(originalValue as List<ASTNode>)
params[parameter] =
astTransformer.translateList<ASTNode>(originalValue as List<ASTNode>, context)
}

else -> params[parameter] = originalValue
}
}

val newInstance = primaryConstructor.callBy(params) as ASTNode
newInstance.parent = parent
newInstance.parent = context.parent
newInstance.origin = source
listOf(newInstance)
}
Expand Down
Loading