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]).
 bencode(?Term, ?Codes) is semidet
True if Codes is the bencoding of Term. In Term, atoms represent byte strings. Lists of Key-Value pairs, sorted by Key represent dictionaries. Integers and lists represent themselves.
   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.
   52% TODO test arbitrary byte strings
   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).