summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile19
-rw-r--r--bencode.erl55
-rw-r--r--tests.erl50
3 files changed, 124 insertions, 0 deletions
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..7e34b06
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,19 @@
+
+EUNITPATH=/home/ssmith/software/eunit-1.1
+
+ERLC=erlc
+ERLFLAGS=-I$(EUNITPATH)/inc
+
+SRC=bencode.erl tests.erl
+BEAM=$(SRC:.erl=.beam)
+
+all: $(BEAM)
+
+%.beam : %.erl
+ $(ERLC) $(ERLFLAGS) $<
+
+test: $(BEAM)
+ erl -noshell -pa $(EUNITPATH)/ebin -s tests -s init stop
+
+clean:
+ rm -f $(BEAM) *~
diff --git a/bencode.erl b/bencode.erl
new file mode 100644
index 0000000..aaf737e
--- /dev/null
+++ b/bencode.erl
@@ -0,0 +1,55 @@
+
+-module(bencode).
+
+-export([bdecode/1]).
+
+-import(string, [substr/2, substr/3, to_integer/1, chr/2]).
+
+%% String
+bdecode([Char | _Rest] = Str) when Char >= $0, Char =< $9 ->
+ ColIdx = chr(Str, $:),
+ IntStr = substr(Str, 1, ColIdx-1),
+ {Len, _} = to_integer(IntStr),
+ Result = substr(Str, ColIdx+1, Len),
+ Rest = substr(Str, ColIdx+Len+1),
+ {{string, Result}, Rest};
+
+%% Int
+bdecode([Char | Rest]) when Char == $i ->
+ End = chr(Rest, $e),
+ {Result, _} = to_integer(substr(Rest, 1, End-1)),
+ {{int, Result}, substr(Rest, End+1)};
+
+%% List
+bdecode([Char | Rest]) when Char == $l ->
+ {List, Rem} = dolist(Rest),
+ {{list, List}, Rem};
+
+%% Dict
+bdecode([Char | Rest]) when Char == $d ->
+ {Dict, Rem} = dodict(Rest),
+ {{dict, Dict}, Rem}.
+
+
+dolist(Str) ->
+ dolist(Str, []).
+
+dolist([Char | Rest], List) when Char == $e ->
+ {List, Rest};
+
+dolist(Str, List) ->
+ {Result, Rest} = bdecode(Str),
+ dolist(Rest, lists:append(List, [Result])).
+
+
+dodict(Str) ->
+ dodict(Str, dict:new()).
+
+dodict([Char | Rest], Dict) when Char == $e ->
+ {Dict, Rest};
+
+dodict(Str, Dict) ->
+ {{string, Key}, Rest} = bdecode(Str),
+ {Val, Rest2} = bdecode(Rest),
+ dodict(Rest2, dict:store(Key, Val, Dict)).
+
diff --git a/tests.erl b/tests.erl
new file mode 100644
index 0000000..92f5646
--- /dev/null
+++ b/tests.erl
@@ -0,0 +1,50 @@
+
+-module(tests).
+
+-include("eunit.hrl").
+
+-export([start/0,
+ test_int1/0, test_int2/0, test_int3/0, test_int4/0,
+ test_string1/0, test_string2/0,
+ test_list1/0, test_list2/0, test_list3/0, test_list4/0, test_list5/0,
+ test_dict1/0]).
+
+%% Integer decoding tests
+test_int1() ->
+ ?match({{int, 999}, []}, bencode:bdecode("i999e")).
+test_int2() ->
+ ?match({{int, 0}, []}, bencode:bdecode("i0e")).
+test_int3() ->
+ ?match({{int, 123}, "abc"}, bencode:bdecode("i123eabc")).
+test_int4() ->
+ ?match({{int, -10}, []}, bencode:bdecode("i-10e")).
+
+%% String decoding tests
+test_string1() ->
+ ?match({{string, ""}, []}, bencode:bdecode("0:")).
+test_string2() ->
+ ?match({{string, "abcde"}, []}, bencode:bdecode("5:abcde")).
+
+%% List decoding tests
+test_list1() ->
+ ?match({{list, []}, []}, bencode:bdecode("le")).
+test_list2() ->
+ ?match({{list, [{int,1}]}, []}, bencode:bdecode("li1ee")).
+test_list3() ->
+ ?match({{list, [{int,1}]}, []}, bencode:bdecode("li1ee")).
+test_list4() ->
+ ?match({{list, [{int,1}, {int,2}]}, []}, bencode:bdecode("li1ei2ee")).
+test_list5() ->
+ ?match({{list, [{int,1},
+ {list, [{int, 2}, {int, 3}]}]},
+ []},
+ bencode:bdecode("li1eli2ei3eee")).
+
+%% Dict decoding tests
+test_dict1() ->
+ {{dict, Dict}, []} = bencode:bdecode("d3:agei25e4:eyes4:bluee"),
+ ?match([{"age", {int, 25}}, {"eyes", {string, "blue"}}], dict:to_list(Dict)).
+
+%% Entry point
+start() ->
+ eunit:run([tests], {prefix, "test_"}).