LR parser on Prolog

PrologでLR構文解析器を書いたので、自分のためのメモ代わりに。手続き的であまり論理型言語っぽくない書き方だけど。

lr(Ws):- start_state(S), lr([S],Ws,[],_).
lr(Ws,Out):- start_state(S), lr([S],Ws,[],Out).

lr(Ss,Ws,Os,Out):-
  Ss=[S|_], Ws=[W|Ws0],
  entry(S,W,Op),
  ( Op=sh(Sn), lr([Sn|Ss],Ws0,Os,Out)
  ; Op=re(R), reduce(R,Ss,Ss1), lr(Ss1,Ws,[R|Os],Out)
  ; Op==accept, Out=Os
  ).

pop(0,S,S).
pop(N,[_|S],Xs):-
  M is N-1, pop(M,S,Xs).

reduce(R,Ss,[S1|Ss0]):-
  rule(R,LHS,RHS),
  length(RHS,Len),
  pop(Len,Ss,Ss0),
  Ss0=[S0|_],
  entry(S0,LHS,go(S1)).

導出規則とLR表の例。

(1)  s -> np vp
(2) np -> np pp
(3) np -> det n
(4) np -> n
(5) np -> pron
(6) vp -> v np
(7) vp -> vp pp
(8) pp -> p np


  |det| n |   p   |pron| v | $ | np| pp| s | vp|
 0|sh1|sh5|       | sh2|   |   |  4|   |  3|   |
 1|   |sh6|       |    |   |   |   |   |   |   |
 2|   |   |  re5  |    |re5|re5|   |   |   |   |
 3|   |   |       |    |   |acc|   |   |   |   |
 4|   |   |  sh7  |    |sh8|   |   | 10|   |  9|
 5|   |   |  re4  |    |re4|re4|   |   |   |   |
 6|   |   |  re3  |    |re3|re3|   |   |   |   |
 7|sh1|sh5|       | sh2|   |   | 11|   |   |   |
 8|sh1|sh5|       | sh2|   |   | 12|   |   |   |
 9|   |   |  sh7  |    |   |re1|   | 13|   |   |
10|   |   |  re2  |    |   |re2|   |   |   |   |
11|   |   |sh7/re8|    |   |re8|   | 10|   |   |
12|   |   |sh7/re6|    |   |re6|   | 10|   |   |
13|   |   |  re7  |    |   |re7|   | 10|   |   |

述語で書き下すとこうなる。

rule(1,s,[np,vp]).
rule(2,np,[np,pp]).
rule(3,np,[det,n]).
rule(4,np,[n]).
rule(5,np,[pron]).
rule(6,vp,[v,np]).
rule(7,vp,[vp,pp]).
rule(8,pp,[p,np]).

start_state(0).

entry(0,det,sh(1)).
entry(0,n,sh(5)).
entry(0,pron,sh(2)).
entry(0,np,go(4)).
entry(0,s,go(3)).
entry(1,n,sh(6)).
entry(2,p,re(5)).
entry(2,v,re(5)).
entry(2,$,re(5)).
entry(3,$,accept).
entry(4,p,sh(7)).
entry(4,v,sh(8)).
entry(4,pp,go(10)).
entry(4,vp,go(9)).
entry(5,p,re(4)).
entry(5,v,re(4)).
entry(5,$,re(4)).
entry(6,p,re(3)).
entry(6,v,re(3)).
entry(6,$,re(3)).
entry(7,det,sh(1)).
entry(7,n,sh(5)).
entry(7,pron,sh(2)).
entry(7,np,go(11)).
entry(8,det,sh(1)).
entry(8,n,sh(5)).
entry(8,pron,sh(2)).
entry(8,np,go(12)).
entry(9,p,sh(7)).
entry(9,$,re(1)).
entry(9,pp,go(13)).
entry(10,p,re(2)).
entry(10,v,re(2)).
entry(10,$,re(2)).
entry(11,p,sh(7)).
entry(11,p,re(8)).
entry(11,v,re(8)).
entry(11,$,re(8)).
entry(11,pp,go(10)).
entry(12,p,sh(7)).
entry(12,p,re(6)).
entry(12,$,re(6)).
entry(12,pp,go(10)).
entry(13,p,re(7)).
entry(13,$,re(7)).
entry(13,pp,go(10)).

こんな感じで動作確認。

| ?- lr([pron,v,det,n,p,det,n,$]).
yes
| ?- lr([pron,v,det,n,p,det,n,$],Out).
Out = [1,6,2,8,3,3,5] ? ;
Out = [1,7,8,3,6,3,5] ? ;
no

文長の長さのリストを用いるので実際には使い物にならないはず。一応Prologのバックトラックでgeneralized LR parserっぽくなってるけど、非効率だし。Prolog上で効率的にGLRを扱いたいのであれば、SGLR : 逐次型一般化LRパーザのPrologによる実現(pdf)を読んだ方がいい。ちなみに上の導出規則とLR表もこちらから。

最初のやつを少し簡潔に書くとこんな感じ。

lr(Ws):- start_state(S), lr([S],Ws,[],_).
lr(Ws,Out):- start_state(S), lr([S],Ws,[],Out).

lr([S|Ss0],[W|Ws0],Os,Out):-
  entry(S,W,sh(Sn)),
  lr([Sn,S|Ss0],Ws0,Os,Out).
lr([S|Ss0],[W|Ws0],Os,Out):-
  entry(S,W,re(R)),
  rule(R,LHS,RHS),
  length(RHS,Len),
  pop(Len,[S|Ss0],[S0|Ss2]),
  entry(S0,LHS,go(Sg)),
  lr([Sg,S0|Ss2],[W|Ws0],[R|Os],Out).
lr([S|_],[W|_],Os,Out):-
  entry(S,W,accept),
  Out=Os.

pop(0,S,S).
pop(N,[_|S],Xs):-
  M is N-1, pop(M,S,Xs).