首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >错误标记的位置总是从0开始。

错误标记的位置总是从0开始。
EN

Stack Overflow用户
提问于 2022-02-08 20:18:15
回答 2查看 79关注 0票数 0

我正在编写一个带有错误处理的解析器。我想将无法解析的输入部分的确切位置输出给用户。

但是,错误标记的位置总是从0开始,即使在成功解析错误令牌之前也是如此。

下面是我所做的一个非常简单的例子。(有问题的部分可能在parser.yy中。)

Location.hh

代码语言:javascript
复制
#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

代码语言:javascript
复制
#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

代码语言:javascript
复制
%{
    #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

代码语言:javascript
复制
%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 &current, 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 &current, 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();
}

供投入:

代码语言:javascript
复制
123
456
789;
123;
089
xxx
123;
765
432;

预期产出:

代码语言:javascript
复制
0-2 number
3-3 number
5-5 error: syntax error
4-6 error context
7-8 number

实际产出:

代码语言:javascript
复制
0-2 number
3-3 number
5-5 error: syntax error
0-6 error context
7-8 number
EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2022-02-10 17:50:05

我是建立在rici's answer的基础上的,所以先读一遍。

让我们考虑一下规则:

代码语言:javascript
复制
numbers:
      %empty
    | numbers number ';'
    | error ';' { yyerrok; }
    ;

这意味着非终端numbers可以是以下三种方式之一:

它可能是空的,可能是空的,可能是一个numbers.

  • It,它可能是一个number,前面有任何有效的,可能是一个error.

你看到问题了吗?从一开始,整个numbers就必须是一个error;没有任何规则规定在它之前允许任何其他的东西。当然,Bison顺从地遵从您的愿望,并使error从非终端numbers的最开始开始。它可以做到这一点,因为error是所有交易中的一员,对于其中包含的内容没有任何规定。Bison为了实现您的规则,需要将error扩展到以前的所有numbers之上。

当你理解这个问题时,解决它是相当容易的。你只需要告诉Bison,numberserror之前是被允许的

代码语言:javascript
复制
numbers:
      %empty
    | numbers number ';'
    | numbers error ';' { yyerrok; }
    ;

这是海事组织最好的解决办法。不过,还有另一种方法。

可以将error令牌移动到number

代码语言:javascript
复制
numbers:
      %empty
    | numbers number ';' { yyerrok; }
    ;

number:
      DIGIT
    | number DIGIT
    | error
    ;

请注意,yyerrok需要留在numbers中,因为如果将解析器放置在以令牌error结尾的规则旁边,解析器将进入无限循环。

这种方法的一个缺点是,如果您在此error旁边放置一个操作,它将被多次触发(每个非法终端或多或少会触发一次)。也许在某些情况下,这是比较可取的,但通常我建议使用第一种方法来解决这个问题。

票数 0
EN

Stack Overflow用户

发布于 2022-02-09 20:29:38

下面是您的numbers规则,以供参考(没有操作,因为它们并不真正相关):

代码语言:javascript
复制
numbers:
      %empty
    | numbers number ';'
    | error ';' 

numbers也是您的开始符号。应该相当清楚的是,在任何派生过程中,在numbers非终端之前没有任何东西。有一个顶级的numbers非终端,它包含整个输入,它从一个numbers非终端开始,它包含除了最后一个number ;之外的所有东西,等等。所有这些numbers都从一开始就开始了。

类似地,error伪令牌处于某些numbers派生的开始。因此,它也必须从输入的开头开始。

换句话说,您的语句“错误标记的位置总是从0开始,即使在成功解析之前也是如此”,这是不可测试的。错误标记的位置总是从0开始,因为在它之前没有任何东西,并且您正在接收的输出是“预期的”。或者,至少是可以预测的;我知道你并没有预料到这一点,而且很容易陷入混乱。直到我运行启用跟踪的解析器时,我才真正看到它,这是强烈推荐的;请注意,要这样做,添加std::operator(ostream&, Location const&)的重载是有帮助的。

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/71040349

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档