Skip to content

Make ANTLRParseTreeToPSIConverter aware of Rule subtypes  #26

@vitorpamplona

Description

@vitorpamplona

I'd like to propose a second "rule" processor (ANTLRParseTreeToPSIConverter) by the Rule Context's class name instead of the rule index in Antlr's generated Parser class. The goal is to provide enough information to the Parser Definition's createElement, though ASTNodes.elementType, to replicate the Context classes in the PSI Tree

Antlr has a generator option that includes subclassing rules by name. The grammar below generates a single Rule integer for simpleLiteral but three Context classes in the parser: SimpleLiteralContext, SimpleStringLiteralContext, SimpleNumberLiteralContext.

simpleLiteral
    : STRING           #simpleStringLiteral
    | NUMBER           #simpleNumberLiteral
    ;

SimpleStringLiteralContext, SimpleNumberLiteralContext extend SimpleLiteralContext

public static class SimpleLiteralContext extends ParserRuleContext {
    public SimpleLiteralContext(ParserRuleContext parent, int invokingState) {
        super(parent, invokingState);
    }
    @Override public int getRuleIndex() { return RULE_simpleLiteral; }
    //...
}
public static class SimpleNumberLiteralContext extends SimpleLiteralContext {
    public TerminalNode NUMBER() { return getToken(cqlParser.NUMBER, 0); }
    public SimpleNumberLiteralContext(SimpleLiteralContext ctx) { copyFrom(ctx); }
    //...
}
public static class SimpleStringLiteralContext extends SimpleLiteralContext {
    public TerminalNode STRING() { return getToken(cqlParser.STRING, 0); }
    public SimpleStringLiteralContext(SimpleLiteralContext ctx) { copyFrom(ctx); }
    //...
}

The change would need a RuleMap class as below:

// All Rule names, including subrules (subclasses)
val RULE_NAME_INDEX = antlrParser::class.nestedClasses.map { it.simpleName }.toTypedArray()

// List of Indexed Rule Types (required by IntelliJ)
private val RULES: List<RuleIElementType> = RULE_NAME_INDEX.mapIndexed { index, s ->
    RuleIElementType(index, s, CqlLanguage)
}

// Map between Context names and Rule Types
val RULE_MAP: Map<String, RuleIElementType> = RULES.associateBy { it.debugName }

And then a new ANTLRParseTreeToPSIConverter listener that would be very similar to the current one but with maker.done by Class name and not rule index.

class AntlrContextToPSIConverter(language: Language?, parser: Parser?, builder: PsiBuilder?) :
    ANTLRParseTreeToPSIConverter(language, parser, builder) {
    override fun exitEveryRule(ctx: ParserRuleContext) {
        ...
        marker.done(CqlRuleTypes.RULE_MAP[ctx.javaClass.simpleName]!!)
    }
}

The Parser Definition then create PSINodes for each subclass

   //...
   override fun createElement(node: ASTNode): PsiElement {
        val elType = node.elementType
        if (elType is TokenIElementType) {
            return ANTLRPsiNode(node)
        }
        if (elType !is RuleIElementType) {
            return ANTLRPsiNode(node)
        }

        return when(CqlRuleTypes.RULE_NAMES[elType.ruleIndex]) {
            "SimpleLiteral" -> SimpleLiteralPSI(node)
            "SimpleStringLiteral" -> SimpleStringLiteralPSI(node)
            "SimpleNumberLiteral" -> SimpleNumberLiteralPSI(node)
            //..
        }
    }

We implemented this variation in this project: https://github.com/Path-Check/intellij-cql

As you can see, we had to go around some of the interface design (Map of Rules instead of List) for the adaptor's converters to make it work.

Apologies for blending Java and Kotlin in the same text. :)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions