1:- module(bencode, [bencode/2]). 2:- use_module(library('dcg/basics'), [integer/3]). 3:- use_module(library(plunit), [begin_tests/1, end_tests/1]). 4:- use_module(library(when), [when/2]).
12bencode(Term, Codes) :-
13 phrase(bval(Term), Codes).
14
15bval(I) -->
16 { freeze(I, integer(I)) },
17 "i", integer(I), "e",
18 !.
19bval(L) -->
20 "l", bvals(L), "e",
21 !.
22bval(Atom) -->
23 { freeze(Atom, atom(Atom)) },
24 { when(ground(Atom);ground(Bytes), atom_codes(Atom,Bytes)) },
25 { when(ground(Bytes);ground(Length), length(Bytes,Length)) },
26 integer(Length), ":", Bytes,
27 !.
28bval(Dict) -->
29 { when(ground(Dict), keys_sorted(Dict)) },
30 "d",
31 bpairs(Dict),
32 "e".
33
34bvals([X|Xs]) --> bval(X), bvals(Xs), !.
35bvals([]) --> "".
36
37bpairs([K-V|Pairs]) --> bval(K), bval(V), bpairs(Pairs), !.
38bpairs([]) --> "".
39
40keys_sorted(L) :-
41 is_list(L),
42 keysort(L, L).
43
44
45:- begin_tests(bencode). 46test(spam_encode) :-
47 bencode(spam, X),
48 X = "4:spam".
49test(spam_decode) :-
50 bencode(X, "4:spam"),
51 X = spam.
53
54test(int_encode) :-
55 bencode(42, X),
56 X = "i42e".
57test(int_decode) :-
58 bencode(X, "i42e"),
59 X = 42.
60
61test(negative_encode) :-
62 bencode(-3, X),
63 X = "i-3e".
64test(negative_decode) :-
65 bencode(X, "i-9e"),
66 X = -9.
67
68test(list_encode) :-
69 bencode([spam, eggs], X),
70 X = "l4:spam4:eggse".
71test(list_decode) :-
72 bencode(X, "l4:spam4:eggse"),
73 X = [spam, eggs].
74
75test(dictionary_encode) :-
76 bencode([cow-moo, spam-eggs], X),
77 X = "d3:cow3:moo4:spam4:eggse".
78test(dictionary_decode) :-
79 bencode(X, "d3:cow3:moo4:spam4:eggse"),
80 X = [cow-moo, spam-eggs].
81test(dictionary_list_value) :-
82 bencode([spam-[a,b]], "d4:spaml1:a1:bee").
83test(dictionary_longer) :-
84 bencode([ publisher-bob
85 , 'publisher-webpage'-'www.example.com'
86 , 'publisher.location'-home
87 ],
88 "d9:publisher3:bob17:publisher-webpage15:www.example.com18:publisher.location4:homee"
89 ).
90
91:- end_tests(bencode).