用C ++编写格式文本的解析器

时间:2019-06-24 08:34:04

标签: c++ parsing ascii

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

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

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

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

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

1 个答案:

答案 0 :(得分:1)

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

目前,我只能为您提供有关解析器的一般说明。

这一切都取决于数据的结构。您需要了解能够表达信息的ASCII表示形式的形式语言和语法。还有所谓的Chomsky层次结构,它对语言进行分类并描述了如何实现解析器。

您关于

的声明
  

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

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

但您的示例

  

。START_CMD   info1 info2 info3   *另外nal_info1 ...   .END

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

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

我认为,这种方法足以满足您的目的

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

  • Lex / Flex Yacc / Bison:极其强大。难以理解和实施
  • Boost Spirit:与上述类似。还需要一些时间来了解
  • 手工分析器:很多工作。

如果从手工解析器开始,您将学到最多并了解解析是如何工作的。我将在解释中继续说明。

标准解决方案是Shift / Reduce解析器。

您将需要一个包含产生式(通常是动作)的语法

您将需要TokenTypes来查找,读取和使用输入数据的Lexems。通常使用规则表达式匹配来实现。

然后,您将需要带有属性的令牌。扫描程序/词法分析器(或简称为getToken函数)将读取输入文本并将其“标记化”。然后,它将具有属性(例如,属性是Integer的值)的令牌返回到解析器。

解析器将令牌压入堆栈。然后,它尝试将堆栈的顶部与生产的右侧相匹配。如果存在匹配项,则堆栈将通过生产右侧的元素数量减少,并被生产左侧的无端子取代。然后调用一个动作。

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

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

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

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