diff --git a/src/preserves.nim b/src/preserves.nim index 0c97817..5bb53c6 100644 --- a/src/preserves.nim +++ b/src/preserves.nim @@ -2,7 +2,7 @@ # SPDX-License-Identifier: ISC import bigints -import std/[base64, endians, hashes, options, sets, streams, strutils, tables, typetraits] +import std/[algorithm, base64, endians, hashes, options, sets, sequtils, streams, strutils, tables, typetraits] from std/json import escapeJson, escapeJsonUnquoted from std/macros import hasCustomPragma, getCustomPragmaVal @@ -12,8 +12,10 @@ type pkBoolean, pkFloat, pkDouble, pkSignedInteger, pkBigInteger, pkString, pkByteString, pkSymbol, pkRecord, pkSequence, pkSet, pkDictionary, pkEmbedded - Preserve* {.acyclic.} = object - ## Type that stores a Preserves value. + DictEntry[EmbededType] = tuple[key: PreserveGen[EmbededType], val: PreserveGen[EmbededType]] + + PreserveGen*[EmbeddedType] {.acyclic.} = ref object + ## Generic ``Preserve`` type before embedding. case kind*: PreserveKind of pkBoolean: bool*: bool @@ -32,47 +34,39 @@ type of pkSymbol: symbol*: string of pkRecord: - record*: seq[Preserve] # label is last + record*: seq[PreserveGen[EmbeddedType]] # label is last of pkSequence: - sequence*: seq[Preserve] + sequence*: seq[PreserveGen[EmbeddedType]] of pkSet: - set*: HashSet[Preserve] + set*: seq[PreserveGen[EmbeddedType]] + # HashSet templates not hygenic enough for this type of pkDictionary: - dict*: Table[Preserve, Preserve] + dict*: seq[DictEntry[EmbeddedType]] + # Tables templates not hygenic enough for this type of pkEmbedded: - embedded*: pointer + when EmbeddedType is void: + embedded*: PreserveGen[EmbeddedType] + else: + embedded*: EmbeddedType -proc assertValid*(prs: Preserve) = - case prs.kind - of pkBigInteger: - assert(BiggestInt.low.initBigInt < prs.bigint and prs.bigint < BiggestInt.high.initBigInt) - of pkRecord: - assert(prs.record.len > 0, "invalid Preserves record " & prs.repr) - assert(prs.record[prs.record.high].kind < pkRecord) - for v in prs.record: assertValid(v) - of pkSequence: - for v in prs.sequence: assertValid(v) - of pkSet: - for v in prs.set: assertValid(v) - of pkDictionary: - for key, val in prs.dict.pairs: - # assert(key.kind < pkRecord) - assertValid(key) - assertValid(val) - else: - discard +template PreserveOf*(T: typedesc): untyped = PreserveGen[T] + ## Customize ``PreserveGen`` with an embedded type. + ## ``` + ## type MyPreserve = PreserveOf(MyEmbbededType) + ## ``` -proc isNil*(prs: Preserve): bool = - ## Check if ``prs`` is equivalent to the zero-initialized ``Preserve``. - prs.kind == pkBoolean and prs.bool == false +type + Preserve* = PreserveOf(void) + ## Type of Preserves with all embedded values + ## converted to an unembedded representation. proc `<`(x, y: string | seq[byte]): bool = for i in 0 .. min(x.high, y.high): - if x[i] < y[i]: - return true + if x[i] < y[i]: return true + if x[i] != y[i]: return false x.len < y.len -proc `<`*(x, y: Preserve): bool = +proc `<`*[E](x, y: PreserveGen[E]): bool = if x.kind != y.kind: if x.kind == pkSignedInteger and y.kind == pkBigInteger: result = x.int.initBigInt < y.bigint @@ -84,6 +78,10 @@ proc `<`*(x, y: Preserve): bool = case x.kind of pkBoolean: result = (not x.bool) and 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: @@ -94,10 +92,70 @@ proc `<`*(x, y: Preserve): bool = result = x.bytes < y.bytes of pkSymbol: result = x.symbol < y.symbol - else: - discard + of pkRecord: + if x.record[x.record.high] < y.record[y.record.high]: return true + for i in 0.. 0) - result.add('<') - result.concat(prs.record[prs.record.high]) - for i in 0..') - of pkSequence: - result.add('[') - for i, val in prs.sequence: - if i > 0: - result.add(' ') - result.concat(val) - result.add(']') - of pkSet: - result.add("#{") - for val in prs.set.items: - result.concat(val) - result.add(' ') - if prs.set.len > 1: - result.setLen(result.high) - result.add('}') - of pkDictionary: - result.add('{') - var i = 0 - for (key, value) in prs.dict.pairs: - if i > 0: - result.add(' ') - result.concat(key) - result.add(": ") - result.concat(value) - inc i - result.add('}') - of pkEmbedded: - result.add(prs.embedded.repr) +proc incl*[E](prs: var PreserveGen[E]; key: PreserveGen[E]) = + for i in 0..prs.set.high: + if key < prs.set[i]: + insert(prs.set, [key], i) + return + prs.set.add(key) -proc `$`*(prs: Preserve): string = concat(result, prs) +proc excl*[E](prs: var PreserveGen[E]; key: PreserveGen[E]) = + for i in 0..prs.set.high: + if prs.set[i] == key: + delete(prs.set, i, i) + break -iterator items*(prs: Preserve): Preserve = +proc `[]`*[E](prs: var PreserveGen[E]; key: PreserveGen[E]): PreserveGen[E] = + for (k, v) in prs.dict.items: + if k == key: return v + raise newException(KeyError, "value not in Preserves dictionary") + +proc `[]=`*[E](prs: var PreserveGen[E]; key, val: PreserveGen[E]) = + for i in 0..prs.dict.high: + if key < prs.dict[i].key: + insert(prs.dict, [(key, val,)], i) + return + elif key == prs.dict[i].key: + prs.dict[i].val = val + return + prs.dict.add((key, val,)) + +proc initRecord*(label: Preserve; args: varargs[Preserve, toPreserve]): Preserve = + ## Record constructor. + result = Preserve(kind: pkRecord, + record: newSeqOfCap[Preserve](1+args.len)) + for arg in args: + #assertValid(arg) + result.record.add(arg) + result.record.add(label) + +proc len*[E](prs: PreserveGen[E]): int = + ## Return the number of values one level below ``prs``. + case prs.kind + of pkRecord: prs.record.len.pred + of pkSequence: prs.sequence.len + of pkSet: prs.set.len + of pkDictionary: prs.dict.len + else: 0 + +proc symbol*(s: string; E = void): PreserveGen[E] {.inline.} = + ## Symbol constructor. + PreserveGen[E](kind: pkSymbol, symbol: s) + +proc initRecord*(label: string; args: varargs[Preserve, toPreserve]): Preserve {.inline.} = + ## Record constructor that converts ``label`` to a symbol. + initRecord(symbol(label), args) + +proc initSet*(E = void): PreserveGen[E] = PreserveGen[E](kind: pkSet) + +proc initDictionary*(E = void): PreserveGen[E] = PreserveGen[E](kind: pkDictionary) + +iterator items*[E](prs: PreserveGen[E]): PreserveGen[E] = case prs.kind of pkRecord: for i in 0..prs.record.high.pred: @@ -248,11 +267,18 @@ iterator items*(prs: Preserve): Preserve = of pkSet: for e in prs.set.items: yield e of pkDictionary: - for k, v in prs.dict.pairs: + for (k, v) in prs.dict.items: yield k # key can be an arbitrary Preserve yield v else: discard +proc isFalse*[E](prs: PreserveGen[E]): bool = + ## Check if ``prs`` is equivalent to the zero-initialized ``Preserve``. + prs.kind == pkBoolean and prs.bool == false + +proc isSymbol*(prs: Preserve; sym: string): bool = + (prs.kind == pkSymbol) and (prs.symbol == sym) + func isRecord*(prs: Preserve): bool = if prs.kind == pkRecord: result = true @@ -277,10 +303,6 @@ iterator fields*(prs: Preserve): Preserve = ## Iterate the fields of a record value. for i in 0.. 0x9c: 0xa0 else: 0x90)) of 0xa0: let len = (tag.int and 0x0f) + 1 if len <= 8: - result = Preserve(kind: pkSignedInteger, int: s.readUint8().BiggestInt) + result = Value(kind: pkSignedInteger, int: s.readUint8().BiggestInt) if (result.int and 0x80) != 0: result.int.dec(0x100) for i in 1.. 0) + result.add('<') + result.concat(prs.record[prs.record.high]) + for i in 0..') + of pkSequence: + result.add('[') + for i, val in prs.sequence: + if i > 0: + result.add(' ') + result.concat(val) + result.add(']') + of pkSet: + result.add("#{") + for val in prs.set.items: + result.concat(val) + result.add(' ') + if prs.set.len > 1: + result.setLen(result.high) + result.add('}') + of pkDictionary: + result.add('{') + var i = 0 + for (key, value) in prs.dict.items: + if i > 0: + result.add(' ') + result.concat(key) + result.add(": ") + result.concat(value) + inc i + result.add('}') + of pkEmbedded: + result.add("#!") + when E is void: + result.add("#f") + else: + result.add($prs.embedded) -proc `[]`*(prs: Preserve; i: int): Preserve = - case prs.kind - of pkRecord: prs.record[i] - of pkSequence: prs.sequence[i] - else: - raise newException(ValueError, "`[]` is not valid for " & $prs.kind) - -proc initRecord*(label: Preserve; args: varargs[Preserve, toPreserve]): Preserve = - ## Record constructor. - result = Preserve(kind: pkRecord, - record: newSeqOfCap[Preserve](1+args.len)) - for arg in args: - assertValid(arg) - result.record.add(arg) - result.record.add(label) - -proc initRecord*(label: string; args: varargs[Preserve, toPreserve]): Preserve {.inline.} = - ## Record constructor that converts ``label`` to a symbol. - initRecord(symbol(label), args) +proc `$`*[E](prs: PreserveGen[E]): string = concat(result, prs) diff --git a/src/preserves/jsonhooks.nim b/src/preserves/jsonhooks.nim index 462690a..d47af2c 100644 --- a/src/preserves/jsonhooks.nim +++ b/src/preserves/jsonhooks.nim @@ -21,7 +21,7 @@ proc toPreserveHook*(js: JsonNode): Preserve = of JObject: result = Preserve(kind: pkDictionary) for key, val in js.fields.pairs: - result.dict[Preserve(kind: pkString, string: key)] = toPreserveHook(val) + result[Preserve(kind: pkString, string: key)] = toPreserveHook(val) of JArray: result = Preserve(kind: pkSequence, sequence: newSeq[Preserve](js.elems.len)) @@ -64,7 +64,7 @@ proc toJsonHook*(prs: Preserve): JsonNode = raise newException(ValueError, "cannot convert set to JSON") of pkDictionary: result = newJObject() - for (key, val) in prs.dict.pairs: + for (key, val) in prs.dict.items: if key.kind != pkString: raise newException(ValueError, "cannot convert non-string dictionary key to JSON") result[key.string] = toJsonHook(val) diff --git a/src/preserves/parse.nim b/src/preserves/parse.nim index 0bb6f6d..1a3eab9 100644 --- a/src/preserves/parse.nim +++ b/src/preserves/parse.nim @@ -1,3 +1,4 @@ +# SPDX-FileCopyrightText: ☭ 2021 Emery Hemingway # SPDX-License-Identifier: ISC import std/[base64, parseutils, sets, strutils, tables] @@ -12,87 +13,95 @@ proc shrink(stack: var Stack; n: int) = stack.setLen(stack.len - n) template pushStack(v: Preserve) = stack.add((v, capture[0].si)) -const pegParser = peg("Document", stack: Stack): - # Override rules from pegs.nim - - Document <- Preserves.Document - - Preserves.Record <- Preserves.Record: - var - record: seq[Preserve] - labelOff: int - while stack[labelOff].pos < capture[0].si: - inc labelOff - for i in labelOff.succ..stack.high: - record.add(move stack[i].value) - record.add(move stack[labelOff].value) - stack.shrink record.len - pushStack Preserve(kind: pkRecord, record: move record) - - Preserves.Sequence <- Preserves.Sequence: - var sequence: seq[Preserve] - for frame in stack.mitems: - if frame.pos > capture[0].si: - sequence.add(move frame.value) - stack.shrink sequence.len - pushStack Preserve(kind: pkSequence, sequence: move sequence) - - Preserves.Dictionary <- Preserves.Dictionary: - var dict: Table[Preserve, Preserve] - for i in countDown(stack.high.pred, 0, 2): - if stack[i].pos < capture[0].si: break - dict[move stack[i].value] = move stack[i.succ].value - stack.shrink 2*dict.len - pushStack Preserve(kind: pkDictionary, dict: move dict) - - Preserves.Set <- Preserves.Set: - var set: HashSet[Preserve] - for frame in stack.mitems: - if frame.pos > capture[0].si: - set.incl(move frame.value) - stack.shrink set.len - pushStack Preserve(kind: pkSet, set: move set) - - Preserves.Boolean <- Preserves.Boolean: - case $0 - of "#f": pushStack Preserve(kind: pkBoolean) - of "#t": pushStack Preserve(kind: pkBoolean, bool: true) - else: discard - - Preserves.Float <- Preserves.Float: - pushStack Preserve(kind: pkFloat, float: parseFloat($1)) - - Preserves.Double <- Preserves.Double: - pushStack Preserve(kind: pkDouble) - let i = stack.high - discard parseBiggestFloat($0, stack[i].value.double) - - Preserves.SignedInteger <- Preserves.SignedInteger: - pushStack Preserve(kind: pkSignedInteger, int: parseInt($0)) - - Preserves.String <- Preserves.String: - pushStack Preserve(kind: pkString, string: unescape($0)) - - Preserves.charByteString <- Preserves.charByteString: - let s = unescape($1) - pushStack Preserve(kind: pkByteString, bytes: cast[seq[byte]](s)) - - Preserves.hexByteString <- Preserves.hexByteString: - pushStack Preserve(kind: pkByteString, bytes: cast[seq[byte]](parseHexStr($1))) - - Preserves.b64ByteString <- Preserves.b64ByteString: - pushStack Preserve(kind: pkByteString, bytes: cast[seq[byte]](base64.decode($1))) - - Preserves.Symbol <- Preserves.Symbol: - pushStack Preserve(kind: pkSymbol, symbol: $0) - - Preserves.Compact <- Preserves.Compact: - pushStack decodePreserves(stack.pop.value.bytes) - proc parsePreserves*(text: string): Preserve {.gcsafe.} = + const pegParser = peg("Document", stack: Stack): + # Override rules from pegs.nim + + Document <- Preserves.Document + + Preserves.Record <- Preserves.Record: + var + record: seq[Preserve] + labelOff: int + while stack[labelOff].pos < capture[0].si: + inc labelOff + for i in labelOff.succ..stack.high: + record.add(move stack[i].value) + record.add(move stack[labelOff].value) + stack.shrink record.len + pushStack Preserve(kind: pkRecord, record: move record) + + Preserves.Sequence <- Preserves.Sequence: + var sequence: seq[Preserve] + for frame in stack.mitems: + if frame.pos > capture[0].si: + sequence.add(move frame.value) + stack.shrink sequence.len + pushStack Preserve(kind: pkSequence, sequence: move sequence) + + Preserves.Dictionary <- Preserves.Dictionary: + var prs = Preserve(kind: pkDictionary) + for i in countDown(stack.high.pred, 0, 2): + if stack[i].pos < capture[0].si: break + prs[move stack[i].value] = stack[i.succ].value + stack.shrink prs.dict.len*2 + pushStack prs + + Preserves.Set <- Preserves.Set: + var prs = Preserve(kind: pkSet) + for frame in stack.mitems: + if frame.pos > capture[0].si: + prs.incl(move frame.value) + stack.shrink prs.set.len + pushStack prs + + Preserves.Boolean <- Preserves.Boolean: + case $0 + of "#f": pushStack Preserve(kind: pkBoolean) + of "#t": pushStack Preserve(kind: pkBoolean, bool: true) + else: discard + + Preserves.Float <- Preserves.Float: + pushStack Preserve(kind: pkFloat, float: parseFloat($1)) + + Preserves.Double <- Preserves.Double: + pushStack Preserve(kind: pkDouble) + let i = stack.high + discard parseBiggestFloat($0, stack[i].value.double) + + Preserves.SignedInteger <- Preserves.SignedInteger: + pushStack Preserve(kind: pkSignedInteger, int: parseInt($0)) + + Preserves.String <- Preserves.String: + pushStack Preserve(kind: pkString, string: unescape($0)) + + Preserves.charByteString <- Preserves.charByteString: + let s = unescape($1) + pushStack Preserve(kind: pkByteString, bytes: cast[seq[byte]](s)) + + Preserves.hexByteString <- Preserves.hexByteString: + pushStack Preserve(kind: pkByteString, bytes: cast[seq[byte]](parseHexStr($1))) + + Preserves.b64ByteString <- Preserves.b64ByteString: + pushStack Preserve(kind: pkByteString, bytes: cast[seq[byte]](base64.decode($1))) + + Preserves.Symbol <- Preserves.Symbol: + pushStack Preserve(kind: pkSymbol, symbol: $0) + + Preserves.Embedded <- Preserves.Embedded: + pushStack Preserve( + kind: pkEmbedded, + embedded: stack.pop.value) + + Preserves.Compact <- Preserves.Compact: + pushStack decodePreserves(stack.pop.value.bytes) + var stack: Stack let match = pegParser.match(text, stack) if not match.ok: raise newException(ValueError, "failed to parse Preserves:\n" & text[match.matchMax..text.high]) assert(stack.len == 1) stack.pop.value + +when isMainModule: + assert(parsePreserves("#f") == Preserve()) diff --git a/src/preserves/records.nim b/src/preserves/records.nim index a223c1f..29bc3f2 100644 --- a/src/preserves/records.nim +++ b/src/preserves/records.nim @@ -3,9 +3,9 @@ import std/[macros, typetraits] import ../preserves -type RecordClass* = object +type RecordClass*[EmbeddedType] = object ## Type of a preserves record. - label*: Preserve + label*: PreserveGen[EmbeddedType] arity*: Natural proc `$`*(rec: RecordClass): string = @@ -17,17 +17,17 @@ proc isClassOf*(rec: RecordClass; val: Preserve): bool = assert(val.record.len > 0) result = val.label == rec.label and rec.arity == val.arity -proc classOf*(val: Preserve): RecordClass = +proc classOf*[E](val: PreserveGen[E]): RecordClass[E] = ## Derive the ``RecordClass`` of ``val``. if val.kind != pkRecord: raise newException(Defect, "cannot derive class of non-record value " & $val) assert(val.record.len > 0) - RecordClass(label: val.label, arity: val.arity) + RecordClass[E](label: val.label, arity: val.arity) -proc classOf*[T](x: T): RecordClass = +proc classOf*[T](x: T; E = void): RecordClass[E] = ## Derive the ``RecordClass`` of ``x``. when not T.hasCustomPragma(record): {.error: "no {.record.} pragma on " & $T.} - result.label = preserves.symbol(T.getCustomPragmaVal(record)) + result.label = preserves.symbol(T.getCustomPragmaVal(record), E) for k, v in x.fieldPairs: inc(result.arity) proc classOf*(T: typedesc[tuple]): RecordClass = diff --git a/src/preserves/schemas.nim b/src/preserves/schemas.nim index e5e7c78..a67ffcb 100644 --- a/src/preserves/schemas.nim +++ b/src/preserves/schemas.nim @@ -61,7 +61,7 @@ type Schema* = ref object version*: int - embeddedType*: Preserve + embeddedType*: string definitions*: OrderedTable[string, SchemaNode] ParseState = object @@ -146,8 +146,8 @@ proc `$`*(n: SchemaNode): string = proc `$`*(scm: Schema): string = result.add("version = $1 .\n" % $scm.version) - if not scm.embeddedType.isNil: - result.add("EmbeddedTypeName = $1 .\n" % $scm.embeddedType) + if scm.embeddedType != "": + result.add("EmbeddedTypeName = $1 .\n" % scm.embeddedType) for n, d in scm.definitions.pairs: result.add("$1 = $2 .\n" % [n, $d]) @@ -214,8 +214,8 @@ const parser = peg("Schema", p: ParseState): discard parseInt($1, p.schema.version) EmbeddedTypeName <- "embeddedType" * S * >("#f" | Ref): - if not p.schema.embeddedType.isNil: fail() - if $1 != "#f": p.schema.embeddedType = symbol($1) + if p.schema.embeddedType != "": fail() + if $1 != "#f": p.schema.embeddedType = $1 Include <- "include" * S * >(+Alnum): # TODO: may be relative or absolute diff --git a/tests/test_conversions.nim b/tests/test_conversions.nim index 338b0f7..711ea5e 100644 --- a/tests/test_conversions.nim +++ b/tests/test_conversions.nim @@ -1,6 +1,7 @@ +# SPDX-FileCopyrightText: 2021 ☭ Emery Hemingway # SPDX-License-Identifier: ISC -import std/[options, streams, strutils, unittest] +import std/[options, unittest] import bigints, preserves, preserves/records suite "conversions": diff --git a/tests/test_rfc8259.nim b/tests/test_rfc8259.nim index 2eabe7e..222d834 100644 --- a/tests/test_rfc8259.nim +++ b/tests/test_rfc8259.nim @@ -2,7 +2,7 @@ # SPDX-License-Identifier: ISC import preserves, preserves/jsonhooks -import std/[json,jsonutils,streams, unittest] +import std/[json, jsonutils, streams, unittest] let testVectors = [ """