【发布时间】:2015-09-01 16:26:44
【问题描述】:
假设我想标记一串由空格分隔的单词(符号)和数字。例如,标记"aa 11" 的预期结果将是[tkSym("aa"), tkNum(11)]。
我的第一次尝试是下面的代码:
whitespace --> [Ws], { code_type(Ws, space) }, whitespace.
whitespace --> [].
letter(Let) --> [Let], { code_type(Let, alpha) }.
symbol([Sym|T]) --> letter(Sym), symbol(T).
symbol([Sym]) --> letter(Sym).
digit(Dg) --> [Dg], { code_type(Dg, digit) }.
digits([Dg|Dgs]) --> digit(Dg), digits(Dgs).
digits([Dg]) --> digit(Dg).
token(tkSym(Token)) --> symbol(Token).
token(tkNum(Token)) --> digits(Digits), { number_chars(Token, Digits) }.
tokenize([Token|Tokens]) --> whitespace, token(Token), tokenize(Tokens).
tokenize([]) --> whitespace, [].
在"aa bb" 上调用tokenize 给我留下了几个可能的回应:
?- tokenize(X, "aa bb", []).
X = [tkSym([97|97]), tkSym([98|98])] ;
X = [tkSym([97|97]), tkSym(98), tkSym(98)] ;
X = [tkSym(97), tkSym(97), tkSym([98|98])] ;
X = [tkSym(97), tkSym(97), tkSym(98), tkSym(98)] ;
false.
然而,在这种情况下,只期望一个正确答案似乎是合适的。这是另一种更具确定性的方法:
whitespace --> [Space], { char_type(Space, space) }, whitespace.
whitespace --> [].
symbol([Sym|T]) --> letter(Sym), !, symbol(T).
symbol([]) --> [].
letter(Let) --> [Let], { code_type(Let, alpha) }.
% similarly for numbers
token(tkSym(Token)) --> symbol(Token).
tokenize([Token|Tokens]) --> whitespace, token(Token), !, tokenize(Tokens).
tokenize([]) --> whiteSpace, [].
但有一个问题:虽然在"aa" 上调用的token 的单一答案现在是一个不错的列表,但tokenize 谓词最终会无限递归:
?- token(X, "aa", []).
X = tkSym([97, 97]).
?- tokenize(X, "aa", []).
ERROR: Out of global stack
我错过了什么?这个问题在 Prolog 中通常是如何解决的?
【问题讨论】:
-
关于命名的一条评论:我建议命名为
tokens//1,这样你就有token//1和tokens//1,类似于digit//1和digits/1。这样,您可以避免使用命令式名称,这会暗示非终结符只能在一个方向上使用。tokens//1更具声明性,并且在其他方向需要非终结符时也有意义。 -
两个非常好的学习DCG的资源:第一,Markus Triska's DCG Primer;然后,查看作为 SWI-Prolog 源的一部分提供的(有点难找到)collection of generally useful DCG rules。两者都很小,包含很多非常有用的代码示例/配方。