Prolog DCG set_prolog_flag double_quotes 源代码指令位置很重要;文档?

Prolog DCG set_prolog_flag double_quotes source code directive location matters; documentation?

我通过 SWI-Prolog 艰难地了解到,Prolog 指令的位置 set_prolog_flag 在源代码文件中很重要。

我发现的关于使用指令加载源代码文件的唯一有价值的文档是 Loading Prolog source files

A directive is an instruction to the compiler. Directives are used to set (predicate) properties (see section 4.15), set flags (see set_prolog_flag/2) and load files (this section). Directives are terms of the form :- <term>.

SWI-Prolog 是否有涵盖源代码加载的文档,说明指令是应用于整个文件还是取决于源代码文件中的位置?

还是说从源代码文件加载的所有行都只是将语句简单地播放到顶层,位置总是很重要?

补充/TL;DR

默认

在 Prolog 中使用限定从句文法 (DCG) 时,已知 DCG 要求输入是字符代码列表,例如

?- string_codes("abc123",Cs).
Cs = [97, 98, 99, 49, 50, 51].

并在源代码文件中使用以下 DCG 规则并加载到顶层

digit(0) --> "0".

DCG 可以与

一起使用
?- string_codes("0",Cs),phrase(digit(D),Cs,R).
Cs = [48],
D = 0,
R = []

set_prolog_flag

现在可以更轻松地使用 DCG,而不必使用 string_codes Prolog 指令 ​​

:- set_prolog_flag(double_quotes, chars).

可以在源代码文件中使用,并在源代码文件中使用以下DCG规则并加载到顶层

digit(0) --> "0".

DCG 可以与

一起使用
?- phrase(digit(D),"0",R).
D = 0,
R = [].

遗漏了一些重要的东西

事实证明,如果 set_prolog_flag 出现在 之前 DCG 规则然后跳过 string_codes 有效,但是如果 set_prolog_flag 出现 DCG 规则之后跳过 string_codes 失败。

:- set_prolog_flag(double_quotes, chars).
digit(0) --> "0".

?- phrase(digit(D),"0",R).
D = 0,
R = [].

digit(0) --> "0".
:- set_prolog_flag(double_quotes, chars).

?- phrase(digit(D),"0",R).
false.

导致我犯规的原因

虽然我知道很多使用 Prolog 的编程都可以在顶层完成,但我倾向于依赖源代码文件和 consult/1.
在编写大量代码时,我开始使用 modules。对于模块,我发现 Prolog 标志对于每个模块都是独立的。

?- current_prolog_flag(double_quotes,V).
V = string.

?- current_prolog_flag(symbolic:double_quotes,V).
V = string.

?- set_prolog_flag(symbolic:double_quotes,chars).
true.

?- current_prolog_flag(double_quotes,V).
V = string.

?- current_prolog_flag(symbolic:double_quotes,V).
V = chars.

并且默认的顶级模块是 user

?- current_prolog_flag(double_quotes,V).
V = string.

?- current_prolog_flag(user:double_quotes,V).
V = string.

?- set_prolog_flag(double_quotes,chars).
true.

?- current_prolog_flag(double_quotes,V).
V = chars.

?- current_prolog_flag(user:double_quotes,V).
V = chars.

?- set_prolog_flag(user:double_quotes,codes).
true.

?- current_prolog_flag(double_quotes,V).
V = codes.

?- current_prolog_flag(user:double_quotes,V).
V = codes.

这让我误以为 Prolog 指令 ​​set_prlog_flag 应用于整个模块,无论它写在哪里。

是什么打破了常规

在编写大量示例代码时,更容易将所有小示例保存在一个文件中,并且与每个小示例关联的是 set_prolog_flag。对于标识符示例,它需要两个小示例 DCG 规则,一个用于数字,一个用于字母。数字规则高于字母规则并且有效,但字母规则有 set_prolog_flag 指令,因为我当时正在研究它们。请记住,我认为此时该指令适用于整个文件。然后在测试 ident 中,字母的 DCG 规则有效,但数字的 DCG 规则失败。

digit(0) --> "0", !.
digit(1) --> "1", !.
digit(2) --> "2", !.

:- set_prolog_flag(double_quotes, chars).

ident(Id) --> letter(C), identr(Cs), { name(Id, [C|Cs]) }.

identr([C|Cs]) --> letter(C), !, identr(Cs).
identr([C|Cs]) --> digit(C), !, identr(Cs).
identr([])     --> [].

letter(a) --> "a", !.
letter(b) --> "b", !.
letter(c) --> "c", !.

?- phrase(ident(Id),"ab12",R).
Id = ab,
R = ['1', '2'].

根本原因

所以使用 listing/1

?- listing(digit).
digit(0, [48|B], A) :- !,
        A=B.
digit(1, [49|B], A) :- !,
        A=B.
digit(2, [50|B], A) :- !,
        A=B.


?- listing(ident).
ident(C, A, F) :-
        letter(D, A, B),
        identr(E, B, G),
        name(C, [D|E]),
        F=G.

?- listing(identr).
identr([A|D], B, F) :-
        letter(A, B, C), !,
        E=C,
        identr(D, E, F).
identr([A|D], B, F) :-
        digit(A, B, C), !,
        E=C,
        identr(D, E, F).
identr([], A, A).

?- listing(letter).
letter(a, [a|B], A) :- !,
        A=B.
letter(b, [b|B], A) :- !,
        A=B.
letter(c, [c|B], A) :- !,
        A=B.

问题很明显

digit(0, [48|B], A) :- !,
    A=B.

letter(a, [a|B], A) :- !,
        A=B.

该数字已转换为使用字符代码 48,字母已转换为使用字符 a。那时我问自己 set_prolog_flag 在 source 中的位置是否重要。

确认根本原因

为了对此进行测试,我创建了一个小源代码文件

digit_before(0) --> "0".

:- set_prolog_flag(double_quotes, chars).

digit_after(0) --> "0".

并在顶级

?- current_prolog_flag(double_quotes,V).
V = string.

?- current_prolog_flag(symbolic:double_quotes,V).
V = string.

?- consult("C:/Users/Eric/Documents/Projects/Calculus Project/test.pl").
true.

?- current_prolog_flag(double_quotes,V).
V = chars.

?- current_prolog_flag(symbolic:double_quotes,V).
V = string.

?- listing(digit_before).
digit_before(0, [48|A], A).

true.

?- listing(digit_after).
digit_after(0, ['0'|A], A).

true

确认 Prolog 指令 ​​set_prolog_flag 不适用于整个文件。请注意,digit_before 转换为 48,digit_after 转换为 '0'

备注

注意:指令 set_prolog_flag(F,V) 也可以在顶层使用,不需要前面的 :-

注意:示例使用 :- set_prolog_flag(double_quotes, chars).,但 :- set_prolog_flag(double_quotes, codes). 也有效。首选使用 chars 值,因为它使值在调试等时更易于阅读。

我可以说您可以确保 set_prolog_flag(double_quotes, chars) 指令具有所需的行为(适用于整个文件)。

这可以通过使用带有选项 after_loadinitialization/2. 指令或使用 initialization/1.

来完成
digit_before(0) --> "0".

:- initialization( set_prolog_flag(double_quotes, chars),  after_load ).

digit_after(0) --> "0".

SWI-Prolog initializаtion/2 directive

SWI-Prolog initializаtion/1 directive

关于如何向 SWI-Prolog 社区提出您的想法的问题,我希望(初始)解决方案是存在第二个答案。

有用的链接:

Research papers by Ulrich Neumerkel and Fred Mesnard

Home Page of Markus Triska

包含大量专用于编程语言 Prolog 的不同资料。

在 SWI-Prolog 中,指令和子句是按顺序处理的。 Prolog 标志很复杂。总体规则是它们是线程范围的,其中子线程使用写时复制语义共享来自其创建者的标志,这实际上意味着除了性能和内存使用之外,所有标志都将被复制。但是,一些标志的范围仅限于它们出现的源文件。这意味着 load_files/2 在加载之前保存标志的状态并在加载之后恢复它。其他一些标志是模块范围的,这意味着标志 API 只是更改模块属性的代理。这样的标志不是特定于线程的,因为模块是全局的。另请注意,一些标志会影响读取(例如,double_quotes),而其他标志会影响编译器(optimise)并且最会影响运行时行为。

理想情况下,带有 current_prolog_flag/2 的文档应该记录这些方面。不确定此文档是否准确。对于 double_quotes,它表示 为每个模块维护