Skip to content

Instantly share code, notes, and snippets.

@zetashift
Created November 20, 2020 15:46
Show Gist options
  • Select an option

  • Save zetashift/9c2e2a43bfa33b7de37bb7ee044b415d to your computer and use it in GitHub Desktop.

Select an option

Save zetashift/9c2e2a43bfa33b7de37bb7ee044b415d to your computer and use it in GitHub Desktop.
Bencode from day #2 of Nim days
# A simple Bencode parser following Nim Days by xmonader
import strformat, tables, json, strutils, hashes
type
BencodeKind* = enum
btString, btInt, btList, btDict
BencodeType* = ref object
case kind*: BencodeKind
of BencodeKind.btString: s* : string
of BencodeKind.btInt: i* : int
of BencodeKind.btList: l* : seq[BencodeType]
of BencodeKind.btDict: d* : OrderedTable[BencodeType, BencodeType]
Encoder* = ref object
Decoder* = ref object
# Forward declaration because of recursion
proc encode(this: Encoder, obj: BencodeType): string
proc decode(this: Decoder, source: string): (BencodeType, int)
# Constructors
proc newEncoder*(): Encoder = new Encoder
proc newDecoder*(): Decoder = new Decoder
# Dispatch API for BencodeType
proc encodeObject*(this: Encoder, obj: BencodeType): string = this.encode(obj)
proc decodeObject*(this: Decoder, source: string): BencodeType =
result = this.decode(source)[0]
# For object variants like BencodeType we need to define custom `==` and `hash` procs
proc hash*(obj: BencodeType): Hash =
case obj.kind
of btString: !$(hash(obj.s))
of btInt: !$(hash(obj.i))
of btList: !$(hash(obj.l))
of btDict:
var h = 0
for key, value in obj.d.pairs:
h = h !& hash(key) !& hash(value)
!$(h)
proc `==`*(a, b: BencodeType): bool =
if a.isNil:
if b.isNil: return true
return false
elif b.isNil or a.kind != b.kind:
return false
else:
case a.kind
of btString:
result = a.s == b.s
of btInt:
result = a.i == b.i
of btList:
result = a.l == b.l
of btDict:
if a.d.len != b.d.len: return false
for key, value in a.d:
if not b.d.hasKey(key): return false
if b.d[key] != value: return false
result = true
# Define a simple string representation for BencodeType
proc `$`*(a: BencodeType): string =
case a.kind
of btString: fmt("<Bencode {a.s}>")
of btInt: fmt("<Bencode {a.i}>")
of btList: fmt("<Bencode {a.l}>")
of btDict: fmt("<Bencode {a.d}>")
# Encoding procs
proc encodeString(this: Encoder, s: string): string =
if s.len == 0: return ""
return $s.len & ":" & s
proc encodeInt(this: Encoder, i: int): string =
return fmt("i{i}e")
proc encodeList(this: Encoder, l: seq[BencodeType]): string =
result = "l"
for el in l:
result &= this.encode(el)
result &= "e"
proc encodeDict(this: Encoder, d: OrderedTable[BencodeType, BencodeType]): string =
result = "d"
for key, value in d.pairs():
# Bencode dicts must have String keys
assert key.kind == BencodeKind.btString
result &= this.encode(key) & this.encode(value)
result &= "e"
proc encode(this: Encoder, obj: BencodeType): string =
case obj.kind
of BencodeKind.btString: result = this.encodeString(obj.s)
of BencodeKind.btInt: result = this.encodeInt(obj.i)
of BencodeKind.btList: result = this.encodeList(obj.l)
of BencodeKind.btDict: result = this.encodeDict(obj.d)
# Procs for decoding
proc decodeString(this: Decoder, s: string): (BencodeType, int) =
let
colonPos = s.find(':')
strLen = s[0..<colonPos].parseInt
str = s[colonPos+1..colonPos+strLen]
result = (BencodeType(kind: btString, s: str),
colonPos+strLen+1)
proc decodeInt(this: Decoder, s: string): (BencodeType, int) =
let epos = s.find('e')
let i = parseInt(s[1..<epos])
result = (BencodeType(kind: btInt, i:i), epos+1)
proc decodeList(this: Decoder, s: string): (BencodeType, int) =
var
resultList = newSeq[BencodeType]()
currentChar = s[1]
idx = 1
while idx < s.len:
currentChar = s[idx]
if currentChar == 'e':
idx += 1
break
let (obj, nextObjPos) = this.decode(s[idx..<s.len])
echo obj
resultList.add(obj)
idx += nextObjPos
result = (BencodeType(kind: btList, l: resultList), idx)
proc decodeDict(this: Decoder, s: string): (BencodeType, int) =
var
resultDict = initOrderedTable[BencodeType, BencodeType]()
currentChar = s[1]
idx = 1
readingKey = true
currentKey: BencodeType
while idx < s.len:
currentChar = s[idx]
if currentChar == 'e': break
let (obj, nextObjPos) = this.decode(s[idx..<s.len])
if readingKey:
currentKey = obj
readingKey = false
else:
resultDict[currentKey] = obj
readingKey = true
idx += nextObjPos
result = (BencodeType(kind: btDict, d: resultDict), idx)
proc decode(this: Decoder, source: string): (BencodeType, int) =
var
currentChar = source[0]
idx = 0
while idx < source.len:
currentChar = source[idx]
case currentChar
of 'i':
let (obj, nextObjPos) = this.decodeInt(source[idx..source.len-1])
idx += nextObjPos
return (obj, idx)
of 'l':
let (obj, nextObjPos) = this.decodeList(source[idx..source.len-1])
idx += nextObjPos
return (obj, idx)
of 'd':
let (obj, nextObjPos) = this.decodeDict(source[idx..source.len-1])
idx += nextObjPos
return (obj, idx)
else:
let (obj, nextObjPos) = this.decodeString(source[idx..source.len-1])
idx += nextObjPos
return (obj, idx)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment