Skip to content

Commit 2e02c22

Browse files
committed
Add bdecode
1 parent 0f64e88 commit 2e02c22

File tree

1 file changed

+91
-0
lines changed

1 file changed

+91
-0
lines changed

scrapscript.py

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1040,6 +1040,70 @@ def bencode(obj: object) -> bytes:
10401040
raise NotImplementedError(f"bencode not implemented for {type(obj)}")
10411041

10421042

1043+
class Bdecoder:
1044+
def __init__(self, msg: str) -> None:
1045+
self.msg: str = msg
1046+
self.idx: int = 0
1047+
1048+
def peek(self) -> str:
1049+
return self.msg[self.idx]
1050+
1051+
def read(self) -> str:
1052+
c = self.peek()
1053+
self.idx += 1
1054+
return c
1055+
1056+
def decode_int(self) -> int:
1057+
buf = ""
1058+
while (c := self.read()) != "e":
1059+
buf += c
1060+
return int(buf)
1061+
1062+
def decode_list(self) -> typing.List[Any]:
1063+
result = []
1064+
while self.peek() != "e":
1065+
result.append(self.decode())
1066+
assert self.read() == "e"
1067+
return result
1068+
1069+
def decode_dict(self) -> typing.Dict[Any, Any]:
1070+
result: Dict[Any, Any] = {}
1071+
while self.peek() != "e":
1072+
key = self.decode()
1073+
value = self.decode()
1074+
result[key] = value
1075+
assert self.read() == "e"
1076+
return result
1077+
1078+
def decode_str(self, start: str) -> str:
1079+
len_buf = start
1080+
while (c := self.peek()) != ":":
1081+
assert c.isdigit()
1082+
len_buf += c
1083+
self.read()
1084+
assert self.read() == ":"
1085+
buf = ""
1086+
for _ in range(int(len_buf)):
1087+
buf += self.read()
1088+
return buf
1089+
1090+
def decode(self) -> object:
1091+
ty = self.read()
1092+
if ty == "i":
1093+
return self.decode_int()
1094+
if ty == "l":
1095+
return self.decode_list()
1096+
if ty == "d":
1097+
return self.decode_dict()
1098+
if ty.isdigit():
1099+
return self.decode_str(ty)
1100+
raise NotImplementedError(ty)
1101+
1102+
1103+
def bdecode(msg: str) -> object:
1104+
return Bdecoder(msg).decode()
1105+
1106+
10431107
def serialize(obj: Object) -> bytes:
10441108
return bencode(obj.serialize())
10451109

@@ -2733,6 +2797,33 @@ def test_bencode_dict_sorts_keys(self) -> None:
27332797
self.assertEqual(bencode(d), b"d1:ai2e1:bi1ee")
27342798

27352799

2800+
class BdecodeTests(unittest.TestCase):
2801+
def test_bdecode_int(self) -> None:
2802+
self.assertEqual(bdecode("i123e"), 123)
2803+
2804+
def test_bdecode_bool(self) -> None:
2805+
# TODO(max): Should we discriminate between bool and int?
2806+
self.assertEqual(bdecode("i1e"), 1)
2807+
2808+
def test_bdecode_negative_int(self) -> None:
2809+
self.assertEqual(bdecode("i-123e"), -123)
2810+
2811+
def test_bdecode_bytes(self) -> None:
2812+
self.assertEqual(bdecode("3:abc"), "abc")
2813+
2814+
def test_bdecode_empty_list(self) -> None:
2815+
self.assertEqual(bdecode("le"), [])
2816+
2817+
def test_bdecode_list_of_ints(self) -> None:
2818+
self.assertEqual(bdecode("li1ei2ei3ee"), [1, 2, 3])
2819+
2820+
def test_bdecode_list_of_lists(self) -> None:
2821+
self.assertEqual(bdecode("lli1ei2eeli3ei4eee"), [[1, 2], [3, 4]])
2822+
2823+
def test_bdecode_dict_sorts_keys(self) -> None:
2824+
self.assertEqual(bdecode("d1:ai2e1:bi1ee"), {"b": 1, "a": 2})
2825+
2826+
27362827
class ObjectSerializeTests(unittest.TestCase):
27372828
def test_serialize_int(self) -> None:
27382829
obj = Int(123)

0 commit comments

Comments
 (0)