Написание парсера для форматированного текста на C ++ - PullRequest
0 голосов
/ 24 июня 2019

Я пытаюсь написать синтаксический анализатор для отформатированного файла ASCII с блоками вроде этого

.START_CMD
info1 info2 info3
* additionnal_info1...
.END

каждое поле может быть строкой, целым числом, двойным числом и т. Д., Записанным в форматированном тексте (E15.7, 64s и т. Д.).У меня также может быть некоторая информация, которую я не хочу складировать.

Мой первый наивный гость - просто выполнить сравнение строк if(!strcmp(...)) для ключевых слов, а затем разделить строки по позициям для получения информации.

Знаете ли вы более эффективный способ выполнить ту же задачу?

1 Ответ

1 голос
/ 25 июня 2019

К сожалению, вы не дали столько информации о том, что вы хотите проанализировать.Если вы дадите (пожалуйста, отредактируйте свой вопрос), то я напишу для вас пример парсера.

Пока я могу дать вам только общие пояснения по парсерам.

Все зависит от того, какваши данные структурированы.Вы должны понимать формальные языки и грамматики, которые могут выражать ваше представление информации в ASCII.Существует также так называемая иерархия Хомского, которая классифицирует языки и описывает, как реализовать синтаксический анализатор.

Ваше утверждение относительно

Мой наивный первый гость простовыполните сравнение строк if (! strcmp (...)) для ключевых слов, а затем разбейте строку по позициям для получения информации.

сработает, если ваши данные будут так называемыми повторениями Хомского типа-3язык.Вы не будете использовать strcmp () или другие C-функции, но std :: regex, чтобы сопоставлять шаблоны в тексте ASCII, а затем возвращать некоторые результаты, окрашенные атрибутами.

Но ваш пример

.START_CMD info1 info2 info3 * extensionnal_info1 ... .END

указывает, что у вас есть некоторые вложенные данные с составными спецификаторами.Это не может быть выражено обычными языками Хомского типа-3.Регулярные выражения, обычно реализуемые как DFA (детерминированный конечный автомат), не могут учитываться.У них нет памяти.Знают только их текущее состояние.Таким образом, они не могут сопоставить количество некоторых «открывающих» операторов с «закрывающими».Это невозможно.

Для описания такого языка вам нужна грамматика и лучшая контекстно-свободная грамматика (CFG).И парсер будет реализован с использованием автомата.Вы бы использовали "Parse Stack".И этот стек будет содержать всю дополнительную информацию.Это память, которой нет у выражений повторения.

И, на мой взгляд, такого подхода будет достаточно для ваших целей

Теперь, как это реализовать.Есть несколько вариантов:

  • Lex / Flex Yacc / Bison: чрезвычайно мощный.Трудно понять и реализовать
  • Boost Spirit: аналогично вышеописанному.Также нужно некоторое время, чтобы разобраться
  • Парсер ручной работы: Много работы.

Если вы начнете с парсера, созданного вручную, вы узнаете больше всего и поймете, как работает синтаксический анализ.Я продолжу с этим в моих объяснениях.

Стандартное решение - парсер сдвига / уменьшения.

И вам понадобится грамматика с продукцией (и обычно действиями)

Вам понадобится TokenTypes для поиска, чтения и использования лексем входных данных.Обычно это реализуется с помощью соответствия выражений regurlar.

Тогда вам понадобятся токены с атрибутами.Сканер / Lexer или просто функция getToken прочитает введенный текст и «токенизирует» его.Затем он возвращает токены с атрибутами (атрибутом является, например, значение целого числа) в анализатор.

Анализатор помещает токен в стек.Затем он пытается сопоставить вершину стека с правой стороной производства.Если есть совпадение, стек уменьшается на количество элементов в правой части производства и заменяется нулевым терминалом на левой стороне производства.И вызывается действие.

Это повторяется до тех пор, пока не будут сопоставлены все входные данные или не будет обнаружена синтаксическая ошибка.

Сейчас я покажу вам некоторый (НЕ Скомпилированный. НЕ ПРОВЕРЕННЫЙ) псевдокод

#include <vector>
#include <string>
#include <variant>
#include <functional>
#include <iostream>

// Here we store token types for Terminals and None-Terminals
enum class TokenType {END, OK, EXPRESSION, START1, END1, START2, END2, INTEGER, DOUBLE, STRING};

struct TokenWIthAttribute {
    TokenWIthAttribute(const TokenType &tt) : tokenType(tt) {}
    TokenWIthAttribute(const TokenWIthAttribute &twa) : tokenType(twa.tokenType) {}

    TokenType tokenType{};
    std::variant<int, double, std::string> attribute{};

    bool operator ==(const TokenWIthAttribute& twa) const { return tokenType == twa.tokenType;}
};

using NonTerminal = TokenType;
using Handle = std::vector<TokenWIthAttribute>;
using Action = std::function<TokenWIthAttribute(TokenWIthAttribute&)>;

struct Production {
    NonTerminal     nonTerminal{};  //Left side of Production
    Handle          handle{};       //Rigth side of prodcution
    Action          action;         //Action to take during reduction
};

using Grammar = std::vector<Production>;

TokenWIthAttribute actionEndOK(TokenWIthAttribute& twa) {
    // Do something with twa
    return twa;
}

Grammar grammar{
    { TokenType::OK, {TokenType::START1, TokenType::EXPRESSION, TokenType::END1, TokenType::END},actionEndOK}
    // Many lines of more productions
};

using ParseStack = std::vector<TokenWIthAttribute>;

class Parser
{
public:
    bool parse(std::istream &is);
protected:
    TokenWIthAttribute getToken(std::istream &is);
    void shift(TokenWIthAttribute& twa) { parseStack.push_back(twa); }
    bool matchAndReduce();

    ParseStack parseStack;
};


bool Parser::matchAndReduce()
{
    bool result{ false };
    // Iterate over all productions in the grammar
    for (const Production& production : grammar) {
        if (production.handle.size() <= parseStack.size()) {
            // If enough elements on the stack, match the top of the stack with a production
            if (std::equal(production.handle.begin(), production.handle.end(), parseStack.cend() - production.handle.size())) {
                // Found production: Reduce
                parseStack.resize(parseStack.size() - production.handle.size());
                // Call action. Replace right side of production with left side
                parseStack.emplace_back(production.action(*(parseStack.begin()+parseStack.size()-1)));
                result = true;
                break;
            }
        }
    }
    return result;
}
int main()
{
    std::cout << "Hello World\n";
    return 0;
}

Надеюсь, это даст вам первое впечатление.

...