【问题标题】:Writing a parser for formatted text in C++在 C++ 中为格式化文本编写解析器
【发布时间】:2019-06-24 08:34:04
【问题描述】:

我正在尝试为带有这样的块的格式化 ASCII 文件编写解析器

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

每个字段可以是字符串、整数、双精度等,以格式化文本(E15.764s 等)编写。我也可以有一些我不想储存的信息。

我天真的第一个客人是简单地对关键字进行字符串比较if(!strcmp(...)),然后按位置拆分字符串以获取信息。

您知道执行相同任务的更有效方法吗?

【问题讨论】:

  • 文件必须是 ASCII 格式还是你会考虑制作一个二进制文件?
  • 检查boost spirit
  • 如果您希望您的解析器尽可能可靠,您可以使用 Lex 标记输入并使用 YACC 生成解析器。
  • @NaorHadar,它总是一个 ASCII 码。
  • @VictorGubin,boost 是否知道如何将 1234534567 之类的文本视为 5d 格式的 2 个整数?我认为 boost 中的分词器需要一个分隔符吗?

标签: c++ parsing ascii


【解决方案1】:

不幸的是,您没有提供太多有关要解析的信息。如果您给出(请编辑您的问题),那么我将为您编写一个示例解析器。

目前,我只能给你关于 Parsers 的一般解释。

这完全取决于您的数据的结构。您需要了解能够表达信息的 ASCII 表示的形式语言和语法。还有所谓的乔姆斯基层次结构,它对语言进行分类并描述了实现解析器的方式。

您对

的声明

我天真的第一个客人是简单地对关键字进行字符串比较 if(!strcmp(...)) ,然后按位置拆分字符串以获取信息。

如果您的数据是所谓的 Chomsky Type-3 正则语言,则可以使用。您不会使用 strcmp() 或其他 C 函数,而是使用 std::regex 来匹配 ASCII 文本中的模式,然后返回一些结果,用属性着色。

但是你的例子

.START_CMD 信息1 信息2 信息3 *额外的nal_info1 ... .END

表示您有一些带有复合说明符的嵌套数据。 Chomsky Type-3 常规语言无法表达这一点。正则表达式,通常实现为 DFA(确定性有限自动机)无法计数。他们没有记忆。只知道他们目前的状态。因此它们无法将某些“开始”语句的数量与“结束”语句相匹配。这是不可能的。

您需要一种语法,最好是上下文无关语法 (CFG) 来描述这种语言。解析器将使用下推自动机来实现。您将使用“解析堆栈”。这个堆栈将保存所有附加信息。那是正则表达式所没有的记忆。

在我看来,这种方法足以满足您的目的

现在,如何实现它。有几种选择:

  • Lex/Flex Yacc/Bison:非常强大。难以理解和实施
  • 提升精神:同上。也需要一些时间来理解
  • 手工解析器:很多工作。

如果您从手工制作的解析器开始,您将学到最多并理解解析的工作原理。我将在我的解释中继续这一点。

标准解决方案是 Shift/Reduce Parser。

你需要一个带有产生式(通常是动作)的语法

您将需要 TokenTypes 来查找、读取和使用输入数据的 Lexem。这通常通过正则表达式匹配来实现。

然后您将需要带有属性的令牌。 Scanner/Lexer,或简单的函数 getToken,将读取输入文本并“标记化”它。然后它将带有属性的令牌(一个属性例如一个整数的值)返回给解析器。

解析器将令牌压入堆栈。然后它尝试将堆栈的顶部与产生式的右侧匹配。如果匹配,则堆栈会减少产生式右侧的元素数量,并替换为产生式左侧的无终端。并调用了一个 Action。

重复此操作,直到所有输入都匹配或检测到语法错误。

我现在将向您展示一些(未编译。未测试)伪代码

#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;
}

我希望这能给您一个第一印象。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-06-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多