我正在编写一个带有错误处理的解析器。我想将无法解析的输入部分的确切位置输出给用户。
但是,错误标记的位置总是从0开始,即使在成功解析错误令牌之前也是如此。
下面是我所做的一个非常简单的例子。(有问题的部分可能在parser.yy中。)
Location.hh
#pragma once
#include <string>
// The full version tracks position in bytes, line number and offset in the current line.
// Here however, I've shortened it to line number only.
struct Location
{
int beginning, ending;
operator std::string() const { return std::to_string(beginning) + '-' + std::to_string(ending); }
};LexerClass.hh
#pragma once
#include <istream>
#include <string>
#if ! defined(yyFlexLexerOnce)
#include <FlexLexer.h>
#endif
#include "Location.hh"
class LexerClass : public yyFlexLexer
{
int currentPosition = 0;
protected:
std::string *yylval = nullptr;
Location *yylloc = nullptr;
public:
LexerClass(std::istream &in) : yyFlexLexer(&in) {}
[[nodiscard]] int yylex(std::string *const lval, Location *const lloc);
void onNewLine() { yylloc->beginning = yylloc->ending = ++currentPosition; }
};lexer.ll
%{
#include "./parser.hh"
#include "./LexerClass.hh"
#undef YY_DECL
#define YY_DECL int LexerClass::yylex(std::string *const lval, Location *const lloc)
%}
%option c++ noyywrap
%option yyclass="LexerClass"
%%
%{
yylval = lval;
yylloc = lloc;
%}
[[:blank:]] ;
\n { onNewLine(); }
[0-9] { return yy::Parser::token::DIGIT; }
. { return yytext[0]; }parser.yy
%language "c++"
%code requires {
#include "LexerClass.hh"
#include "Location.hh"
}
%define api.parser.class {Parser}
%define api.value.type {std::string}
%define api.location.type {Location}
%parse-param {LexerClass &lexer}
%defines
%code {
template<typename RHS>
void calcLocation(Location ¤t, const RHS &rhs, const int n);
#define YYLLOC_DEFAULT(Cur, Rhs, N) calcLocation(Cur, Rhs, N)
#define yylex lexer.yylex
}
%token DIGIT
%%
numbers:
%empty
| numbers number ';' { std::cout << std::string(@number) << "\tnumber" << std::endl; }
| error ';' { yyerrok; std::cerr << std::string(@error) << "\terror context" << std::endl; }
;
number:
DIGIT {}
| number DIGIT {}
;
%%
#include <iostream>
template<typename RHS>
inline void calcLocation(Location ¤t, const RHS &rhs, const int n)
{
current = (n <= 1)
? YYRHSLOC(rhs, n)
: Location{YYRHSLOC(rhs, 1).beginning, YYRHSLOC(rhs, n).ending};
}
void yy::Parser::error(const Location &location, const std::string &message)
{
std::cout << std::string(location) << "\terror: " << message << std::endl;
}
int main()
{
LexerClass lexer(std::cin);
yy::Parser parser(lexer);
return parser();
}供投入:
123
456
789;
123;
089
xxx
123;
765
432;预期产出:
0-2 number
3-3 number
5-5 error: syntax error
4-6 error context
7-8 number实际产出:
0-2 number
3-3 number
5-5 error: syntax error
0-6 error context
7-8 number发布于 2022-02-10 17:50:05
我是建立在rici's answer的基础上的,所以先读一遍。
让我们考虑一下规则:
numbers:
%empty
| numbers number ';'
| error ';' { yyerrok; }
;这意味着非终端numbers可以是以下三种方式之一:
它可能是空的,可能是空的,可能是一个numbers.
number,前面有任何有效的,可能是一个error.你看到问题了吗?从一开始,整个numbers就必须是一个error;没有任何规则规定在它之前允许任何其他的东西。当然,Bison顺从地遵从您的愿望,并使error从非终端numbers的最开始开始。它可以做到这一点,因为error是所有交易中的一员,对于其中包含的内容没有任何规定。Bison为了实现您的规则,需要将error扩展到以前的所有numbers之上。
当你理解这个问题时,解决它是相当容易的。你只需要告诉Bison,numbers在error之前是被允许的
numbers:
%empty
| numbers number ';'
| numbers error ';' { yyerrok; }
;这是海事组织最好的解决办法。不过,还有另一种方法。
可以将error令牌移动到number
numbers:
%empty
| numbers number ';' { yyerrok; }
;
number:
DIGIT
| number DIGIT
| error
;请注意,yyerrok需要留在numbers中,因为如果将解析器放置在以令牌error结尾的规则旁边,解析器将进入无限循环。
这种方法的一个缺点是,如果您在此error旁边放置一个操作,它将被多次触发(每个非法终端或多或少会触发一次)。也许在某些情况下,这是比较可取的,但通常我建议使用第一种方法来解决这个问题。
发布于 2022-02-09 20:29:38
下面是您的numbers规则,以供参考(没有操作,因为它们并不真正相关):
numbers:
%empty
| numbers number ';'
| error ';' numbers也是您的开始符号。应该相当清楚的是,在任何派生过程中,在numbers非终端之前没有任何东西。有一个顶级的numbers非终端,它包含整个输入,它从一个numbers非终端开始,它包含除了最后一个number ;之外的所有东西,等等。所有这些numbers都从一开始就开始了。
类似地,error伪令牌处于某些numbers派生的开始。因此,它也必须从输入的开头开始。
换句话说,您的语句“错误标记的位置总是从0开始,即使在成功解析之前也是如此”,这是不可测试的。错误标记的位置总是从0开始,因为在它之前没有任何东西,并且您正在接收的输出是“预期的”。或者,至少是可以预测的;我知道你并没有预料到这一点,而且很容易陷入混乱。直到我运行启用跟踪的解析器时,我才真正看到它,这是强烈推荐的;请注意,要这样做,添加std::operator(ostream&, Location const&)的重载是有帮助的。
https://stackoverflow.com/questions/71040349
复制相似问题