我有一个非常适合我的目的的野牛解析器。它甚至打印本地化的错误消息。但令牌名称未翻译。查看我发现的源代码,我可以将YY_
定义为我自己的gettext函数,并将YY_
传递给gettext,以便提供我自己的错误消息翻译。但这不适用于令牌名称。
是否有一些切换或隐藏功能可用于从解析器中提取令牌名称并进行翻译?
到目前为止,我发现可以覆盖yytnamerr
来格式化令牌名称。因为它不仅仅重新格式化名称,我不喜欢触摸这个功能,因为我必须将它与Bison的进展同步。另一方面,我还需要一种从解析器中提取令牌名称的方法,以便将它们添加到语言定义文件中。
如何使用Bison实现用户友好的错误报告?
答案 0 :(得分:0)
如果指定%token-table
,则bison将生成yytname
表。该表包括所有野牛符号,包括内部符号($end
,$error
和$undefined
),终端 - 命名,单引号字符和双引号字符串 - 以及非终端符号,其中还包括为中规则操作生成的名称。
如果yytname
可见,则可以使用gettext
包可识别的格式轻松提取令牌。例如,您可以在.y
文件中添加如下内容:
#ifdef MAKE_TOKEN
int main(void) {
puts("#include <libintl.h>");
puts("#include <stdio.h>");
puts("int main() {");
for (const char* const* p = yytname; *p; ++p) {
// See Note 1 below
printf(" printf(\"%%s: %%s\\n\", \"%s\", gettext (\"%s\"));\n", *p, *p);
}
puts("}");
}
#endif
然后将一个节添加到Makefile中(对文件名进行适当的替换):
messages.pot: my_parser.c
$(CC) $(CFLAGS) -DMAKE_TOKEN -o token_lister $<
./token_lister > my_parser.tokens.c
# See Note 2 below
$(CC) -o my_parser.tokens my_parser.tokens.c
xgettext -o $@ my_parser.tokens.c
一旦你有翻译,你仍然需要弄清楚如何使用它们,因为bison不提供用于将翻译的令牌名称插入其生成的错误消息的接口。可能最简单的方法是通过迭代该数组并将每个标记名称替换为其翻译(这必须在解析器启动时完成),将翻译直接插入yytname
。这表示野牛骨架yytname
被宣布为const
的烦恼;但是,可以使用非常简单的sed
或awk
调用来删除有问题的const
。 [注3]
话虽如此,我并不清楚这些自动生成的错误消息是“用户友好的”,除非用户对语言的正式语法非常熟悉。并且熟悉语法的用户可能更喜欢原始令牌名称,以便在语法中找到它,而不是仅仅巧合地类似于原始概念的非专家翻译。并不是说我特别指责任何人。
你可能会喜欢Russ Cox的这个fascinating essay,关于他如何为Go实现友好的错误消息。
备注强>:
如果令牌的表示包含"
或\
,则在C字符串中直接使用令牌名称将不起作用。特别是,任何关键字令牌("and"
或"<="
)都会失败,单个字符标记'"'
和'\\'
也会失败。这些并不经常出现在语法中;如果您在扫描仪中替换国际化关键字,则根本不可能使用野牛引用的字符串功能。
如果您确实想使用此类令牌,则必须输出gettext生成器的代码,该代码生成器会转义令牌名称中的"
和\
个字符。
实际上,最好使用几个节,但我认为那个节足以让你前进。您可能希望将部分或全部中间结果标记为.INTERMEDIATE
。生成的可执行文件my_parser.tokens
可用于验证翻译,但这完全是可选的,因此您可能希望删除该行。另一方面,它确实验证了字符串是可编译的。
有关示例,请参阅Russ Cox的gc
(上面提供的链接)。他的Makefile修改了bison输出以从const
中删除yytname
,以便生成的解析器可以将其首选的令牌名称替换为错误消息,这样您就可以看到一般的想法。