我在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的软切/承诺选择。
答案 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解析器,可以使得否定非常有效,它可以使用已经放置在图表中的信息。
最好的问候