错误标记的位置始终从 0 开始
Location of the error token always starts at 0
我正在编写一个带有错误处理的解析器。我想向用户输出无法解析的输入部分的确切位置。
但是,错误标记的位置始终从 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
这是您的 numbers
规则,供参考(没有操作,因为它们并不真正相关):
numbers:
%empty
| numbers number ';'
| error ';'
numbers
也是您的开始符号。应该相当清楚的是,在任何推导中都没有 before a numbers
non-terminal。有一个 top-level numbers
non-terminal,它包含整个输入,它以 numbers
non-terminal 开头,它包含除最后一个 [=16= 之外的所有内容] ;
,等等。所有这些numbers
从头开始。
同样,error
伪令牌处于某些 numbers
推导的开始。所以它也必须从输入的开头开始。
换句话说,你所说的“错误标记的位置总是从 0 开始,即使之前它是被成功解析的部分”是无法测试的。错误标记的位置始终从 0 开始,因为它之前不能有任何内容,并且您收到的输出是“预期的”。或者,至少是可预测的;我知道您没有预料到它,而且很容易陷入这种困惑。直到我 运行 启用了跟踪的解析器,我才真正看到它,这是强烈推荐的;请注意,这样做有助于添加 std::operator(ostream&, Location const&)
.
的重载
我是在 的基础上构建的,所以请先阅读那个。
让我们考虑规则:
numbers:
%empty
| numbers number ';'
| error ';' { yyerrok; }
;
这意味着非终结符 numbers
可以是以下三种之一:
- 可能是空的。
- 它可以是
number
前面有任何有效的 numbers
。
- 可能是
error
。
你看到问题了吗?
整个numbers
必须是一个error
,从头开始;没有规则说在它之前允许做任何其他事情。
Bison当然乖乖的遵从你的意愿,让error
从非终结符numbers
的开头开始。
它可以做到这一点,因为 error
是所有行业的杰作,并且没有关于它的 内部 可以包含什么的规则。 Bison,为了满足您的规则,需要将 error
扩展到所有之前的 numbers
。
当您了解问题所在时,解决它就很容易了。您只需要告诉 Bison 在 error
:
之前允许 numbers
numbers:
%empty
| numbers number ';'
| numbers error ';' { yyerrok; }
;
这是 IMO 的最佳解决方案。不过还有另一种方法。
您可以将 error
令牌移动到 number
:
numbers:
%empty
| numbers number ';' { yyerrok; }
;
number:
DIGIT
| number DIGIT
| error
;
请注意 yyerrok
需要保留在 numbers
中,因为如果将它放在以标记 error
.[=33 结尾的规则旁边,解析器将进入无限循环=]
这种方法的一个缺点是,如果您在此 error
旁边放置一个动作,它将被触发多次(每个非法终端或多或少触发一次)。
也许在某些情况下这是更可取的,但通常我建议使用第一种方法来解决问题。
我正在编写一个带有错误处理的解析器。我想向用户输出无法解析的输入部分的确切位置。
但是,错误标记的位置始终从 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
这是您的 numbers
规则,供参考(没有操作,因为它们并不真正相关):
numbers:
%empty
| numbers number ';'
| error ';'
numbers
也是您的开始符号。应该相当清楚的是,在任何推导中都没有 before a numbers
non-terminal。有一个 top-level numbers
non-terminal,它包含整个输入,它以 numbers
non-terminal 开头,它包含除最后一个 [=16= 之外的所有内容] ;
,等等。所有这些numbers
从头开始。
同样,error
伪令牌处于某些 numbers
推导的开始。所以它也必须从输入的开头开始。
换句话说,你所说的“错误标记的位置总是从 0 开始,即使之前它是被成功解析的部分”是无法测试的。错误标记的位置始终从 0 开始,因为它之前不能有任何内容,并且您收到的输出是“预期的”。或者,至少是可预测的;我知道您没有预料到它,而且很容易陷入这种困惑。直到我 运行 启用了跟踪的解析器,我才真正看到它,这是强烈推荐的;请注意,这样做有助于添加 std::operator(ostream&, Location const&)
.
我是在
让我们考虑规则:
numbers:
%empty
| numbers number ';'
| error ';' { yyerrok; }
;
这意味着非终结符 numbers
可以是以下三种之一:
- 可能是空的。
- 它可以是
number
前面有任何有效的numbers
。 - 可能是
error
。
你看到问题了吗?
整个numbers
必须是一个error
,从头开始;没有规则说在它之前允许做任何其他事情。
Bison当然乖乖的遵从你的意愿,让error
从非终结符numbers
的开头开始。
它可以做到这一点,因为 error
是所有行业的杰作,并且没有关于它的 内部 可以包含什么的规则。 Bison,为了满足您的规则,需要将 error
扩展到所有之前的 numbers
。
当您了解问题所在时,解决它就很容易了。您只需要告诉 Bison 在 error
:
numbers
numbers:
%empty
| numbers number ';'
| numbers error ';' { yyerrok; }
;
这是 IMO 的最佳解决方案。不过还有另一种方法。
您可以将 error
令牌移动到 number
:
numbers:
%empty
| numbers number ';' { yyerrok; }
;
number:
DIGIT
| number DIGIT
| error
;
请注意 yyerrok
需要保留在 numbers
中,因为如果将它放在以标记 error
.[=33 结尾的规则旁边,解析器将进入无限循环=]
这种方法的一个缺点是,如果您在此 error
旁边放置一个动作,它将被触发多次(每个非法终端或多或少触发一次)。
也许在某些情况下这是更可取的,但通常我建议使用第一种方法来解决问题。