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



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

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

这个例子是数数。。

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

======

假设我必须解析字符串";添加项目5";。

如果状态是"0";"列表",比方说它应该打印";5=>列表";

如果";设置";那么";5=>设置";

或者可以是更复杂的字符串";新列表。加5"。。。其中解析";新列表";应该设置State=list,以便解析下一部分了解上下文,并且解释是";将5添加到列表中";vs";一组加5";。

无需使用这些示例,我只是想弄清楚何时使用半上下文表示法,以及如何在第一个/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,V], {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,DictWithCounts).
DictWithCounts = counts{a:3,b:4,dropped:2} ;
false.
?- 
count_with_recursion(abgdbabba,DictWithCounts).
DictWithCounts = counts{a:3,b:4,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,b,c]),
!,
add_it(Key,DictIn,DictOut).

inc_for_key(Key,DictIn,DictOut) :-
+memberchk(Key,[a,b,c]),
add_it(dropped,DictIn,DictOut).
add_it(Key,DictIn,DictOut) :-
(get_dict(Key,DictIn,Count) -> succ(Count,CountNew) ; CountNew=1),
put_dict(Key,DictIn,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,DictIn,Dict2) }, dcg_count(Dict2,DictOut).
count_with_phrase(Atom,DictWithCounts) :-
atom_chars(Atom,Chars),
phrase(dcg_count(counts{},DictWithCounts),Chars).
% ===
% Using standard Prolog to count
% ===
count_with_recursion(Atom,DictWithCounts) :-
atom_chars(Atom,Chars),
count_rec(Chars,counts{},DictWithCounts).

count_rec([],Dict,Dict).
count_rec([C|Cs],DictIn,DictOut) :- inc_for_key(C,DictIn,Dict2), count_rec(Cs,Dict2,DictOut).

使用plunit测试:

:- begin_tests(counting).
test("count using foldl/4, #1", true(DictWithCounts == counts{a:3,b:4,dropped:2})) :-
count_with_foldl(abgdbabba,DictWithCounts).

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

:- end_tests(counting).

DCGs中的单个状态变量

在上文中;输入状态";在论证位置1处;输出状态";其最终在自变量位置2处返回。

dcg_count(DictIn,DictOut)

这完全类似于函数式编程,在函数式编程中,不可变的结构被传递到函数中并由函数返回。这里的结构是";相关的";谓词,这是看待事物的另一种方式(这种方式有时会与必然在时间上向前计算的机器的现实相冲突)。

请注意,在上面,状态变量是地面-无变量。

如果单个状态变量是";空单元格";在较大结构的叶上。较大的结构";生长在它的叶子上";,DCG填充空信元,但为下一个呼叫留下另一个空信元。

例如这里有一个DCG;打开列表";在其末端CCD_ 12。Fin始终是一个未绑定的变量,应按以下规则填写:

用原子中的:替换tag:

collect(Fin) --> [],         { Fin = [] }.
collect(Fin) --> [t,a,g], !, collect(Fin2), { Fin = [':'|Fin2] }.
collect(Fin) --> [C],        collect(Fin2), { Fin = [C|Fin2] }.
collect(Atom,Collected) :-
atom_chars(Atom,Chars),
phrase(collect(Collected),Chars).
?- collect(xytagyx,Out).
Out = [x,y,:,y,x] ;
false.

传统上,DCG代码编写得更紧凑(这种方式也使其易于进行尾调用优化,这一点不容忽视):

collect([])        --> [].
collect([':'|Fin]) --> [t,a,g], !, collect(Fin).
collect([C|Fin])   --> [C],        collect(Fin).
collect(Atom,Collected) :-
atom_chars(Atom,Chars),
phrase(collect(Collected),Chars).

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

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

因此,当遇到y时,规则

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

只是在其Fin上完成其部分并将列表增加1个listcell;打开列表";用于继续附加:

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).

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

最新更新