我正在解析具有功能定义的文件。由于函数可能是用多行编写的,所以我一直在解析,直到遇到;
:
#include <iostream>
#include <string>
void removeLineBreaks(std::string &str)
{
auto pos = str.find('\n');
while (pos != std::string::npos)
{
str.replace(pos, 1, "");
pos = str.find('\n', pos);
}
}
int main()
{
std::ifstream ifStream("a.pr");
std::string sLine;
const char sDelim(';');
while (std::getline(ifStream, sLine, sDelim))
{
sLine += sDelim;
removeLineBreaks(sLine);
// process further
}
}
文本可以是这样的:
a=f(b,c); // comment
d=f(e,f);
自从我读到;
以来,我得到了两篇文章:
a=f(b,c);
和
// comment
\n d=f(e,f);
。
如果我在第二部分调用removeLineBreaks
,它将变成// comment d=f(e,f);
,因此解析器将其视为注释。
我必须使用哪些选项才能使其正常工作?我可以考虑一下-在行上调用removeLineBreaks
之前,获取字符串直到\n
,如果它以//
开头,请从行中切下该部分,然后才调用{ {1}}。
还有其他想法吗?
答案 0 :(得分:0)
您首先需要从输入中删除//
注释,然后才可以在分号处进行拆分。
请考虑以下输入:
a=f(b,c); // Functions comments are not functions; a=F(b,c);
如果您首先分割分号,然后删除注释,那么您将获得两个功能:
a=f(b,c);
a=F(b,c);
但是您只想拥有第一个。
解决方案是:
//
注释以及所有换行符。这些步骤不必按顺序进行。您可以在输入的字符流上同时执行所有这些步骤,最后发出功能流。实际上,这就是真正的解析器会做的。
您实质上是在编写一个简单的解析器。随着您的语言变得越来越复杂,您将发现以这种方式解析文件越来越困难。例如,使用上述方法,将很难发出带有行号信息的错误消息。
如果要编写适当的解析器,我建议使用递归下降解析器以及PEG(解析器表达语法)。与其他方法相比,该方法易于学习,陷阱少,但对于计算机语言却非常强大。看到这里:https://en.wikipedia.org/wiki/Parsing_expression_grammar
警告:如果您听到有人建议flex
和bison
(或lex和yacc),我强烈建议您不要使用它们。它们使用起来很复杂,并且在解析内容和如何指定方面非常有限。我宁愿建议使用重量轻且现代的解析框架,例如PEGTL:https://github.com/taocpp/PEGTL。
答案 1 :(得分:-2)
这是一个技巧,但是C ++预处理程序会删除注释,因此您可以在解析定义之前调用它。对于gcc,它是g++ -E input.cpp
。 gcc预处理器将留下您必须处理的预处理注释,但是它们比一般的注释要容易得多。
@NathanOliver在评论中写道:
全行阅读,然后向后工作直到您点击;
如果您只需要处理函数声明和注释,那么这可能是最简单,最直接的选择。
如果您想要更严肃的东西(即实际格式实际上可能更复杂),那么正确的抽象就是将其视为一系列标记。您必须看一本好的编译器书的前几章以获取完整的摘要,但是如果我们可以假设您的文件仅包含声明和注释,则这是一种入门的简单方法。
您在这里关心的令牌有4类:空格,注释和代码以及分号。您可以通过前两个字符告诉您拥有哪个字符。
例如,如果这是您的输入:
a=f(b,
c); //comment
d=f(e,f);
然后我们将其分解为
a=f(b, // Code
\n // Whitespace
c) // Code
; // Semicolon
// Whitespace
//comment\n //Comment
d=f(e,f) // Code
; // Semicolon
(也可以使用其他方法来分解它,例如["a=f(b,", "c)", ";", "d=f(e,f)", ";"]
并跳过非代码内容。)
您可以编写一个函数,该函数将逐个字符地查找并返回您可以找到的该类别中最大的文本块。然后,您的“解析器”就可以重复调用此函数,丢弃注释和空格,并将声明拼接在一起。像lex
这样的工具可以为您做到这一点,尽管在这种情况下,学习lex可能和自己学习lex一样容易。