使用DCG对Prolog中的字符串进行标记

时间:2015-08-31 20:02:01

标签: prolog dcg

让我们说我要标记一串由空格分隔的单词(符号)和数字。例如,标记"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, [].  

tokenize上拨打"aa bb"给我留下了几个可能的回复:

 ?- 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, [].

但是有一个问题:虽然token"aa"的单个答案现在是一个很好的列表,但tokenize谓词最终会无限递归:

 ?- token(X, "aa", []).
 X = tkSym([97, 97]).

 ?- tokenize(X, "aa", []).
 ERROR: Out of global stack

我错过了什么?如何在Prolog中解决问题?

1 个答案:

答案 0 :(得分:3)

潜在的问题是,在你的第二个版本中,token//1也成功了#34;空"令牌:

?- phrase(token(T), "").
T = tkSym([]).

因此,无意中,以下成功也是如此,任意数量的令牌也是如此:

?- phrase((token(T1),token(T2)), "").
T1 = T2, T2 = tkSym([]).

要解决此问题,我建议您调整定义,以便令牌必须至少包含一个词法元素,这也是典型的。确保至少描述一个元素的好方法是将DCG规则分成两组。例如,显示为symbol///1

symbol([L|Ls]) --> letter(L), symbol_r(Ls).

symbol_r([L|Ls]) --> letter(L), symbol_r(Ls).
symbol_r([])     --> []. 

这样,您可以避免无限制地使用空标记的无限递归。

其他要点:

始终使用phrase/2以便携方式访问DCG,即独立于任何特定Prolog系统使用的实际实现方法。

最终DCG子句中的[]是多余的,您只需删除它即可。

另外,请避免使用这么多!/0。可以提交第一个匹配的标记化,但只能在一个地方进行,例如通过围绕once/1调用的phrase/2

有关命名,请参阅上面的评论。我建议使用tokens//1来使其更具说明性。使用上述symbol//1定义的示例查询:

?- phrase(tokens(Ts), "").
Ts = [].

?- phrase(tokens(Ls), "a").
Ls = [tkSym([97])].

?- phrase(tokens(Ls), "a b").
Ls = [tkSym([97]), tkSym([98])].