How do I solve this std::bad_cast with from antlrcpp::Any?

Viewed 1321

I'm trying to parse an expression with ANTLR4. Therefor I'm using the visitor pattern and creating an abstract syntax tree.

The result can be an expression or something other (which I removed from this example code). The visitor expects a return type of antlrcpp::Any so the elements of the abstract syntax tree are converted toantlrcpp::Any.

But I'm getting a std::bad_cast if I'm trying to convert the result back from antlrcpp::Any to std::shared_ptr<Expression>.

#include <iostream>
#include <string>
#include <memory>

#include "antlr4-runtime.h"
#include "Parser/SimpleLangLexer.h"
#include "Parser/SimpleLangParser.h"
#include "Parser/SimpleLangBaseVisitor.h"

struct Expression;

struct Node {
    virtual ~Node() { }
};

struct Expression: public Node {
    int constValue;
    Expression(int constValue) : constValue(constValue) { }
    virtual ~Expression() override { }
};

struct AstVisitor: public SimpleLangBaseVisitor {

    virtual antlrcpp::Any visitTopLevelElement(SimpleLangParser::TopLevelElementContext *ctx) override {
        std::shared_ptr<Expression> expression = ctx->expression()->accept(this);
        return std::dynamic_pointer_cast<Node>(expression);
    }

    virtual antlrcpp::Any visitIntExpr(SimpleLangParser::IntExprContext *ctx) override {

        std::string strInteger = ctx->INTEGER()->getSymbol()->getText();
        int constValue = std::stoi(strInteger);

        auto intExpr = std::make_shared<Expression>(constValue);
        return intExpr;
    }
};

int main() {

    std::string line = "123;";
    antlr4::ANTLRInputStream input(line);
    SimpleLangLexer lexer(&input);
    antlr4::CommonTokenStream tokens(&lexer);
    SimpleLangParser parser(&tokens);

    antlr4::tree::ParseTree *tree = parser.start();
    // Prints:
    // > (start (topLevelElement (expression 123) ;) <EOF>)
    std::cout << std::endl << tree->toStringTree(&parser) << std::endl;

    AstVisitor astVisitor;
    antlrcpp::Any result = tree->accept(&astVisitor);

    // Prints:
    // > Dn
    // std::cout << result.get_typeinfo().name() << std::endl;

    // Error:
    // > terminate called after throwing an instance of 'std::bad_cast'
    //     what():  std::bad_cast
    std::shared_ptr<Node> node = result;
    std::shared_ptr<Expression> iexpr = std::dynamic_pointer_cast<Expression>(node);

    // Expected:
    // > 123
    std::cout << iexpr->constValue << std::endl;
    return 0;
}

Implementation of antlrcpp::Any

My grammar:

grammar SimpleLang;

start
    : topLevelElement* EOF
    ;

topLevelElement
    : expression ';'
    ;

expression
    : IDENTIFIER #identifierExpr
    | INTEGER #intExpr
    ;

IDENTIFIER
    : [_a-zA-Z][_a-zA-Z0-9]*
    ;

INTEGER
    : [0-9]+
    ;

WS: [ \r\n\t] -> skip;

Note: I greatly reduced my code for this question and I hope it makes still sense.

1 Answers
antlrcpp::Any result = tree->accept(&astVisitor);
// Error:
// > terminate called after throwing an instance of 'std::bad_cast'
//     what():  std::bad_cast

std::shared_ptr<Node> node = tree->accept(result);

here you call tree->accept first with a pointer to an astVisitor.

Then you take the result of that, and pass it to tree->accept, which probably expects a pointer to SimpleLangBaseVisitor. But the first tree->accept call returned an Any that probably doesn't contain a pointer to a SimpleLangBaseVisitor.

So it throws an error.


As an aside, that Any you are using is missing std::typeinfo const& get_typeinfo() const. If you had that, you could do some get_typeinfo().name() printf debugging to get a clue what is going wrong.

If you have access to the source code add:

struct Base {
  virtual ~Base();
  virtual Base* clone() const = 0;
  std::typeinfo const& get_typeinfo() const = 0;
};

template<typename T>
struct Derived : Base {
  // ...
  std::typeinfo const& get_typeinfo() const override {
    return typeid(T);
  }
};

and to Any itself:

std::typeinfo const& get_typeinfo() const {
  if (!_ptr) return typeid(std::nullptr_t);
  return ptr->get_typeinfo();
}

once you have that, std::cerr << some_any.get_typeinfo().name() << std::endl; will solve many "why am I getting a bad_cast?!" pains. But it does require that both your code, and the code generating the Any, be built with this modified Any.

Related