From 6ebb75269009982bab6a8623702470933cee98b8 Mon Sep 17 00:00:00 2001 From: Emery Hemingway Date: Tue, 15 Jun 2021 12:48:20 +0200 Subject: [PATCH] Store embedded values as pointers Reduce Preserve from a generic to a concrete type. --- README.md | 1 - preserves.nimble | 2 +- src/preserves.nim | 442 ++++++++++++++++++++++------------------- tests/test_rfc8259.nim | 37 ++-- 4 files changed, 260 insertions(+), 222 deletions(-) diff --git a/README.md b/README.md index 5def74a..5449276 100644 --- a/README.md +++ b/README.md @@ -4,4 +4,3 @@ Missing features: * embedded values * parsing from human-readable encoding * ordering of compound values -* conversion of Nim tuples and objects diff --git a/preserves.nimble b/preserves.nimble index aadf4bf..e148139 100644 --- a/preserves.nimble +++ b/preserves.nimble @@ -9,4 +9,4 @@ srcDir = "src" # Dependencies -requires "nim >= 1.4.6", "bigints" +requires "nim >= 1.4.8", "bigints" diff --git a/src/preserves.nim b/src/preserves.nim index c0215f5..0627724 100644 --- a/src/preserves.nim +++ b/src/preserves.nim @@ -5,12 +5,11 @@ import bigints type PreserveKind* = enum - pkBoolean, pkFloat, pkDouble, pkSignedInteger, pkBigInteger, pkString, - pkByteString, pkSymbol, pkRecord, pkSequence, pkSet, pkDictionary, pkEmbedded + pkBoolean, pkFloat, pkDouble, pkSignedInteger, pkBigInteger, pkString, pkByteString, + pkSymbol, pkRecord, pkSequence, pkSet, pkDictionary, pkEmbedded - Preserve*[T] {.acyclic.} = object + Preserve* {.acyclic.} = object ## Type that stores a Preserves value. - ## ``T`` is the domain-specific type of "embedded" values, otherwise ``void``. case kind*: PreserveKind of pkBoolean: bool*: bool @@ -29,17 +28,124 @@ type of pkSymbol: symbol*: string of pkRecord: - record*: seq[Preserve[T]] # record[0] is the label + record*: seq[Preserve] # record[0] is the label of pkSequence: - seq*: seq[Preserve[T]] + sequence*: seq[Preserve] of pkSet: - set*: HashSet[Preserve[T]] + set*: HashSet[Preserve] of pkDictionary: - dict*: Table[Preserve[T], Preserve[T]] + dict*: Table[Preserve, Preserve] of pkEmbedded: - embedded*: T + embedded*: pointer -proc `$`*[T](prs: Preserve[T]): string = +proc `<`(x, y: string | seq[byte]): bool = + for i in 0 .. min(x.high, y.high): + if x[i] < y[i]: + return true + x.len < y.len + +proc `<`*(x, y: Preserve): bool = + if x.kind != y.kind: + if x.kind == pkSignedInteger and y.kind == pkBigInteger: + result = x.int.initBigInt < y.bigint + elif x.kind == pkBigInteger and y.kind == pkSignedInteger: + result = x.bigint < y.int.initBigInt + else: + result = x.kind < y.kind + else: + case x.kind + of pkBoolean: + result = (not x.bool) and y.bool + of pkSignedInteger: + result = x.int < y.int + of pkBigInteger: + result = x.bigint < y.bigint + of pkString: + result = x.string < y.string + of pkByteString: + result = x.bytes < y.bytes + of pkSymbol: + result = x.symbol < y.symbol + else: + discard + +proc hash*(prs: Preserve): Hash = + var h = hash(prs.kind.int) + case prs.kind + of pkBoolean: + h = h !& hash(prs.bool) + of pkFloat: + h = h !& hash(prs.float) + of pkDouble: + h = h !& hash(prs.double) + of pkSignedInteger: + h = h !& hash(prs.int) + of pkBigInteger: + h = h !& hash(prs.bigint.flags) + h = h !& hash(prs.bigint) + of pkString: + h = h !& hash(prs.string) + of pkByteString: + h = h !& hash(prs.bytes) + of pkSymbol: + h = h !& hash(prs.symbol) + of pkRecord: + for val in prs.record: + h = h !& hash(val) + of pkSequence: + for val in prs.sequence: + h = h !& hash(val) + of pkSet: + for val in prs.set.items: + h = h !& hash(val) + of pkDictionary: + for (key, val) in prs.dict.pairs: + h = h !& hash(val) + of pkEmbedded: + h = h !& hash(prs.embedded) + !$h + +proc `==`*(x, y: Preserve): bool = + if x.kind == y.kind: + case x.kind + of pkBoolean: + result = x.bool == y.bool + of pkFloat: + result = x.float == y.float + of pkDouble: + result = x.double == y.double + of pkSignedInteger: + result = x.int == y.int + of pkBigInteger: + result = x.bigint == y.bigint + of pkString: + result = x.string == y.string + of pkByteString: + result = x.bytes == y.bytes + of pkSymbol: + result = x.symbol == y.symbol + of pkRecord: + for i, val in x.record: + if y.record[i] != val: return false + result = true + of pkSequence: + for i, val in x.sequence: + if y.sequence[i] != val: return false + result = true + of pkSet: + for val in x.set.items: + if not y.set.contains(val): return false + for val in y.set.items: + if not x.set.contains(val): return false + result = true + of pkDictionary: + for (key, val) in x.dict.pairs: + if y.dict[key] != val: return false + result = true + of pkEmbedded: + result = x.embedded == y.embedded + +proc `$`*(prs: Preserve): string = case prs.kind: of pkBoolean: case prs.bool @@ -69,14 +175,14 @@ proc `$`*[T](prs: Preserve[T]): string = result.add('>') of pkSequence: result.add('[') - for i, val in prs.seq: + for i, val in prs.sequence: if i > 0: result.add(' ') result.add($val) result.add(']') of pkSet: result.add("#{") - for val in prs.set: + for val in prs.set.items: result.add($val) result.add(' ') if result.len > 1: @@ -86,178 +192,50 @@ proc `$`*[T](prs: Preserve[T]): string = result.add('{') for (key, value) in prs.dict.pairs: result.add($key) - result.add(" :") + result.add(": ") result.add($value) result.add(' ') if result.len > 1: result.setLen(result.high) result.add('}') of pkEmbedded: - when not T is void: - $prs.embedded + result.add(prs.embedded.repr) -proc toPreserve*(b: bool): Preserve[void] = - Preserve[void](kind: pkBoolean, bool: b) +proc isNamedTuple(T: typedesc): bool {.magic: "TypeTrait".} +proc distinctBase(T: typedesc): typedesc {.magic: "TypeTrait".} +template distinctBase[T](a: T): untyped = distinctBase(type(a))(a) -proc toPreserve*(n: SomeInteger): Preserve[void] = - Preserve[void](kind: pkSignedInteger, int: n.BiggestInt) - -proc toPreserve*(n: BigInt): Preserve[void] = - if initBigInt(low(BiggestInt)) < n and n < initBigInt(high(BiggestInt)): - var tmp: BiggestUint - for limb in n.limbs: - tmp = (tmp shl 32) or limb - if Negative in n.flags: - tmp = (not tmp) + 1 - result = Preserve[void](kind: pkSignedInteger, int: cast[BiggestInt](tmp)) - else: - result = Preserve[void](kind: pkBigInteger, bigint: n) - -proc toPreserve*(s: string): Preserve[void] = - Preserve[void](kind: pkString, string: s) - -proc symbol*[T](s: string): Preserve[void] {.inline.} = +proc symbol*(s: string): Preserve {.inline.} = ## Symbol constructor. - Preserve[T](kind: pkSymbol, symbol: s) + Preserve(kind: pkSymbol, symbol: s) -proc record*[T](label: Preserve[T], args: varargs[Preserve[T]]): Preserve[T] = +proc initRecord*(label: Preserve, args: varargs[Preserve]): Preserve = ## Record constructor. - result = Preserve[T](kind: pkRecord, record: newSeqOfCap(1+args.len)) + result = Preserve(kind: pkRecord, + record: newSeqOfCap[Preserve](1+args.len)) result.record.add(label) for arg in args: result.record.add(arg) -proc record*[T](label: string, args: varargs[Preserve[T]]): Preserve[T] {.inline.} = +proc initRecord*(label: string, args: varargs[Preserve]): Preserve {.inline.} = ## Record constructor that converts ``label`` to a symbol. - record(symbol[T](label), args) + initRecord(symbol(label), args) -proc label*[T](prs: Preserve[T]): Preserve[T] {.inline.} = +proc label*(prs: Preserve): Preserve {.inline.} = ## Return the label of a record value. prs.record[0] -proc arity*[T](prs: Preserve[T]): int {.inline.} = +proc arity*(prs: Preserve): int {.inline.} = ## Return the number of fields in a record value. pred(prs.record.len) -proc fields*[T](prs: Preserve[T]): seq[Preserve[T]] {.inline.} = +proc fields*(prs: Preserve): seq[Preserve] {.inline.} = ## Return the fields of a record value. prs.record[1..prs.record.high] -iterator fields*[T](prs: Preserve[T]): Preserve[T] = +iterator fields*(prs: Preserve): Preserve = ## Iterate the fields of a record value. for i in 1..prs.record.high: yield prs.record[i] -iterator setItems*[T](prs: Preserve[T]): Preserve[T] = - for v in prs.set.keys: yield v - -proc `<`(x, y: string | seq[byte]): bool = - for i in 0 .. min(x.high, y.high): - if x[i] < y[i]: - return true - x.len < y.len - -proc `<`*[T](x, y: Preserve[T]): bool = - if x.kind != y.kind: - if x.kind == pkSignedInteger and y.kind == pkBigInteger: - result = x.int < y.bigint - elif x.kind == pkBigInteger and y.kind == pkSignedInteger: - result = x.bigint < y.int - else: - result = x.kind < y.kind - else: - case x.kind - of pkBoolean: - result = (not x.bool) and y.bool - of pkSignedInteger: - result = x.int < y.int - of pkBigInteger: - result = x.bigint < y.bigint - of pkString: - result = x.string < y.string - of pkByteString: - result = x.bytes < y.bytes - of pkSymbol: - result = x.symbol < y.symbol - else: - discard - -proc `==`*[T](x, y: Preserve[T]): bool = - if x.kind == y.kind: - case x.kind - of pkBoolean: - result = x.bool == y.bool - of pkFloat: - result = x.float == y.float - of pkDouble: - result = x.double == y.double - of pkSignedInteger: - result = x.int == y.int - of pkBigInteger: - result = x.bigint == y.bigint - of pkString: - result = x.string == y.string - of pkByteString: - result = x.bytes == y.bytes - of pkSymbol: - result = x.symbol == y.symbol - of pkRecord: - for i, val in x.record: - if y.record[i] != val: return false - result = true - of pkSequence: - for i, val in x.seq: - if y.seq[i] != val: return false - result = true - of pkSet: - for val in x.set: - if not y.set.contains(val): return false - for val in y.set: - if not x.set.contains(val): return false - result = true - of pkDictionary: - for (key, val) in x.dict.pairs: - if y.dict[key] != val: return false - result = true - of pkEmbedded: - when not T is void: - result = x.embedded == y.embedded - -proc hash*[T](prs: Preserve[T]): Hash = - var h = hash(prs.kind.int) - case prs.kind - of pkBoolean: - h = h !& hash(prs.bool) - of pkFloat: - h = h !& hash(prs.float) - of pkDouble: - h = h !& hash(prs.double) - of pkSignedInteger: - h = h !& hash(prs.int) - of pkBigInteger: - h = h !& hash(prs.bigint.flags) - h = h !& hash(prs.bigint) - of pkString: - h = h !& hash(prs.string) - of pkByteString: - h = h !& hash(prs.bytes) - of pkSymbol: - h = h !& hash(prs.symbol) - of pkRecord: - for val in prs.record: - h = h !& hash(val) - of pkSequence: - for val in prs.seq: - h = h !& hash(val) - of pkSet: - for val in prs.set: - h = h !& hash(val) - of pkDictionary: - for (key, val) in prs.dict.pairs: - h = h !& hash(val) - of pkEmbedded: - when not T is void: - h = h !& hash(prs.embedded) - !$h - proc writeVarint(s: Stream; n: int) = var n = n while true: @@ -278,7 +256,7 @@ proc readVarint(s: Stream): int = break shift.inc 7 -proc write*[T](str: Stream; prs: Preserve[T]) = +proc write*(str: Stream; prs: Preserve) = case prs.kind: of pkBoolean: case prs.bool @@ -354,7 +332,7 @@ proc write*[T](str: Stream; prs: Preserve[T]) = str.write(0x84'u8) of pkSequence: str.write(0xb5'u8) - for e in prs.seq: + for e in prs.sequence: str.write(e) str.write(0x84'u8) of pkSet: @@ -369,65 +347,63 @@ proc write*[T](str: Stream; prs: Preserve[T]) = str.write(value) str.write(0x84'u8) of pkEmbedded: - when not T is void: - str.write(0x86'u8) - str.write(prs.embedded) + str.write(0x86'u8) + raiseAssert("binary representation of embedded values is undefined") -proc parsePreserve*(s: Stream): Preserve[void] = +proc parsePreserve*(s: Stream): Preserve = proc assertStream(check: bool) = if not check: raise newException(ValueError, "invalid Preserves stream") const endMarker = 0x84 let tag = s.readUint8() case tag - of 0x80: result = Preserve[void](kind: pkBoolean, bool: false) - of 0x81: result = Preserve[void](kind: pkBoolean, bool: true) + of 0x80: result = Preserve(kind: pkBoolean, bool: false) + of 0x81: result = Preserve(kind: pkBoolean, bool: true) of 0x82: when system.cpuEndian == bigEndian: - result = Preserve[void](kind: pkFloat, float: s.readFloat32()) + result = Preserve(kind: pkFloat, float: s.readFloat32()) else: - result = Preserve[void](kind: pkFloat) + result = Preserve(kind: pkFloat) var be = s.readFloat32() swapEndian32(result.float.addr, be.addr) of 0x83: when system.cpuEndian == bigEndian: - result = Preserve[void](kind: pkDouble, double: s.readFloat64()) + result = Preserve(kind: pkDouble, double: s.readFloat64()) else: - result = Preserve[void](kind: pkDouble) + result = Preserve(kind: pkDouble) var be = s.readFloat64() swapEndian64(result.double.addr, be.addr) of 0x84: assertStream(false) of 0xb1: - result = Preserve[void](kind: pkString) + result = Preserve(kind: pkString) let len = s.readVarint() result.string = s.readStr(len) of 0xb2: - result = Preserve[void](kind: pkByteString) + result = Preserve(kind: pkByteString) let len = s.readVarint() result.bytes = cast[seq[byte]](s.readStr(len)) of 0xb3: let len = s.readVarint() - result = symbol[void](s.readStr(len)) + result = symbol(s.readStr(len)) of 0xb4: - result = Preserve[void](kind: pkRecord) + result = Preserve(kind: pkRecord) while s.peekUint8() != endMarker: result.record.add(s.parsePreserve()) discard s.readUint8() assertStream(result.record.len > 0) of 0xb5: - result = Preserve[void](kind: pkSequence) + result = Preserve(kind: pkSequence) while s.peekUint8() != endMarker: - result.seq.add(s.parsePreserve()) + result.sequence.add(s.parsePreserve()) discard s.readUint8() of 0xb6: - result = Preserve[void](kind: pkSet) + result = Preserve(kind: pkSet) while s.peekUint8() != endMarker: - let val = s.parsePreserve() - result.set.incl(val) + result.set.incl(s.parsePreserve()) discard s.readUint8() of 0xb7: - result = Preserve[void](kind: pkDictionary) + result = Preserve(kind: pkDictionary) while s.peekUint8() != endMarker: let key = s.parsePreserve() let val = s.parsePreserve() @@ -435,54 +411,100 @@ proc parsePreserve*(s: Stream): Preserve[void] = discard s.readUint8() of 0xb0: let len = s.readVarint() - result = Preserve[void](kind: pkBigInteger) + result = Preserve(kind: pkBigInteger) for _ in 1..len: result.bigint = (result.bigint shl 8) + s.readUint8().int32 else: case 0xf0 and tag of 0x90: var n = tag.BiggestInt - result = Preserve[void](kind: pkSignedInteger, + result = Preserve(kind: pkSignedInteger, int: n - (if n > 0x9c: 0xa0 else: 0x90)) of 0xa0: let len = (tag.int and 0x0f) + 1 if len <= 8: - result = Preserve[void](kind: pkSignedInteger, int: s.readUint8().BiggestInt) + result = Preserve(kind: pkSignedInteger, int: s.readUint8().BiggestInt) if (result.int and 0x80) != 0: result.int.dec(0x100) for i in 1..