API для посетителей Antlr4 c ++ - PullRequest
0 голосов
/ 10 мая 2018

Я использую API-интерфейс C ++ для Antlr4 для обхода дерева разбора. Однако я изо всех сил пытаюсь заставить это функционировать правильно. А именно, я не уверен, как использовать вызов visitChildren(ParseTree *tree).

Мне дан контекст для каждого правила, которое я определил. И я могу пройти по дереву, используя контексты: context->accept[RuleContext]([RuleContext]* rule)

Однако, когда я их использую, я постоянно посещаю один и тот же узел несколько раз.

Например:

program:
    : nameRule
      dateRule
      ( statements )*
      EOF
    ;

nameRule
    : NAME IDENTIFIER ;

dateRule
    : DATE IDENTIFIER ;

statements:
    : statementX
    | statementY
    | statementZ
    ;

statementX:
    : // do something here

statementY:
    : // do something here

statementZ:
    : // do something here

IDENTIFIER, DATE и NAME являются клеммами.

Я строю структуру синтаксического анализа Antlr следующим образом:

void Parser::parse() {
    ifstream file(FLAGS_c, ifstream::binary);
    // Convert the file into ANTLR's format.
    ANTLRInputStream stream = ANTLRInputStream(file);

    // Give the input to the lexer.
    MyLexer lexer = new MyLexer(&stream);
    // Generate the tokens.
    CommonTokenStream tokens(lexer);

    file.close();

    tokens.fill();

    // Create the translation that will parse the input.
    MyParser parser = new MyParser(&tokens);
    parser->setBuildParseTree(true);
    MyParser::ProgramContext *tree = parser->program();

    auto *visitor = new MyVisitor();
    visitor->visitProgram(tree);
}

Так что, когда я пытаюсь обойти это, это выглядит примерно так, класс MyVisitor расширяется MyParserVisitor. MyVisitor - это класс посетителей, который я использую для обхода сгенерированного дерева.

Any MyVisitor::visitProgram(ParserVisitor::ProgramContext *context) {
    this->visitNameRule(context->nameRule());
    this->visitDateRule(context->dateRule());

    if (!this->statements.empty()) {
        for (auto &it : this->statements) {
            this->visitStatements(it);
        }
    }
    return Any(context);
}

// Omitting name and date rules.

Any MyVisitor::visitStatements(ParserVisitor::StatementContext *context) {
    this->visitStatementX(context->statementX());
    this->visitStatementY(context->statementY());
    this->visitStatementZ(context->statementZ());
    return Any(context);
}

В этом случае операторы X, Y и Z будут посещаться при каждом посещении операторов. Даже если их нет в программе ввода.

Это правильный способ использовать это? Если это не так, то я предполагаю, что visitChildren(ParseTree *tree) - это правильный API для каждой функции посетителя. Но я не понимаю, как получить доступ к ParseTree структуре данных из *Context.

1 Ответ

0 голосов
/ 10 мая 2018

Этот вопрос не имеет прямого отношения к посетителю C ++, а является общей проблемой посетителя в ANTLR4. То, что вы делаете, заключается в том, чтобы ускорить ход посетителей так, как вы этого не делаете. Не заходите явно к определенным поддеревьям вручную, а вместо этого вызывайте супер-реализацию, чтобы позволить вам сделать это, и собрать результат в отдельных visitStatementXXX функциях. Посмотрите на эту реализацию (очень простого) средства оценки выражений , используемого в модульном тесте (написанном на C ++). Вот частичная копия, чтобы продемонстрировать принцип:

class EvalParseVisitor : public MySQLParserBaseVisitor {
public:
  std::vector<EvalValue> results; // One entry for each select item.

  bool asBool(EvalValue in) {
    if (!in.isNullType() && in.number != 0)
      return true;
    return false;
  };

  virtual Any visitSelectItem(MySQLParser::SelectItemContext *context) override {
    Any result = visitChildren(context);
    results.push_back(result.as<EvalValue>());
    return result;
  }

  virtual Any visitExprNot(MySQLParser::ExprNotContext *context) override {
    EvalValue value = visit(context->expr());
    switch (value.type) {
      case EvalValue::Null:
        return EvalValue::fromNotNull();
      case EvalValue::NotNull:
        return EvalValue::fromNull();
      default:
        return EvalValue::fromBool(!asBool(value));
    }
  }

  virtual Any visitExprAnd(MySQLParser::ExprAndContext *context) override {
    EvalValue left = visit(context->expr(0));
    EvalValue right = visit(context->expr(1));

    if (left.isNullType() || right.isNullType())
      return EvalValue::fromNull();
    return EvalValue::fromBool(asBool(left) && asBool(right));

    return visitChildren(context);
  }
...

Важной частью является вызов visit(), который, в свою очередь, выполняет итерацию по дочерним узлам данного контекстного дерева и запускает только функции посетителя для элементов, которые действительно существуют.

...