Prolog - 使用DCG处理二进制数据

时间:2014-10-31 21:35:52

标签: prolog binary-data swi-prolog dcg clpfd

在我看来,应该能够在字节列表上使用DCG处理二进制数据。但是,为了使其一般工作,必须使用按位运算,这意味着涉及is/2,这意味着实例化顺序是一个问题,这可能会混淆使用DCG来解析和生成。这里的想法是序列化/反序列化二进制数据,但我认为这个例子足以说明问题。

让我用一些代码来说明。假设我有一个二进制协议。我想从一个字节读取两个4位整数。我的天真自我试试这个:

two_four_bit_ints(High, Low) -->
  [B],
  {
    High is B >> 4,
    Low  is B /\ 0xF
  }.

这似乎适用于解析:

?- phrase(two_four_bit_ints(H,L), [255]).
H = L, L = 15.

?- phrase(two_four_bit_ints(H,L), [0]).
H = L, L = 0.

?- phrase(two_four_bit_ints(H,L), [15]).
H = 0,
L = 15.

?- phrase(two_four_bit_ints(H,L), [240]).
H = 15,
L = 0.

但这会生成:

?- phrase(two_four_bit_ints(15,15), [X]).
ERROR: is/2: Arguments are not sufficiently instantiated

?- phrase(two_four_bit_ints(15,15), X).
ERROR: is/2: Arguments are not sufficiently instantiated

不确定该怎么做。我支持某人大声喊叫"使用clpfd"但它似乎不支持位移操作,我担心在低级代码中调用如此强大的系统会产生性能影响。

由于我没有看到很多二进制文件的助手,在Prolog中还有其他更优选的二进制提取/编码方法吗?我现在只使用SWI,所以我很乐意接受那些不能移植到ISO的建议,但如果它是便携式的,那也很好。我非常希望找到一个像Erlang一样的语法移植的东西,但没有任何运气搜索。

4 个答案:

答案 0 :(得分:4)

更好地支持二进制数据在Prolog中具有非常好的功能。然而,Prolog的关系性质使得一般解决方案非常困难。因此,您面临一个严肃的决定:或者,您将其他语言的某些库直接映射到Prolog,从而忽略了Prolog的关系性质(并且理想地避免了所有边界的干净实例化错误)或者您选择了更多的关系方法

当选择更多关系解决方案时,您可以将现有库用作library(clfd),也可以自己实现整个约束机制。有了一些聪明的限制,你可能会采用一种更简单的方法,但我怀疑这会有效。权衡取决于正确性和效率。请注意,SICStus或SWI中的clpfd系统需要数十年才能达到其质量水平。

无论你采取哪种方式,都有一些评论:

library(clpfd)

的效率 SWI-Prolog中的

library(clpfd)已经过专门优化,在某些情况下可以与传统(is)/2相媲美。要查看此内容,请编译规则:

list_len([_|Es], N0) :- N0 #> 0, N1 #= N0-1, list_len(Es, N1).

并使用listing(list_len)查看生成的代码:

list_len([_|C], A) :-
    (   integer(A)
    ->  A>=0+1
    ;   clpfd:clpfd_geq(A, 1)
    ),
    (   integer(A)
    ->  B is A+ -1
    ;   clpfd:clpfd_equal(B, A-1)
    ),
    list_len(C, B).

实际上,(is)/2(>=)/2等可评估表达式的内置函数用于那些直接对应于这些基本操作的情况。

要完全模拟bitshift操作,您需要(div)/2,目前仅支持SICStus' library(clpfd)但不是SWI。所以在这里等待你的是一些额外的头痛。但只要您使用无符号的非负值,就不会出现问题。对于一般班次,您需要由SWI支持的(^)/2 - 但不是SICStus支持。

这是CLPFD版本:

two_four_bit_ints(High, Low) -->
  [B],
  { B in 0..255,
    Low in 0..15,
    High in 0..15,
    B #= Low + High*16
  }.

请注意,原始程序无意中在非预期的情况下定义了行为,例如B = -1234B = 1+1。您可以添加between(0, 255, B)但是您可以轻松获得组合枚举(读取:爆炸)。

library(clpfd)的当前实现可能会进一步显着改善此类情况,但为了改进它们,必须使用它们!

I / O和pio

ISO Prolog支持

上的基本I / O操作
  • bytes(get_byte/1),
  • 代码(get_code/1)和
  • 字符(get_char/1)。

如果您想使用DCG,您肯定希望使用library(pio)。目前, SWI的library(pio)仅支持codes

答案 1 :(得分:4)

在SWI-Prolog中,CLP(FD)中现在支持许多按位操作

请使用最新的git版本尝试以下操作。只需在代码中将(is)/2替换为(#=)/2即可:

two_four_bit_ints(High, Low) -->
  [B],
  {
    High #= B >> 4,
    Low  #= B /\ 0xF
  }.

您的前4个示例查询与之前完全一样,并且应该具有可接受的效率:

?- phrase(two_four_bit_ints(H,L), [255]).
H = L, L = 15.

?- phrase(two_four_bit_ints(H,L), [0]).
H = L, L = 0.

?- phrase(two_four_bit_ints(H,L), [15]).
H = 0,
L = 15.

?- phrase(two_four_bit_ints(H,L), [240]).
H = 15,
L = 0.

请注意,如果CLP(FD)约束在原始算法也支持的模式中使用,则它们直接编译为低级谓词。

使用CLP(FD)约束的一个好处是其他2个查询现在也可以工作:

?- phrase(two_four_bit_ints(15,15), [X]).
15#=X/\15,
15#=X>>4.

?- phrase(two_four_bit_ints(15,15), X).
X = [_G1048],
15#=_G1048/\15,
15#=_G1048>>4.

至少你可以用它来进一步推理。

事实上,最常见的查询现在也有效:

?- phrase(two_four_bit_ints(A,B), X).
X = [_G1270],
A#=_G1270>>4,
B#=_G1270/\15.

在某些情况下,可能会执行更强大的传播。我会在需要时立即调查。

答案 2 :(得分:1)

要比评论更明确,以解析整数并将整数转换回字符串为例,你可以说:

foo_parse(Number) -->
    digits(Ds),
    { number_codes(Number, Ds) }.

foo_generate(Number) -->
    { number_codes(Number, Ds) },
    Ds.

你可以通过为这两个子句中的第一个提供var(Number)“后卫”来避免这种情况,完成自己的剪切等,但我不确定它是否更容易编写,阅读或使用。无论如何,这两个DCG可能会从不同的环境中调用。

因此,对于您的情况,生成将类似于:

fourbit_fourbit_generate(High, Low) -->
    { D is (High << 4) + Low },
    [D].

只是我的意见。

答案 3 :(得分:1)

这可以工作

two_four_bit_ints(High, Low) -->
 [B],
  {
     integer(B) % suggestion by @false, instead of nonvar(B)
  -> High is B >> 4,
     Low  is B /\ 0xF
  ;  B is (High << 4) \/ Low
  }.

记住,DCG只是简单的Prolog,但你可以看到它们像可执行的语义语法。