我正在尝试为具有这样的块的格式化ASCII文件编写解析器
.START_CMD
info1 info2 info3
* additionnal_info1...
.END
每个字段可以是用格式化文本(E15.7
,64s
等)编写的字符串,整数,双精度数等。我还可以得到一些我不想库存的信息。
我天真的第一位客人只是对关键字进行字符串比较if(!strcmp(...))
,然后按位置对字符串进行拆分以获取信息。
您知道执行相同任务的更有效方法吗?
答案 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)来描述这种语言。解析器将使用下推自动机实现。您将使用“解析堆栈”。并且此堆栈将保存所有其他信息。那就是正则表达式所没有的记忆。
我认为,这种方法足以满足您的目的
现在,如何实现它。有几种选择:
如果从手工解析器开始,您将学到最多并了解解析是如何工作的。我将在解释中继续说明。
标准解决方案是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;
}
我希望这能给您第一印象。