解析文本时在 DCG 中处理状态/上下文

问题描述

如何在解析文本时传递状态(并在需要时更改它)!?

https://www.metalevel.at/prolog/dcg

这个例子是在做计数..

不知道我应该如何通过初始状态。我是否必须将其作为调用参数或作为列表中的第一个元素? 每当我想使用 State 时,我是否必须将 state(S) 作为第一个目标?

======

假设我必须解析字符串“add item 5”。

如果状态是 "list" ,假设它应该打印 "5=>list"

如果“设置”则“5=>设置”

或者可能是更复杂的字符串“new list.add 5”...其中解析“new list”应该设置State=list,以便解析下一部分知道上下文并且解释为“add 5到列表中”与“将 5 个添加一个集合中”。

无需使用这些示例,我只是想弄清楚何时使用半上下文表示法以及如何在 first/top 规则中将上下文作为参数传递。

正如你所看到的,我很困惑,但这是我能在互联网上找到的唯一示例,并且序言文档一如既往地密集、简洁且不是很有帮助;(没有示例。


澄清:

sent([X,Y],Ctx) -->  make(X,Ctx),add(Y,Ctx).
make(V,Ctx) --> [make,V],{say(">make ~w |ctx: ~w",[V,Ctx])}.
add(V,_Ctx) --> [add,{say(">add ~w",[V])}.

在这个例子中,我在每个级别传递上下文,即使我没有在 add() 中使用它。 我可以为 add() 删除它,但如果有子规则,我必须保留它。

我知道使用状态线程只需要在顶级规则中声明 Ctx 并且在使用时声明

解决方法

DCG 是一种引起一些兴趣的解析方法,因为它们紧密而轻量级地集成到宿主语言 - 当然是 Prolog。确实如此轻量级,以至于实际上没有特定于包含在 DCG 子句中的解析,只有通过差异列表实现的(相当)有效的模式匹配。

如果使用 DCG 进行解析,则不能将状态串连到不同的子句中,因为差异列表用于匹配标记。

因此,请使用传统方式,在参数中手动传递状态,或切换到扩展 DCG - 例如,pack(edcg)。

,

也许这个例子有帮助。 我暂时不讨论半上下文技巧,稍后再讨论。

我们想要计算原子中 abc 的出现次数。任何其他字符都归入 dropped 类别(但也算在内)。

“状态”因此是 abcdropped 的当前计数。状态应使用映射实现,在本例中为 SWI-Prolog dict。

三种方法:

  1. foldl/4
  2. phrase/3
  3. 使用原版 Prolog

使用以下代码:

?-
count_with_foldl(abgdbabba,DictWithCounts).
DictWithCounts = counts{a:3,b:4,dropped:2}.

?- 
count_with_phrase(abgdbabba,dropped:2} ;
false.

?- 
count_with_recursion(abgdbabba,dropped:2}.

代码:

% ===
% Morph DictIn to DictOut so that:
% Only for Keys [a,b,c]:
% If Key exists in DictIn,DictOut is DictIn with the count for Key incremented
% If Key notexists in DictIn,DictOut is DictIn with a new entry Key with count 1
% ===

inc_for_key(Key,DictIn,DictOut) :-
   memberchk(Key,[a,c]),!,add_it(Key,DictOut).
   
inc_for_key(Key,DictOut) :-
   \+memberchk(Key,add_it(dropped,DictOut).

add_it(Key,DictOut) :-
   (get_dict(Key,Count) -> succ(Count,CountNew) ; CountNew=1),put_dict(Key,CountNew,DictOut).

% ===
% Using foldl to count
% ===

count_with_foldl(Atom,DictWithCounts) :-
   atom_chars(Atom,Chars),foldl(inc_for_key,Chars,counts{},DictWithCounts).

% ===
% Using a DCG to count
% ===

dcg_count(Dict,Dict)      --> [].
dcg_count(DictIn,DictOut) --> [C],{ inc_for_key(C,Dict2) },dcg_count(Dict2,DictOut).

count_with_phrase(Atom,phrase(dcg_count(counts{},DictWithCounts),Chars).

% ===
% Using standard Prolog to count
% ===

count_with_recursion(Atom,count_rec(Chars,DictWithCounts).
   
count_rec([],Dict,Dict).
count_rec([C|Cs],DictOut) :- inc_for_key(C,Dict2),count_rec(Cs,Dict2,DictOut).

使用 plunit 测试:

:- begin_tests(counting).

test("count using foldl/4,#1",true(DictWithCounts == counts{a:3,dropped:2})) :-
   count_with_foldl(abgdbabba,DictWithCounts).
   
test("count whith phrase/2,[true(DictWithCounts == counts{a:3,dropped:2}),nondet]) :- 
   count_with_phrase(abgdbabba,DictWithCounts).

test("count whith recursion,dropped:2})]) :- 
   count_with_recursion(abgdbabba,DictWithCounts).
   
:- end_tests(counting).

DCG 中的单个状态变量

在上面,DCG 在参数位置 1 处采用“输入状态”,然后将其发展为“输出状态”,最终在参数位置 2 处返回。

dcg_count(DictIn,DictOut)

这完全类似于函数式编程,通过这种方式将不可变结构传递给函数并由函数返回。在这里,结构通过谓词“相关”,这是另一种看待事物的方式(这种方式有时会与必须及时向前计算的机器的现实相冲突)。

请注意,上面的状态变量是接地的 - 没有变量。

单个状态变量就足够了,如果该状态变量是较大结构叶处的“空单元”的名称。较大的结构“在其叶子上生长”,DCG 填充空单元格,但为下一次调用留下另一个空单元格。

例如,这里是一个 DCG,它在其末尾 Fin 增长了一个“开放列表”。 Fin 始终是一个未绑定的变量,应由规则填充:

将原子中的 tag 替换为 :

collect(Fin) --> [],{ Fin = [] }.
collect(Fin) --> [t,a,g],collect(Fin2),{ Fin = [':'|Fin2] }.
collect(Fin) --> [C],{ Fin = [C|Fin2] }.

collect(Atom,Collected) :-
   atom_chars(Atom,phrase(collect(Collected),Chars).
?- collect(xytagyx,Out).
Out = [x,y,:,x] ;
false.

DCG 代码传统上写得更紧凑(这种方式也使其适合尾调用优化,这是不可忽视的):

collect([])        --> [].
collect([':'|Fin]) --> [t,collect(Fin).
collect([C|Fin])   --> [C],collect(Fin).

collect(Atom,Chars).

在这种情况下,每个 DCG 调用只能看到打开列表远端的空单元格,而 Collectecd 表示它的开始:

Collected ----> [|]        
               /   \       
              x    [|]     
                  /   \    
                 :   ~empty cell~ <--- Fin

所以,当遇到y规则时

collect([C|Fin]) --> [C],collect(Fin).

只是在它的 Fin 上做它的一部分并将列表增加 1 个列表单元格,但将其保留为“开放列表”以继续追加:

Collected ----> [|]        
               /   \       
              x    [|]     
                  /   \    
                 :    [|]
                     /   \
            C ----> y   ~empty cell~ <--- Fin

规则

collect(Fin) --> [],{ Fin = [] }.

将打开的列表转换为适当的列表,将空单元格设置为[]

Collected ----> [|]        
               /   \       
              x    [|]     
                  /   \    
                 :    [|]
                     /   \
                    y    []

这当然正是 DCG Primer 的树示例中发生的情况:

tree_nodes(nil) --> [].
tree_nodes(node(Name,Left,Right)) -->
        tree_nodes(Left),[Name],tree_nodes(Right).

这里,在叶子上生长的数据结构不是列表,而是二叉树。