繁体   English   中英

解析器DCG是否适合不确定?

[英]Is it appropriate for a parser DCG to not be deterministic?

我正在为查询引擎编写解析器。 我的解析器DCG query不是确定性的。

我将以关系方式使用解析器,以检查和合成查询。

解析器DCG是否适合不确定?

在代码中:

如果我希望能够以两种方式使用query / 2,那么它是否需要

?- phrase(query, [q,u,e,r,y]).
true;
false.

或者我应该能够获得

?- phrase(query, [q,u,e,r,y]).
true.

尽管如此,鉴于第一个片段需要我这样使用它

?- bagof(X, phrase(query, [q,u,e,r,y]), [true]).
true.

用它来检查配方?

第一个问自己的问题是,你的语法确定性,或者语法的术语,是明确的 这不是问你的DCG是否具有确定性,而是如果语法是明确的。 这可以通过基本的解析概念来回答,不需要使用DCG来回答这个问题。 换句话说,是否只有一种方法可以解析有效输入。 标准书是“编译器:原理,技术和工具”( WorldCat

现在,您实际上是在询问解析的三种不同用途。

  1. 识别器。
  2. 解析器。
  3. 一台发电机。

如果你的语法是明确的那么

  1. 对于识别器,答案应仅对于可以解析的有效输入为真,对于无效输入则为假。
  2. 对于解析器,它应该是确定性的,因为只有一种方法来解析输入。 解析器和识别器之间的区别在于识别器仅返回true或false,解析器将返回更多内容,通常是抽象语法树。
  3. 对于生成器,它应该是半确定性的,以便它可以生成多个结果。

所有这一切都可以用一个DCG完成,是的。 这三种不同的方式取决于您如何使用DCG的输入和输出。


这是一个非常简单的语法示例。

语法只是一个中缀二进制表达式,包含一个运算符和两个可能的操作数。 运算符是(+),操作数是(1)或(2)。

expr(expr(Operand_1,Operator,Operand_2)) -->
    operand(Operand_1),
    operator(Operator),
    operand(Operand_2).

operand(operand(1)) --> "1".
operand(operand(2)) --> "2".

operator(operator(+)) --> "+".

recognizer(Input) :-
    string_codes(Input,Codes),
    DCG = expr(_),
    phrase(DCG,Codes,[]).

parser(Input,Ast) :-
    string_codes(Input,Codes),
    DCG = expr(Ast),
    phrase(DCG,Codes,[]).

generator(Generated) :-
    DCG = expr(_),
    phrase(DCG,Codes,[]),
    string_codes(Generated,Codes).

:- begin_tests(expr).

recognizer_test_case_success("1+1").
recognizer_test_case_success("1+2").
recognizer_test_case_success("2+1").
recognizer_test_case_success("2+2").

test(recognizer,[ forall(recognizer_test_case_success(Input)) ] ) :-
    recognizer(Input).

recognizer_test_case_fail("2+3").

test(recognizer,[ forall(recognizer_test_case_fail(Input)), fail ] ) :-
    recognizer(Input).

parser_test_case_success("1+1",expr(operand(1),operator(+),operand(1))).
parser_test_case_success("1+2",expr(operand(1),operator(+),operand(2))).
parser_test_case_success("2+1",expr(operand(2),operator(+),operand(1))).
parser_test_case_success("2+2",expr(operand(2),operator(+),operand(2))).

test(parser,[ forall(parser_test_case_success(Input,Expected_ast)) ] ) :-
    parser(Input,Ast),
    assertion( Ast == Expected_ast).

parser_test_case_fail("2+3").

test(parser,[ forall(parser_test_case_fail(Input)), fail ] ) :-
    parser(Input,_).

test(generator,all(Generated == ["1+1","1+2","2+1","2+2"]) ) :-
    generator(Generated).

:- end_tests(expr).

语法是明确的,只有4个有效的字符串,都是唯一的。

识别器是确定性的,只返回true或false。
解析器是确定性的并返回唯一的AST。
生成器是半确定性的,并返回所有4个有效的唯一字符串。

示例运行测试用例。

?- run_tests.
% PL-Unit: expr ........... done
% All 11 tests passed
true.

为了扩大丹尼尔的评论

正如丹尼尔所说

1 + 2 + 3 

可以解析为

(1 + 2) + 3 

要么

1 + (2 + 3)

所以1+2+3是一个例子,正如你所说is specified by a recursive DCG ,正如我所指出的,解决问题的一个常见方法是使用括号来启动新的上下文。 开始一个新的背景是什么意思,就像重新开始一个新的清单 如果要创建AST,只需将新上下文(在括号之间)作为当前节点的新子树放入。

关于write_canonical / 1 ,这也很有用,但要注意运算符的左右关联性。 请参阅关联属性

例如

+是左关联的

?- write_canonical(1+2+3).
+(+(1,2),3)
true.

^是正确的关联

?- write_canonical(2^3^4).
^(2,^(3,4))
true.

2^3^4 = 2^(3^4) = 2^81 = 2417851639229258349412352

2^3^4 != (2^3)^4 = 8^4 = 4096

这个附加信息的重点是警告你语法设计充满了隐藏的陷阱,如果你没有严格的课程并完成其中的一些,你可以很容易地创建一个看起来很棒并且效果很好的语法然后几年被发现有一个严重的问题。 虽然Python不是模棱两可的AFAIK,但确实存在语法问题,它有足够的问题,当创建Python 3时,许多问题都得到了解决。 因此Python 3不向后兼容Python 2( 差异 )。 是的,他们已经进行了更改和库,以便更容易在Python 3中使用Python 2代码,但重点是语法在设计时可能会使用更多的分析。

代码应该是非确定性的唯一原因是您的问题有多个答案。 在这种情况下,您当然希望您的查询有多个解决方案。 然而,即便如此,如果可能的话,你还是希望在最后的解决方案之后留下选择点。

这就是我的意思:

“两个数字中较小的数字是多少?”

min_a(A, B, B) :- B < A.
min_a(A, B, A) :- A =< B.

所以现在你问,“1和2中的较小者是什么”,你期望的答案是“1”:

?- min_a(1, 2, Min).
Min = 1.

?- min_a(2, 1, Min).
Min = 1 ; % crap...
false.

?- min_a(2, 1, 2).
false.

?- min_a(2, 1, 1).
true ; % crap...
false.

所以这不是坏代码,但我认为它仍然是废话。 这就是为什么,对于两个数字中较小的一个,你会使用像SWI-Prolog中的min()函数

同样地,你想问一下,“1到10之间的偶数是多少”; 你写的查询:

?- between(1, 10, X), X rem 2 =:= 0.
X = 2 ;
X = 4 ;
X = 6 ;
X = 8 ;
X = 10.

......那没关系,但是如果你要求数字是3的倍数,你会得到:

?- between(1, 10, X), X rem 3 =:= 0.
X = 3 ;
X = 6 ;
X = 9 ;
false. % crap...

“低悬的果实”是你作为程序员会发现不存在非确定性的情况,但由于某些原因,你的Prolog无法从你编写的代码中推断出它。 在大多数情况下,您可以对此采取一些措施。

关于你的实际问题。 如果可以,请编写代码,以便只有在您要问的问题有多个答案时才存在非确定性。 当您使用DCG进行解析和生成时,这有时意味着您最终会有两个代码路径。 它感觉很笨拙但是更容易编写,阅读,理解,并且可能更有效。 谨慎一点,看看这个问题 我无法确切地知道,但OP遇到的问题几乎肯定是由不必要的非决定论引起的。 大输入可能发生的事情是留下了很多选择点,有大量内存无法回收,大量处理时间进入簿记状态,大量解决方案树只被遍历(如预期的那样)没有解决方案......你明白了。

有关我的意思的例子,你可以看一下SWI-Prolog中库(dcg / basics)的实现 注意几件事:

  • 文档非常明确地指出什么是确定性的,什么是非确定性的,以及非确定性如何对客户端代码有用;
  • 必要时使用削减来摆脱无用的选择点;
  • number//1 (朝向底部)的实现可以“生成提取数字”。

(提示:当你编写自己的解析器时,使用这个库中的原语!)

我希望你发现这个不必要的长答案很有用。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM