Created
November 20, 2020 15:46
-
-
Save zetashift/9c2e2a43bfa33b7de37bb7ee044b415d to your computer and use it in GitHub Desktop.
Bencode from day #2 of Nim days
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # 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