Antlr4 visitor functions wrap the result in an array (more precisely in accept function)

56 Views Asked by At

I was writing antlr4 parser, using target language JavaScript, and I need to use visitor to get custom output from my tree (one array, every tree level on other array level). But I get some troubles with returning values from visit functions, when I try to return value from every visit function I get result wrapped in array (I don't need this). I tried to debug my code, but I can't truely see a code part in antlr4 library when this array is created (probably when accept function return a value, accept called inside visitChildren method). I have default operations that work as a chain until value match with concrete operation (expression -> relation -> add -> mult -> etc.).

visitor.js:

visitFormula(ctx) {
    let result = [];

    for (let i = 1; i < ctx.getChildCount() - 1; i++) {
      let part = this.visit(ctx.getChild(i));
      result.push(part);
    }

    console.log("formula " + JSON.stringify(result));

    return result;
  }

  // Visit a parse tree produced by MyGrammarParser#expo.
  visitExpo(ctx) {
    let result = this.visitChildren(ctx);
    console.log("expo " + JSON.stringify(result));
    return result;
  }

  // Visit a parse tree produced by MyGrammarParser#unary.ex
  visitUnary(ctx) {
    let result = this.visitChildren(ctx);
    console.log("unary " + JSON.stringify(result));
    return result;
  }



  // Visit a parse tree produced by MyGrammarParser#mult.
  visitMult(ctx) {
    let result = this.visitChildren(ctx);
    console.log("mult " + JSON.stringify(result));
    return result;
  }

  // Visit a parse tree produced by MyGrammarParser#add.
  visitAdd(ctx) {
    let result = this.visitChildren(ctx);
    console.log("add " + JSON.stringify(result));
    return result;
  }

  visitTerm(ctx) {
    if (this.isTerminalNode(ctx)) {
        console.log("term " + JSON.stringify(ctx.getText()));
        return ctx.getText();
    }
    let result = this.visitChildren(ctx);
    console.log("term " + JSON.stringify(result));

    return result;
  }

  visitFunction(ctx) {
    let result = [ctx.FUNCTION_NAME().getText()];

    const expressionList = ctx.expression();

    for (let i = 0; i < expressionList.length; i++) {
      console.log("FUNC ARGS", expressionList[i].getText());
      let part = this.visit(expressionList[i]);
      result.push(part);
    }
    console.log("function " + JSON.stringify(result));
    return result;
  }

parser.g4:

formula : ST expression EOF;

expo: unary (CARRET unary)*;

unary : (ADD | negation)* term;

negation : SUB;

mult : expo ((MUL | DIV) expo)*;

add : mult ((ADD | SUB) mult)*;

relation
    :   add (RELATION_SYMBOL add)*
    ;

expression
    :   relation ((AND | OR) relation)*
    ;

function
    :   FUNCTION_NAME OPEN_ROUND_BRACKET (expression (COMMA expression)*)? CLOSE_ROUND_BRACKET
    ;

term : function | INTEGER | DECIMAL | PERCENT | reference | OPEN_ROUND_BRACKET expression CLOSE_ROUND_BRACKET;

index.js:

const formula = "=IF($I$11=D169,1,0)";
var chars = new InputStream(formula, true);
var lexer = new MyGrammarLexer(chars);
var tokens = new CommonTokenStream(lexer);
var parser = new MyGrammarParser(tokens);


const tree = parser.formula();

const visitor = new GrammarParserVisitor(); // FormulaWalker 

let res = visitor.visitFormula(tree);
console.log(JSON.stringify(res));

I can show part of the result (when visting this input's =IF($I$11=D169,1,0) third parameter), that i get from console.log in visit functions:

term "0"
unary ["0"]
expo [["0"]]
mult [[["0"]]]
add [[[["0"]]]]
relation [[[[["0"]]]]]

But I need just to return string without wrapping it in arrays. Why I get this problem and how can I fix it?

1

There are 1 best solutions below

0
Bart Kiers On

Here's a start for a visitor in JavaScript. I simplified your grammar a little bit (yes, know this is not correct, but this is just a demo):

grammar MyGrammar;

formula    : ST expression EOF;
expo       : unary (CARRET unary)*;
unary      : (ADD | negation)* term;
negation   : SUB;
mult       : expo ((MUL | DIV) expo)*;
add        : mult ((ADD | SUB) mult)*;
relation   : add (rel_op add)*;
expression : relation ((AND | OR) relation)*;
function   : FUNCTION_NAME OPEN_ROUND_BRACKET (expression (COMMA expression)*)? CLOSE_ROUND_BRACKET;
term       : function | INTEGER | DECIMAL | PERCENT | reference | OPEN_ROUND_BRACKET expression CLOSE_ROUND_BRACKET;
reference  : CELL;
rel_op     : RELATION_SYMBOL | ST;

INTEGER             : [0-9]+;
DECIMAL             : INTEGER '.' INTEGER;
PERCENT             : '%';
CELL                : [A-Z] INTEGER;
ST                  : '=';
CARRET              : '^';
ADD                 : '+';
SUB                 : '-';
MUL                 : '*';
DIV                 : '/';
COMMA               : ',';
AND                 : 'AND';
OR                  : 'OR';
OPEN_ROUND_BRACKET  : '(';
CLOSE_ROUND_BRACKET : ')';
RELATION_SYMBOL     : '>=' | '>' | '<=' | '<';
FUNCTION_NAME       : [A-Z]+;

A visitor might look like this:

import MyGrammarVisitor from "./MyGrammarVisitor.js";

export default class DemoVisitor extends MyGrammarVisitor {

  // formula : ST expression EOF;
  visitFormula(ctx) {
    return this.visit(ctx.expression());
  }

  // expo: unary (CARRET unary)*;
  visitExpo(ctx) {
    // TODO account for multiple `unary`s
    return this.visit(ctx.unary(0));
  }

  // unary : (ADD | negation)* term;
  visitUnary(ctx) {
    return this.visit(ctx.term());
  }

  // negation : SUB;
  visitNegation(ctx) {
    throw new Error('TODO: negation')
  }

  // mult : expo ((MUL | DIV) expo)*;
  visitMult(ctx) {
    // TODO account for multiple `expo`s
    return this.visit(ctx.expo(0));
  }

  // add : mult ((ADD | SUB) mult)*;
  visitAdd(ctx) {
    // TODO account for multiple `mult`s
    return this.visit(ctx.mult(0));
  }

  // relation : add (rel_op add)*;
  visitRelation(ctx) {
    if (ctx.add().length > 1) {
      const v1 = this.visit(ctx.add(0));
      const v2 = this.visit(ctx.add(1));
      const operator = this.visit(ctx.rel_op());

      if (operator == '=') {
        return v1 == v2;
      }
      else {
        throw new Error('Operator not implemented: `' + operator + '`');
      }
    }

    return this.visit(ctx.add(0));
  }

  // expression : relation ((AND | OR) relation)*;
  visitExpression(ctx) {
    // TODO account for multiple `relation`s
    return this.visit(ctx.relation(0));
  }

  // function : FUNCTION_NAME OPEN_ROUND_BRACKET (expression (COMMA expression)*)? CLOSE_ROUND_BRACKET;
  visitFunction(ctx) {
    // Assume there is only 1 type of function: an IF with 3 expressions
    const e1 = this.visit(ctx.expression(0));
    const e2 = this.visit(ctx.expression(1));
    const e3 = this.visit(ctx.expression(2));
    console.log('e1:', e1, ', e2:', e2, ', e2:', e3);
    return e1 === true ? e2 : e3;
  }

  // term : function | INTEGER | DECIMAL | PERCENT | reference | OPEN_ROUND_BRACKET expression CLOSE_ROUND_BRACKET;
  visitTerm(ctx) {
    if (ctx.function_() != null) return this.visit(ctx.function_());
    if (ctx.INTEGER() != null) return parseInt(ctx.INTEGER().getText());
    if (ctx.reference() != null) return this.visit(ctx.reference());
    throw new Error('TODO: implement other terms');
  }

  // reference : CELL;
  visitReference(ctx) {
    return ctx.CELL().getText();
  }

  // rel_op : RELATION_SYMBOL | ST;
  visitRel_op(ctx) {
    return ctx.RELATION_SYMBOL() == null ? ctx.ST().getText() : ctx.RELATION_SYMBOL().getText();
  }
}

which can be tested with the following main.js:

import antlr4 from 'antlr4';
import MyGrammarLexer from './MyGrammarLexer.js';
import MyGrammarParser from './MyGrammarParser.js';
import DemoVisitor from './DemoVisitor.js';

const input = "=IF(D169=D169,100,4242)";
const lexer = new MyGrammarLexer(new antlr4.InputStream(input));
const parser = new MyGrammarParser(new antlr4.CommonTokenStream(lexer));
const visitor = new DemoVisitor();
const result = visitor.visit(parser.formula());

console.log('result:', result);

My package.json looks like this:

{
  "name": "antlr4-demo",
  "version": "1.0.0",
  "type": "module",
  "scripts": {
    "start": "node ./main.js"
  },
  "dependencies": {
    "antlr4": "^4.13.1-patch-1"
  }
}

If you now run this file with npm run start, you will see this on your console:

e1: true , e2: 100 , e2: 4242
result: 100