How to build a JavaScript ANTLR visitor
Asked Answered
T

1

8

I actually have a problem with the implementation of an antlr-visitor in JavaScript. I have already created a grammar. However, I found no documentation, example or tutorial. Somehow only multi-dimensional arrays are returned. A similar problem occured there: https://github.com/antlr/antlr4/issues/1995 Unfortunately there is no solution in this discussion. In Java I have already a finished visitor and therefore just want to convert it to JS. So I would prefer, if there is a solution without a listener. Thanks in advance for any help

EDIT: Here is the code for the visitor, the grammar and the startingtool.

const antlr4 = require('antlr4');
const grammarLexer = require('./SimpleGrammarLexer');
const grammarParser = require('./SimpleGrammarParser');
const extendGrammarVisitor = require('./ExtendGrammarVisitor.js');

export class SimpleGrammar {
    public static parseCode(formula: string) {
        const inputStream = new antlr4.InputStream(formula);
        const lexer = new grammarLexer.SimpleGrammarLexer(inputStream);
        const commonTokenStream = new antlr4.CommonTokenStream(lexer);
        const parser = new grammarParser.SimpleGrammarParser(commonTokenStream);
        const visitor = new extendGrammarVisitor.ExtendGrammarVisitor();
        const parseTree = parser.r();
        visitor.visitR(parseTree);
    }
}


grammar SimpleGrammar;
r: input;
INT    : [0-9]+;
DOUBLE : [0-9]+'.'[0-9]+;
PI     : 'pi';
E      : 'e';
POW    : '^';
NL     : '\n';
WS     : [ \t\r]+ -> skip;
ID     : [a-zA-Z_][a-zA-Z_0-9]*;

PLUS  : '+';
EQUAL : '=';
MINUS : '-';
MULT  : '*';
DIV   : '/';
LPAR  : '(';
RPAR  : ')';

input
    : setVar NL input     # ToSetVar
    | plusOrMinus NL? EOF # Calculate
    ;

setVar
    : ID EQUAL plusOrMinus # SetVariable
    ;


plusOrMinus 
    : plusOrMinus PLUS multOrDiv  # Plus
    | plusOrMinus MINUS multOrDiv # Minus
    | multOrDiv                   # ToMultOrDiv
    ;

multOrDiv
    : multOrDiv MULT pow # Multiplication
    | multOrDiv DIV pow  # Division
    | pow                # ToPow
    ;

pow
    : unaryMinus (POW pow)? # Power
    ;

unaryMinus
    : MINUS unaryMinus # ChangeSign
    | atom             # ToAtom
    ;

atom
    : PI                    # ConstantPI
    | E                     # ConstantE
    | DOUBLE                # Double
    | INT                   # Int
    | ID                    # Variable
    | LPAR plusOrMinus RPAR # Braces
    ;


"use strict";
var __extends = (this && this.__extends) || function (d, b) {
    for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
    function __() { this.constructor = d; }
    d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
var simpleGrammarVisitor = require('./SimpleGrammarVisitor.js');
var ExtendGrammarVisitor = (function (_super) {
    __extends(ExtendGrammarVisitor, _super);
    function ExtendGrammarVisitor() {
        _super.apply(this, arguments);
    }
    ExtendGrammarVisitor.prototype.visitR = function(ctx) {
        return this.visitChildren(ctx);
    };
    ExtendGrammarVisitor.prototype.visitToSetVar = function (ctx) {
        return this.visitChildren(ctx);
    };

    ExtendGrammarVisitor.prototype.visitCalculate = function (ctx) {
        return this.visitChildren(ctx);
    };

    ExtendGrammarVisitor.prototype.visitSetVariable = function (ctx) {
        return this.visitChildren(ctx);
    };

    ExtendGrammarVisitor.prototype.visitToMultOrDiv = function (ctx) {
        return this.visitChildren(ctx);
    };

    ExtendGrammarVisitor.prototype.visitPlus = function (ctx) {
        var example = this.visit(ctx.plusorminus(0)); // ???
        return this.visitChildren(ctx);
    };

    ExtendGrammarVisitor.prototype.visitMinus = function (ctx) {
        return this.visitChildren(ctx);
    };

    ExtendGrammarVisitor.prototype.visitMultiplication = function (ctx) {
        return this.visitChildren(ctx);
    };

    ExtendGrammarVisitor.prototype.visitDivision = function (ctx) {
        return this.visitChildren(ctx);
    };

    ExtendGrammarVisitor.prototype.visitToPow = function (ctx) {
        return this.visitChildren(ctx);
    };

    ExtendGrammarVisitor.prototype.visitPower = function (ctx) {
        return this.visitChildren(ctx);
    };

    ExtendGrammarVisitor.prototype.visitChangeSign = function (ctx) {
        return this.visitChildren(ctx);
    };

    ExtendGrammarVisitor.prototype.visitToAtom = function (ctx) {
        return this.visitChildren(ctx);
    };

    ExtendGrammarVisitor.prototype.visitConstantPI = function (ctx) {
        return this.visitChildren(ctx);
    };

    ExtendGrammarVisitor.prototype.visitConstantE = function (ctx) {
        return this.visitChildren(ctx);
    };

    ExtendGrammarVisitor.prototype.visitDouble = function (ctx) {
        return this.visitChildren(ctx);
    };

    ExtendGrammarVisitor.prototype.visitInt = function (ctx) {
        return this.visitChildren(ctx);
    };

    ExtendGrammarVisitor.prototype.visitVariable = function (ctx) {
        return this.visitChildren(ctx);
    };

    ExtendGrammarVisitor.prototype.visitBraces = function (ctx) {
        return this.visitChildren(ctx);
    };
    return ExtendGrammarVisitor;
}(simpleGrammarVisitor.SimpleGrammarVisitor));
exports.ExtendGrammarVisitor = ExtendGrammarVisitor;

At this point I don't know how to continue and implement the methods for the visitor.

Tympanic answered 21/2, 2019 at 21:32 Comment(1)
visitChildren returns an array in JavaScript, as opposed to returning the value of the last child in Java. So if you're getting results wrapped in arrays, it means that you didn't define some visit method and visitChildren got called instead. For more specific help, please post an MCVE of your code.Supersedure
S
2

In JavaScript visitChildren returns an array containing the result of visiting the children. So if a node has two children for example, then visitChildren(node) will return [visit(node.child1), visit(node.child2)] (whereas in Java you'd only get the result of visit(node.child2) and the result of visit(node.child1) would be discarded). If it has only one child, you get an array containing only one element (whereas in Java, you'd just get the element without the array).

In your code you're calling visitChildren everywhere, so that's why you end up with nested arrays. If you don't want arrays, you either shouldn't call visitChildren or call it, unpack the array and then return something else. For example here's two ways how one might implement visitPlus to return the result of the addition (if that's the goal):

// Using visitChildren and unpacking
ExtendGrammarVisitor.prototype.visitPlus = function (ctx) {
    let [lhs, rhs] = this.visitChildren(ctx);
    return lhs+rhs;
};

// Or without visitChildren
ExtendGrammarVisitor.prototype.visitPlus = function (ctx) {
    return this.visit(ctx.plusOrMinus()) + this.visit(ctx.multOrDiv());
    // If you added labels to the grammar, you could write the above more readably
    // using labels like `lhs` and `rhs` instead of `plusOrMinus` and `multOrDiv`,
    // so the reader doesn't have to remember which one is the right and which one
    // the left operand
};

Of course, for this to work, all the other visitor methods also need to be changed to return numbers. And if you want your visitor to do something other than evaluating the expression, you need to adjust the code accordingly.

PS: You can simplify your grammar simply by making use of ANTLR4's precedence handling rules, which allow you to list infix operators in descending order of precedence with the operators being left-associative unless you specify <assoc=right>. That way you can consolidate all of your expression rules into a single one like this:

expr
    : PI                                     # ConstantPI
    | E                                      # ConstantE
    | DOUBLE                                 # Double
    | INT                                    # Int
    | ID                                     # Variable
    | LPAR expr RPAR                         # Braces
    | MINUS expr                             # ChangeSign
    | <assoc=right> lhs=expr op=POW rhs=expr # InfixExpression
    | lhs=expr op=(MULT|DIV) rhs=expr        # InfixExpression
    | lhs=expr op=(PLUS|MINUS) rhs=expr      # InfixExpression
    ;
Supersedure answered 23/2, 2019 at 15:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.