[英]Parsing in Prolog without cut?
我發現這個很好的片段用於解析Prolog中的lisp(從這里開始 ):
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的軟切/承諾選擇。
你在這里觸及一個非常深刻的問題。 在剪切的地方你添加了評論“最長的輸入匹配”。 但你實際上做的是提交第一個解決方案,它將產生非終端ws//0
的“最長輸入匹配”,但不一定是expression//1
。
許多編程語言基於最長輸入匹配來定義其令牌。 這通常會導致非常奇怪的效果。 例如,一個數字后面可能會緊跟許多編程語言中的字母。 Pascal,Haskell,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.
剪切不是用於提高效率,而是用於提交第一個解決方案(請參閱!/ 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提交解決方案(如果有)。
您可以使用已在Parsing Expression Grammars(PEG)中找到其位置的構造,但也可在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解析器,可以使得否定非常有效,它可以使用已經放置在圖表中的信息。
最好的祝福
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.