柠檬 + re2c 没有得到正确的规则解析
lemon + re2c not getting correct rule resolution
这是我的柠檬解析器语法
%nonassoc IMPLICATION.
%nonassoc PERIOD.
%nonassoc NEWLINE.
%nonassoc END.
%nonassoc STRING.
program ::= in END.
in ::= .
in ::= in rule NEWLINE.
in ::= in rule.
rule ::= STRING(A) IMPLICATION STRING(B) PERIOD. {cout<<A->token<<endl; cout<<B->token<<endl;}
我的输入字符串是
p<-body1.
q<-body3.
我希望输出为
p
body1
q
body3
但我得到的输出是
q
q
\n (Empty line here)
\n (Empty line here)
我确定我正在以正确的顺序传递标记,并且我已经验证了这一点,因为解析器会因错误的输入而抛出 syntax/parser 错误。
这是我用来将标记传递给解析器的代码
do
{
token = lexer.scan(); // returns an int with the type of token
Token* t = new Token(lexer.getTokenValue().c_str());
lpmlnParse(pParser, token, t);
}while(token != PARSE_TOKEN_END);
我不知道出了什么问题。有人能指出我正确的方向吗?
这仍然是一个猜测,因为没有说明扫描器是如何工作的,或者 lexer.getTokenValue()
的值是什么,或者 Token
构造函数如何使用它的参数。
但是让我们假设 lexer
对象包含一个私有 std::string
成员,在每个标记被扫描后分配给匹配的文本:
struct lexer {
// ...
int scan() {
int toke;
const char* start = current_;
/* re2c stuff */
tstring_.assign(start, current_ - start);
return toke;
}
const std::string& getTokenValue() const {
return tstring_;
}
std::string tstring_;
const char* current_;
};
并假设 Token
包含 const char*
成员(而不是 std::string
):
struct Token {
explicit Token(const char* s) : str_(s) {}
const char* str_;
}
这至少可以解释观察到的行为。
对 lexer.scan()
的每次连续调用都会覆盖 tstring_
的内容。 (在一般情况下,std::string::assign
可能会重新分配内部字符数组,但由于现代 C++ 库使用短字符串优化,并且示例代码中的所有标记都是短字符串,因此这里不会发生这种情况。)
因为 std::string::c_str
和 Token
构造函数都不复制字符,最终结果是新创建的 Token
有一个指向可变内部缓冲区的指针将随着扫描的进行而被覆盖(或更糟,被删除)。
因此,Token
的字符串值在缩减操作中观察到与首次创建 Token
时不同。
这仍然不足以解释为什么 q
是由可能减少 p->body1.
.
的规则打印的
与 bison
不同,lemon
解析器不会尝试优化前瞻。 bison
-生成的解析器将在请求前瞻令牌之前执行归约,如果不需要先行令牌来决定是减少还是移动。相反,lemon
生成的解析器仅在前瞻标记可用时才减少。在这种情况下,生产 rule ::= STRING(A) IMPLICATION STRING(B) PERIOD.
的减少不依赖于 PERIOD
之后的标记,但柠檬解析器仍将等待下一个标记。
从语法来看,人们可能认为下一个标记是 NEWLINE
,但在这种情况下,输出应该显示两个换行符(或四个空行,因为语义操作也会打印一个换行符)。由于情况并非如此,我们可以推测词法分析器正在跳过换行符而不是返回 NEWLINE
标记。如果是这种情况,语法仍然有效,因为 NEWLINE
标记是可选的(in rule
和 in rule NEWLINE
都是有效的右侧)。那么先行标记将是以下 STRING
标记,即 q
。 q->body3.
之后的前瞻标记将是 END
,而不是 NEWLINE
,因此相应的标记字符串可能为空,而不是换行符。
显然,如果以上所有推测都有效,解决方案是复制令牌字符串,例如将 [=13= 中的 const char* str_;
替换为 std::string str_;
] 目的。在这种情况下,将 const char*
构造函数替换为 const std::string&
构造函数甚至是简单的 std::string
构造函数是合理的,从而避免使用 std::string::c_str()
的必要性。
这是我的柠檬解析器语法
%nonassoc IMPLICATION.
%nonassoc PERIOD.
%nonassoc NEWLINE.
%nonassoc END.
%nonassoc STRING.
program ::= in END.
in ::= .
in ::= in rule NEWLINE.
in ::= in rule.
rule ::= STRING(A) IMPLICATION STRING(B) PERIOD. {cout<<A->token<<endl; cout<<B->token<<endl;}
我的输入字符串是
p<-body1.
q<-body3.
我希望输出为
p
body1
q
body3
但我得到的输出是
q
q
\n (Empty line here)
\n (Empty line here)
我确定我正在以正确的顺序传递标记,并且我已经验证了这一点,因为解析器会因错误的输入而抛出 syntax/parser 错误。
这是我用来将标记传递给解析器的代码
do
{
token = lexer.scan(); // returns an int with the type of token
Token* t = new Token(lexer.getTokenValue().c_str());
lpmlnParse(pParser, token, t);
}while(token != PARSE_TOKEN_END);
我不知道出了什么问题。有人能指出我正确的方向吗?
这仍然是一个猜测,因为没有说明扫描器是如何工作的,或者 lexer.getTokenValue()
的值是什么,或者 Token
构造函数如何使用它的参数。
但是让我们假设 lexer
对象包含一个私有 std::string
成员,在每个标记被扫描后分配给匹配的文本:
struct lexer {
// ...
int scan() {
int toke;
const char* start = current_;
/* re2c stuff */
tstring_.assign(start, current_ - start);
return toke;
}
const std::string& getTokenValue() const {
return tstring_;
}
std::string tstring_;
const char* current_;
};
并假设 Token
包含 const char*
成员(而不是 std::string
):
struct Token {
explicit Token(const char* s) : str_(s) {}
const char* str_;
}
这至少可以解释观察到的行为。
对 lexer.scan()
的每次连续调用都会覆盖 tstring_
的内容。 (在一般情况下,std::string::assign
可能会重新分配内部字符数组,但由于现代 C++ 库使用短字符串优化,并且示例代码中的所有标记都是短字符串,因此这里不会发生这种情况。)
因为 std::string::c_str
和 Token
构造函数都不复制字符,最终结果是新创建的 Token
有一个指向可变内部缓冲区的指针将随着扫描的进行而被覆盖(或更糟,被删除)。
因此,Token
的字符串值在缩减操作中观察到与首次创建 Token
时不同。
这仍然不足以解释为什么 q
是由可能减少 p->body1.
.
与 bison
不同,lemon
解析器不会尝试优化前瞻。 bison
-生成的解析器将在请求前瞻令牌之前执行归约,如果不需要先行令牌来决定是减少还是移动。相反,lemon
生成的解析器仅在前瞻标记可用时才减少。在这种情况下,生产 rule ::= STRING(A) IMPLICATION STRING(B) PERIOD.
的减少不依赖于 PERIOD
之后的标记,但柠檬解析器仍将等待下一个标记。
从语法来看,人们可能认为下一个标记是 NEWLINE
,但在这种情况下,输出应该显示两个换行符(或四个空行,因为语义操作也会打印一个换行符)。由于情况并非如此,我们可以推测词法分析器正在跳过换行符而不是返回 NEWLINE
标记。如果是这种情况,语法仍然有效,因为 NEWLINE
标记是可选的(in rule
和 in rule NEWLINE
都是有效的右侧)。那么先行标记将是以下 STRING
标记,即 q
。 q->body3.
之后的前瞻标记将是 END
,而不是 NEWLINE
,因此相应的标记字符串可能为空,而不是换行符。
显然,如果以上所有推测都有效,解决方案是复制令牌字符串,例如将 [=13= 中的 const char* str_;
替换为 std::string str_;
] 目的。在这种情况下,将 const char*
构造函数替换为 const std::string&
构造函数甚至是简单的 std::string
构造函数是合理的,从而避免使用 std::string::c_str()
的必要性。