这仍然是一个猜测,因为没有迹象表明扫描器是如何工作的,或者它的值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_;
};
并假设 aToken
包括一个const char*
成员(而不是 a 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.
将是 a END
,而不是 a NEWLINE
,因此相应的标记字符串可能是空的,而不是换行符是合理的。
显然,如果上述所有推测都是有效的,则解决方案是制作令牌字符串的副本,例如通过在对象中替换const char* str_;
为。在这种情况下,将构造函数替换为构造函数,甚至是简单的构造函数是合理的,从而避免了使用.std::string str_;
Token
const char*
const std::string&
std::string
std::string::c_str()