在没有削减的情况下解析Prolog?

时间:2011-06-15 13:37:30

标签: parsing lisp prolog dcg prolog-cut

我在Prolog中找到了解析lisp的好片段(来自here):

ws --> [W], { code_type(W, space) }, ws.
ws --> [].

parse(String, Expr) :- phrase(expressions(Expr), String).

expressions([E|Es]) -->
    ws, expression(E), ws,
    !, % single solution: longest input match
    expressions(Es).
expressions([]) --> [].

% A number N is represented as n(N), a symbol S as s(S).

expression(s(A))         --> symbol(Cs), { atom_codes(A, Cs) }.
expression(n(N))         --> number(Cs), { number_codes(N, Cs) }.
expression(List)         --> "(", expressions(List), ")".
expression([s(quote),Q]) --> "'", expression(Q).

number([D|Ds]) --> digit(D), number(Ds).
number([D])    --> digit(D).

digit(D) --> [D], { code_type(D, digit) }.

symbol([A|As]) -->
    [A],
    { memberchk(A, "+/-*><=") ; code_type(A, alpha) },
    symbolr(As).

symbolr([A|As]) -->
    [A],
    { memberchk(A, "+/-*><=") ; code_type(A, alnum) },
    symbolr(As).
symbolr([]) --> [].

然而,表达式使用剪切。我假设这是为了提高效率。是否可以编写此代码,以便在不切割的情况下高效工作?

也会有感兴趣的答案涉及Mercury的软切/承诺选择。

3 个答案:

答案 0 :(得分:8)

你在这里触及一个非常深刻的问题。在切割的地方你有 添加了评论“最长的输入匹配”。但你实际做的是提交 到第一个解决方案,它将为非终端ws//0产生“最长输入匹配”,但不一定是expression//1

许多编程语言根据最长的输入匹配来定义其令牌。这通常会导致非常奇怪的效果。例如,数字可以是立即的 接着是许多编程语言的一封信。帕斯卡,哈斯克尔就是这种情况, Prolog和许多其他语言。例如。 if a>2then 1 else 2是有效的Haskell。 有效的Prolog:X is 2mod 3.

鉴于此,定义一种编程语言可能是一个好主意,因此根本不依赖于这些特性。

当然,您希望优化语法。但我只能建议首先明确一个明确的定义。

关于效率(和纯度):

eos([],[]).

nows --> call(eos).
nows, [W] --> [W], { code_type(W, nospace) }.

ws --> nows.
ws --> [W], {code_type(W, space)}, ws.

答案 1 :(得分:8)

剪切不是用于提高效率,而是用于提交第一个解决方案(请参阅!/ 0旁边的注释:“单一解决方案:最长输入匹配”)。如果您注释掉!/ 0,则可以获得例如:

?- parse("abc", E).
E = [s(abc)] ;
E = [s(ab), s(c)] ;
E = [s(a), s(bc)] ;
E = [s(a), s(b), s(c)] ;
false.

很明显,在这种情况下,只需要由形成令牌的最长字符序列组成的第一个解决方案。鉴于上面的例子,我因此不同意“假”:表达式// 1是不明确的,因为数字// 1和符号// 1是。在Mercury中,您可以使用确定性声明cc_nondet来提交解决方案(如果有的话)。

答案 2 :(得分:4)

您可以使用已在Parsing Expression Grammars(PEGs)中找到其位置但在DCG中也可用的构建体。即对DCG目标的否定。在PEG中,带有参数的感叹号(!)用于否定,即!即在DCG中,DCG目标的否定由(\ +)运算符表示,该运算符已用于普通否定,因为普通Prolog子句和查询失败。

首先让我们解释一下(\ +)在DCG中是如何工作的。如果你有一个生产规则 形式:

 A --> B, \+C, D.

然后将其翻译为:

 A(I,O) :- B(I,X), \+ C(X,_), D(X,O).

这意味着尝试解析C DCG目标,但实际上没有消耗输入列表。现在,如果需要,这可以用来代替切割,并且它给出了一点声明的感觉。为了解释这个想法,我们假设有一个没有ws // 0的语法。因此表达式// 1的原始子句集将是:

expressions([E|Es]) --> expression(E), !, expressions(Es).
expressions([]) --> [].

通过否定,我们可以将其转换为以下无格式形式:

expressions([E|Es]) --> expression(E), expressions(Es).
expressions([]) --> \+ expression(_).

不幸的是,上述变体非常无效,因为尝试解析表达式的次数是两次。一旦进入第一条规则,然后再进入第二条规则进行否定。但是你可以做以下事情,只检查表达式开头的否定:

expressions([E|Es]) --> expression(E), expressions(Es).
expressions([]) --> \+ symbol(_), \+ number(_), \+ "(", \+ "'".

如果你尝试否定,你会发现你得到了一个相对严格的解析器。如果您尝试解析输入的最大前缀以及是否要检测某些错误,这一点很重要。试试这个:

?- phrase(expressions(X),"'",Y).

你应该在否定版本中检查表达式的第一个符号。在剪切和剪切免费版本中,您将获得空列表的成功。

但你也可以用另一种方式处理错误,我只做了一个错误的例子,突出了一下否定版本的工作原理。

在其他设置中,例如CYK解析器,可以使得否定非常有效,它可以使用已经放置在图表中的信息。

最好的问候