mcchatman8009 / antlr4-tool

A useful Antlr4 tool with full TypeScript support
Other
36 stars 20 forks source link

Add labeled alternatives into module.exports and declaration file #11

Closed alwonder closed 6 years ago

alwonder commented 6 years ago

Problem

The other day I've came across the issue that I can't access my labeled alternative rule contexts. It won't declare neither in .d.ts file nor exports section in Parser.js file. For example, if I have the grammar with rules like:

parse
  : expression EOF
  ;

expression
  : literal                                                    #primaryExpr
  | '(' expression ')'                                         #parensExpr
  | expression op=('*'|'/') expression                         #mulDivExpr
  | expression op=('+'|'-') expression                         #addSubExpr
  | expression op=('AND'|'OR'|'NOT') expression                #booleanExpr
  | expression op=('=='|'!='|'>'|'<'|'>='|'<=') expression     #comparisonExpr
  ;

literal
  : sign=('+'|'-')? NUMBER_LITERAL
  | BOOL_LITERAL
  ;

it compiles only ExpressionContext and LiteralContext classes although Parser.js has members for labeled alternatives.

  // MyLanguageParser.d.ts

  export declare class ParseContext extends ParserRuleContext {
      // ...
  }

  export declare class ExpressionContext extends ParserRuleContext {
      // ...
  }

  export declare class LiteralContext extends ParserRuleContext {
      // ...
  }
  // MyLanguageParser.js

  /* ...generated parser... */

  exports.MyLanguageParser = MyLanguageParser;
  exports.ParseContext = ParseContext;
  MyLanguageParser.ParseContext = ParseContext;
  exports.ExpressionContext = ExpressionContext;
  MyLanguageParser.ExpressionContext = ExpressionContext;
  exports.LiteralContext = LiteralContext;
  MyLanguageParser.LiteralContext = LiteralContext;

Solution

I found out this happens because labeled alternative contexts don't exist inside the parser instance. But we can get it from the Parser class itself as static methods. So, if we pass through Parser class keys, we'll get both labeled alternatives and regular rule contexts. Thereby after some corrections I get the following output.

  // MyLanguageParser.d.ts

  export declare class ParseContext extends ParserRuleContext {
      // ...
  }

  export declare class AddSubExprContext extends ParserRuleContext {
      // ...
  }

  export declare class PrimaryExprContext extends ParserRuleContext {
      // ...
  }

  export declare class BooleanExprContext extends ParserRuleContext {
      // ...
  }

  export declare class ComparisonExprContext extends ParserRuleContext {
      // ...
  }

  export declare class ParensExprContext extends ParserRuleContext {
      // ...
  }

  export declare class MulDivExprContext extends ParserRuleContext {
      // ...
  }

  export declare class LiteralContext extends ParserRuleContext {
      // ...
  }

  export declare class ExpressionContext extends ParserRuleContext {
      // ...
  }
  // MyLanguageParser.js

  /* ...generated parser... */

  exports.MyLanguageParser = MyLanguageParser;
  exports.ParseContext = ParseContext;
  MyLanguageParser.ParseContext = ParseContext;
  exports.ExpressionContext = ExpressionContext;
  MyLanguageParser.ExpressionContext = ExpressionContext;
  exports.LiteralContext = LiteralContext;
  MyLanguageParser.LiteralContext = LiteralContext;
  exports.AddSubExprContext = AddSubExprContext;
  MyLanguageParser.AddSubExprContext = AddSubExprContext;
  exports.PrimaryExprContext = PrimaryExprContext;
  MyLanguageParser.PrimaryExprContext = PrimaryExprContext;
  exports.BooleanExprContext = BooleanExprContext;
  MyLanguageParser.BooleanExprContext = BooleanExprContext;
  exports.ComparisonExprContext = ComparisonExprContext;
  MyLanguageParser.ComparisonExprContext = ComparisonExprContext;
  exports.ParensExprContext = ParensExprContext;
  MyLanguageParser.ParensExprContext = ParensExprContext;
  exports.MulDivExprContext = MulDivExprContext;
  MyLanguageParser.MulDivExprContext = MulDivExprContext;

After this change I'm able to check the current context instance and safely invoke its inner methods

  // Example Expression Visitor method

  static visitExpression(ctx: ExpressionContext) {
    if (ctx instanceof PrimaryExprContext) {
      return ExpressionVisitor.visitPrimary(ctx.primary());
    }

    if (ctx instanceof ParensExprContext) {
      return ExpressionVisitor.visitParenthesesExpression(ctx.expression());
    }

    if (
      ctx instanceof AddSubExprContext
      || ctx instanceof MulDivExprContext
      || ctx instanceof BooleanExprContext
      || ctx instanceof ComparisonExprContext
    ) {
      return ExpressionVisitor.visitTwoMembersExpression(ctx);
    }

    throw new Error('Error while visiting ExpressionContext. No expected statements found');
  }
mcchatman8009 commented 6 years ago

@alwonder Reviewing now

mcchatman8009 commented 6 years ago

@alwonder Looks great, I'm done reviewing and will merge and release. And thanks again, I appreciate your contribution to the project. I have some other changes that I plan to release in the future that I would like for you and others to review.