Remodel Preserves[E] into Value

Using a parameterized Preserve[E] type is too much hassle. Replace
with a Value type with an embedded field of "ref RootObj".
This commit is contained in:
Emery Hemingway 2023-12-27 17:05:30 +02:00
parent 85cef2e1d2
commit 867d25afee
28 changed files with 540 additions and 744 deletions

View File

@ -1 +1,2 @@
NIM_FLAGS += --path:$(TUP_CWD)/../nim
NIM_FLAGS += --path:$(TUP_CWD)/../npeg/src

View File

@ -1,6 +1,6 @@
# Package
version = "20231227"
version = "20231230"
author = "Emery Hemingway"
description = "data model and serialization format"
license = "Unlicense"

View File

@ -1,2 +1,2 @@
include_rules
: preserves.nim |> !nim_run |>
: preserves.nim |> !nim_check |>

File diff suppressed because it is too large Load Diff

View File

@ -7,3 +7,4 @@ NIM_FLAGS_preserves_schemac += -d:npegDotDir="../.."
DOT_FILES = ../../Atom.dot ../../Document.dot ../../Schema.dot
: preserves_schemac.nim |> !nim_bin |> $(BIN_DIR)/preserves-schemac | $(DOT_FILES) $(BIN_DIR)/<preserves-schemac>
: foreach $(DOT_FILES) |> dot -Tsvg -LO %f > %o |> ../../%B-Grammer-Graph.svg
: foreach *hooks.nim |> !nim_run |>

View File

@ -11,10 +11,10 @@ const
fullTimeFormat = "HH:mm:sszzz"
dateTimeFormat = "yyyy-MM-dd'T'HH:mm:sszzz"
proc toPreserveHook*(dt: DateTime; E: typedesc): Preserve[E] =
initRecord[E](toSymbol("rfc3339", E), toPreserve($dt, E))
proc toPreservesHook*(dt: DateTime): Value =
initRecord("rfc3339", toPreserves($dt))
proc fromPreserveHook*[E](dt: var DateTime; pr: Preserve[E]): bool =
proc fromPreservesHook*(dt: var DateTime; pr: Value): bool =
result = pr.isRecord(label, 1) and pr.record[0].isString
if result:
try:
@ -39,6 +39,6 @@ runnableExamples:
import preserves
var a, b: DateTime
a = now()
var pr = a.toPreserveHook(void)
check fromPreserveHook(b, pr)
var pr = a.toPreservesHook()
check b.fromPreservesHook(pr)
check $a == $b

View File

@ -4,51 +4,51 @@
import std/[json, tables]
import ../preserves
proc toPreserveHook*(js: JsonNode; E: typedesc): Preserve[E] =
proc toPreservesHook*(js: JsonNode): Value =
case js.kind
of JString:
result = Preserve[E](kind: pkString, string: js.str)
result = js.str.toPreserves()
of JInt:
result = Preserve[E](kind: pkRegister, register: js.num)
result = js.num.toPreserves()
of JFloat:
result = Preserve[E](kind: pkDouble, double: js.fnum)
result = js.fnum.toPreserves()
of JBool:
result = case js.bval
of false: toSymbol("false", E)
of true: toSymbol("true", E)
of false: toSymbol("false")
of true: toSymbol("true")
of JNull:
result = toSymbol("null", E)
result = toSymbol("null")
of JObject:
result = Preserve[E](kind: pkDictionary)
result = Value(kind: pkDictionary)
for key, val in js.fields.pairs:
result[Preserve[E](kind: pkSymbol, symbol: Symbol key)] = toPreserveHook(val, E)
result[Value(kind: pkSymbol, symbol: Symbol key)] = toPreservesHook(val)
of JArray:
result = Preserve[E](kind: pkSequence,
sequence: newSeq[Preserve[E]](js.elems.len))
result = Value(kind: pkSequence,
sequence: newSeq[Value](js.elems.len))
for i, e in js.elems:
result.sequence[i] = toPreserveHook(e, E)
result.sequence[i] = toPreservesHook(e)
proc fromPreserveHook*[E](js: var JsonNode; prs: Preserve[E]): bool =
proc fromPreservesHook*(js: var JsonNode; pr: Value): bool =
runnableExamples:
import std/json
var js = JsonNode()
var pr = js.toPreserveHook(void)
assert fromPreserveHook(js, pr)
var pr = js.toPreservesHook()
assert js.fromPreservesHook(pr)
fromJsonHook(pr, js)
js = toJsonHook(pr)
case prs.kind:
case pr.kind:
of pkBoolean:
js = newJBool(prs.bool)
js = newJBool(pr.bool)
of pkFloat:
js = newJFloat(prs.float)
js = newJFloat(pr.float)
of pkDouble:
js = newJFloat(prs.double)
js = newJFloat(pr.double)
of pkRegister:
js = newJInt(prs.register)
js = newJInt(pr.register)
of pkString:
js = newJString(prs.string)
js = newJString(pr.string)
of pkSymbol:
case prs.symbol.string
case pr.symbol.string
of "false":
js = newJBool(false)
of "true":
@ -59,38 +59,38 @@ proc fromPreserveHook*[E](js: var JsonNode; prs: Preserve[E]): bool =
return false
of pkSequence:
js = newJArray()
js.elems.setLen(prs.sequence.len)
for i, val in prs.sequence:
if not fromPreserveHook(js.elems[i], val):
js.elems.setLen(pr.sequence.len)
for i, val in pr.sequence:
if not js.elems[i].fromPreservesHook(val):
return false
of pkSet:
js = newJArray()
js.elems.setLen(prs.set.len)
js.elems.setLen(pr.set.len)
var i: int
for val in prs.set:
if not fromPreserveHook(js.elems[i], val):
for val in pr.set:
if not js.elems[i].fromPreservesHook(val):
return false
inc i
of pkDictionary:
js = newJObject()
for (key, val) in prs.dict.items:
for (key, val) in pr.dict.items:
case key.kind
of pkSymbol:
var jsVal: JsonNode
if not fromPreserveHook(jsVal, val): return false
if not jsVal.fromPreservesHook(val): return false
js[string key.symbol] = jsVal
of pkString:
var jsVal: JsonNode
if not fromPreserveHook(jsVal, val): return false
if not jsVal.fromPreservesHook(val): return false
js[key.string] = jsVal
else:
return false
else: return false
true
proc toJsonHook*[E](pr: Preserve[E]): JsonNode =
if not fromPreserveHook(result, pr):
proc toJsonHook*(pr: Value): JsonNode =
if not result.fromPreservesHook(pr):
raise newException(ValueError, "cannot convert Preserves value to JSON")
proc fromJsonHook*[E](pr: var Preserve[E]; js: JsonNode) =
pr = toPreserveHook(js, E)
proc fromJsonHook*(pr: var Value; js: JsonNode) =
pr = toPreservesHook(js)

View File

@ -138,11 +138,7 @@ proc embeddedIdentString(scm: Schema): string =
proc embeddedIdent(scm: Schema): PNode =
ident(embeddedIdentString(scm))
proc preserveIdent(scm: Schema): Pnode =
if scm.hasEmbeddedType:
nkBracketExpr.newTree(ident"Preserve", embeddedIdent(scm))
else:
nkBracketExpr.newTree(ident"Preserve", ident"void")
proc preserveIdent(scm: Schema): Pnode = ident"Value"
proc parameterize(scm: Schema; node: PNode; embeddable: bool): PNode =
if embeddable and node.kind notin {nkBracketExpr}:
@ -152,7 +148,7 @@ proc parameterize(scm: Schema; node: PNode; embeddable: bool): PNode =
proc parameterize(scm: Schema; spec: TypeSpec): PNode =
parameterize(scm, spec.node, spec.isEmbedded)
proc hash(r: Ref): Hash = r.toPreserve.hash
proc hash(r: Ref): Hash = r.toPreserves.hash
type RefSet = HashSet[Ref]
proc attrs(loc: Location; pat: Pattern; seen: RefSet): Attributes {.gcsafe.}
@ -588,10 +584,10 @@ proc addFields(recList: PNode; loc: Location; known: var TypeTable; pat: Pattern
proc addFields(recList: PNode; loc: Location; known: var TypeTable; entries: DictionaryEntries; parentName: string): PNode {.discardable.} =
var sortedEntries =
initOrderedTable[Preserve[void], NamedSimplePattern](entries.len)
initOrderedTable[Value, NamedSimplePattern](entries.len)
for key, val in entries.pairs:
sortedEntries[key] = val
sort(sortedEntries) do (x, y: (Preserve[void], NamedSimplePattern)) -> int:
sort(sortedEntries) do (x, y: (Value, NamedSimplePattern)) -> int:
cmp(x[0], y[0])
for key, val in sortedEntries.pairs:
doAssert(key.isSymbol)
@ -834,7 +830,7 @@ proc renderNimBundle*(bundle: Bundle): Table[string, string] =
newEmpty(),
nkStmtList.newTree(
nkCall.newTree(ident"$",
nkCall.newTree(ident"toPreserve", ident"x", embeddedIdent(scm)))))
nkCall.newTree(ident"toPreserves", ident"x", embeddedIdent(scm)))))
procs.add nkProcDef.newTree(
"encode".ident.toExport,
newEmpty(),
@ -849,7 +845,7 @@ proc renderNimBundle*(bundle: Bundle): Table[string, string] =
newEmpty(),
nkStmtList.newTree(
nkCall.newTree(ident"encode",
nkCall.newTree(ident"toPreserve", ident"x", embeddedIdent(scm)))))
nkCall.newTree(ident"toPreserves", ident"x", embeddedIdent(scm)))))
if not unembeddableType.isNil:
procs.add nkProcDef.newTree(
"$".toFieldIdent,
@ -865,7 +861,7 @@ proc renderNimBundle*(bundle: Bundle): Table[string, string] =
newEmpty(),
nkStmtList.newTree(
nkCall.newTree(ident"$",
nkCall.newTree(ident"toPreserve", ident"x"))))
nkCall.newTree(ident"toPreserves", ident"x"))))
procs.add nkProcDef.newTree(
"encode".ident.toExport,
newEmpty(),
@ -880,7 +876,7 @@ proc renderNimBundle*(bundle: Bundle): Table[string, string] =
newEmpty(),
nkStmtList.newTree(
nkCall.newTree(ident"encode", nkCall.newTree(
ident"toPreserve", ident"x"))))
ident"toPreserves", ident"x"))))
var module = newNode(nkStmtList).add(
imports,
typeSection
@ -902,7 +898,7 @@ when isMainModule:
writeFile(path, txt)
stdout.writeLine(path)
import std/[options, os, parseopt]
import std/[os, parseopt]
var inputs: seq[string]
for kind, key, val in getopt():
case kind
@ -934,9 +930,9 @@ when isMainModule:
let raw = readFile inputPath
if raw[0] == 0xb4.char:
var pr = decodePreserves raw
if not fromPreserve(bundle, pr):
if not fromPreserves(bundle, pr):
var schema: Schema
if fromPreserve(schema, pr):
if fromPreserves(schema, pr):
bundle.modules[@[Symbol fileName]] = schema
else:
new bundle

View File

@ -1 +0,0 @@
threads:off

View File

@ -33,7 +33,7 @@ when isMainModule:
if not fileExists inputPath:
quit(inputPath & " does not exist or is not a file")
var schema = parsePreservesSchema(readFile(inputPath))
write(outStream, schema.toPreserve)
write(outStream, schema.toPreserves)
else:
let bundle = Bundle()
@ -53,6 +53,6 @@ when isMainModule:
if bundle.modules.len == 0:
quit "no schemas parsed"
else:
write(outStream, bundle.toPreserve)
write(outStream, bundle.toPreserves)
close(outStream)

View File

@ -15,31 +15,31 @@ proc readVarint(s: Stream): uint =
c = uint s.readUint8
result = result or (c shl shift)
proc decodePreserves*(s: Stream; E = void): Preserve[E] =
proc decodePreserves*(s: Stream): Value =
## Decode a Preserves value from a binary-encoded stream.
if s.atEnd: raise newException(IOError, "End of Preserves stream")
const endMarker = 0x84
let tag = s.readUint8()
case tag
of 0x80: result = Preserve[E](kind: pkBoolean, bool: false)
of 0x81: result = Preserve[E](kind: pkBoolean, bool: true)
of 0x80: result = Value(kind: pkBoolean, bool: false)
of 0x81: result = Value(kind: pkBoolean, bool: true)
of 0x85:
discard decodePreserves(s, E)
result = decodePreserves(s, E)
discard decodePreserves(s)
result = decodePreserves(s)
of 0x86:
result = decodePreserves(s, E)
result = decodePreserves(s)
result.embedded = true
of 0x87:
var N: int
let n = int s.readUint8()
case n
of 4:
result = Preserve[E](kind: pkFloat)
result = Value(kind: pkFloat)
var buf: uint32
N = s.readData(addr buf, sizeof(buf))
bigEndian32(addr result.float, addr buf)
of 8:
result = Preserve[E](kind: pkDouble)
result = Value(kind: pkDouble)
var buf: uint64
N = s.readData(addr buf, sizeof(buf))
bigEndian64(addr result.double, addr buf)
@ -49,7 +49,7 @@ proc decodePreserves*(s: Stream; E = void): Preserve[E] =
of 0xb0:
var n = int s.readVarint()
if n <= sizeof(int):
result = Preserve[E](kind: pkRegister)
result = Value(kind: pkRegister)
if n > 0:
var
buf: array[sizeof(int), byte]
@ -65,7 +65,7 @@ proc decodePreserves*(s: Stream; E = void): Preserve[E] =
bigEndian64(addr result.register, addr buf[0])
else: {.error: "int size " & $buf.len & " not supported here".}
else:
result = Preserve[E](kind: pkBigInt)
result = Value(kind: pkBigInt)
var buf = newSeq[byte](n)
if s.readData(addr buf[0], buf.len) != n:
raise newException(IOError, "short read")
@ -76,7 +76,7 @@ proc decodePreserves*(s: Stream; E = void): Preserve[E] =
else:
result.bigint.fromBytes(buf, bigEndian)
of 0xb1:
result = Preserve[E](kind: pkString, string: newString(s.readVarint()))
result = Value(kind: pkString, string: newString(s.readVarint()))
if result.string.len > 0:
if s.readData(addr result.string[0], result.string.len) != result.string.len:
raise newException(IOError, "short read")
@ -86,48 +86,48 @@ proc decodePreserves*(s: Stream; E = void): Preserve[E] =
let n = s.readData(addr data[0], data.len)
if n != data.len:
raise newException(IOError, "short read")
result = Preserve[E](kind: pkByteString, bytes: data)
result = Value(kind: pkByteString, bytes: data)
of 0xb3:
var data = newString(s.readVarint())
if data.len > 0:
let n = s.readData(addr data[0], data.len)
if n != data.len:
raise newException(IOError, "short read")
result = Preserve[E](kind: pkSymbol, symbol: Symbol data)
result = Value(kind: pkSymbol, symbol: Symbol data)
of 0xb4:
result = Preserve[E](kind: pkRecord)
var label = decodePreserves(s, E)
result = Value(kind: pkRecord)
var label = decodePreserves(s)
while s.peekUint8() != endMarker:
result.record.add decodePreserves(s, E)
result.record.add decodePreserves(s)
result.record.add(move label)
discard s.readUint8()
of 0xb5:
result = Preserve[E](kind: pkSequence)
result = Value(kind: pkSequence)
while s.peekUint8() != endMarker:
result.sequence.add decodePreserves(s, E)
result.sequence.add decodePreserves(s)
discard s.readUint8()
of 0xb6:
result = Preserve[E](kind: pkSet)
result = Value(kind: pkSet)
while s.peekUint8() != endMarker:
incl(result, decodePreserves(s, E))
incl(result, decodePreserves(s))
discard s.readUint8()
of 0xb7:
result = Preserve[E](kind: pkDictionary)
result = Value(kind: pkDictionary)
while s.peekUint8() != endMarker:
result[decodePreserves(s, E)] = decodePreserves(s, E)
result[decodePreserves(s)] = decodePreserves(s)
discard s.readUint8()
of endMarker:
raise newException(ValueError, "invalid Preserves stream")
else:
raise newException(ValueError, "invalid Preserves tag byte 0x" & tag.toHex(2))
proc decodePreserves*(s: string; E = void): Preserve[E] =
proc decodePreserves*(s: string): Value =
## Decode a string of binary-encoded Preserves.
decodePreserves(s.newStringStream, E)
decodePreserves(s.newStringStream)
proc decodePreserves*(s: seq[byte]; E = void): Preserve[E] =
proc decodePreserves*(s: seq[byte]): Value =
## Decode a byte-string of binary-encoded Preserves.
decodePreserves(cast[string](s), E)
decodePreserves(cast[string](s))
type BufferedDecoder* = object
## Type for buffering binary Preserves before decoding.
@ -163,14 +163,14 @@ proc feed*[T: byte|char](dec: var BufferedDecoder; data: openarray[T]) =
if data.len > 0:
dec.feed(unsafeAddr data[0], data.len)
proc decode*(dec: var BufferedDecoder; E = void): (bool, Preserve[E]) =
proc decode*(dec: var BufferedDecoder): (bool, Value) =
## Decode from `dec`. If decoding fails the internal position of the
## decoder does not advance.
if dec.appendPosition > 0:
assert(dec.decodePosition < dec.appendPosition)
dec.stream.setPosition(dec.decodePosition)
try:
result[1] = decodePreserves(dec.stream, E)
result[1] = decodePreserves(dec.stream)
result[0] = true
dec.decodePosition = dec.stream.getPosition()
if dec.decodePosition == dec.appendPosition:

View File

@ -12,7 +12,7 @@ proc writeVarint(s: Stream; n: Natural) =
n = n shr 7
s.write(uint8 n and 0x7f)
proc write*[E](str: Stream; pr: Preserve[E]) =
proc write*(str: Stream; pr: Value) =
## Write the binary-encoding of a Preserves value to a stream.
if pr.embedded: str.write(0x86'u8)
case pr.kind:
@ -110,10 +110,10 @@ proc write*[E](str: Stream; pr: Preserve[E]) =
str.write(value)
str.write(0x84'u8)
of pkEmbedded:
str.write(0x86'u8)
str.write(pr.embed.toPreserve)
# str.write(0x86'u8)
raise newException(ValueError, "cannot encode an embedded object")
proc encode*[E](pr: Preserve[E]): seq[byte] =
proc encode*(pr: Value): seq[byte] =
## Return the binary-encoding of a Preserves value.
let s = newStringStream()
s.write pr

View File

@ -10,7 +10,6 @@ import ../pegs
import ./decoding, ./values
type
Value = Preserve[void]
Frame = tuple[value: Value, pos: int]
Stack = seq[Frame]
@ -226,11 +225,6 @@ proc parsePreserves*(text: string): Value =
assert(stack.len == 1)
stack.pop.value
proc parsePreserves*(text: string; E: typedesc): Preserve[E] =
## Parse a textencoded Preserves `string` to a `Preserve[E]` value for embedded type `E`.
when E is void: parsePreserves(text)
else: mapEmbeds(parsePreserves(text), E)
proc parsePreservesAtom*(text: string): Atom =
## Parse a text-encoded Preserves `string` to a Preserves `Atom`.
let pegParser = peg("Atom", a: Atom):

View File

@ -19,17 +19,17 @@ when isMainModule:
of "preserves_from_json":
let
js = stdin.newFileStream.parseJson
pr = js.toPreserve
pr = js.toPreserves
stdout.newFileStream.write(pr)
of "preserves_from_xml":
let
xn = stdin.newFileStream.parseXml
pr = xn.toPreserveHook(void)
pr = xn.toPreservesHook()
stdout.newFileStream.write(pr)
of "preserves_to_json":
let
pr = stdin.readAll.decodePreserves
js = preserveTo(pr, JsonNode)
js = preservesTo(pr, JsonNode)
if js.isSome:
stdout.writeLine(get js)
else:
@ -37,7 +37,7 @@ when isMainModule:
of "preserves_to_xml":
let pr = stdin.readAll.decodePreserves
var xn: XmlNode
if fromPreserve(xn, pr):
if fromPreserves(xn, pr):
stdout.writeLine(xn)
else:
quit("Preserves not convertable to XML")

View File

@ -1,7 +1,7 @@
# SPDX-FileCopyrightText: ☭ Emery Hemingway
# SPDX-License-Identifier: Unlicense
import std/[base64, endians, math, sequtils, streams, strutils, unicode]
import std/[base64, endians, math, sequtils, streams, strutils]
import bigints
import ./values
@ -42,7 +42,7 @@ proc writeSymbol(stream: Stream; sym: string) =
writeEscaped(stream, sym, '|')
write(stream, '|')
proc writeText*[E](stream: Stream; pr: Preserve[E]; mode = textPreserves) =
proc writeText*(stream: Stream; pr: Value; mode = textPreserves) =
## Encode Preserves to a `Stream` as text.
if pr.embedded: write(stream, "#!")
case pr.kind:
@ -153,17 +153,20 @@ proc writeText*[E](stream: Stream; pr: Preserve[E]; mode = textPreserves) =
write(stream, '}')
of pkEmbedded:
write(stream, "#!")
when compiles($pr.embed) and not E is void:
write(stream, $pr.embed)
if pr.embeddedRef.isNil:
write(stream, "<null>")
else:
write(stream, "")
when compiles($pr.embed):
write(stream, $pr.embed)
else:
write(stream, "")
proc `$`*(sym: Symbol): string =
var stream = newStringStream()
writeSymbol(stream, sym.string)
result = move stream.data
proc `$`*[E](pr: Preserve[E]): string =
proc `$`*(pr: Value): string =
## Generate the textual representation of ``pr``.
var stream = newStringStream()
writeText(stream, pr, textPreserves)

View File

@ -1,7 +1,7 @@
# SPDX-FileCopyrightText: ☭ Emery Hemingway
# SPDX-License-Identifier: Unlicense
import std/[hashes, math, options, sets, sequtils, tables]
import std/[algorithm, hashes, math, options, sets, sequtils, tables]
import bigints
@ -44,9 +44,7 @@ type
else:
discard
Preserve*[E] = object
embedded*: bool
## Flag to mark embedded Preserves
Value* = object
case kind*: PreserveKind
of pkBoolean:
bool*: bool
@ -65,26 +63,33 @@ type
of pkSymbol:
symbol*: Symbol
of pkRecord:
record*: seq[Preserve[E]] # label is last
record*: seq[Value] # label is last
of pkSequence:
sequence*: seq[Preserve[E]]
sequence*: seq[Value]
of pkSet:
set*: seq[Preserve[E]]
set*: seq[Value]
# TODO: HashSet
of pkDictionary:
dict*: seq[DictEntry[E]]
dict*: seq[DictEntry]
# TODO: Tables
of pkEmbedded:
embed*: E
embeddedRef*: EmbeddedRef
embedded*: bool
## Flag to mark embedded Preserves value
DictEntry*[E] = tuple[key: Preserve[E], val: Preserve[E]]
DictEntry* = tuple[key: Value, val: Value]
EmbeddedRef* = ref RootObj
EmbeddedObj* = RootObj
## Object refs embedded in Preserves `Value`s must inherit from `EmbeddedObj`.
## At the moment this is just an alias to `RootObj` but this may change in the future.
func `===`[T: SomeFloat](a, b: T): bool =
## Compare where Nan == NaN.
let class = a.classify
(class == b.classify) and ((class notin {fcNormal,fcSubnormal}) or (a == b))
func `==`*[A, B](x: Preserve[A]; y: Preserve[B]): bool =
func `==`*(x, y: Value): bool =
## Check `x` and `y` for equivalence.
if x.kind == y.kind and x.embedded == y.embedded:
case x.kind
@ -126,11 +131,7 @@ func `==`*[A, B](x: Preserve[A]; y: Preserve[B]): bool =
(x.dict[i].key == y.dict[i].key) and
(x.dict[i].val == y.dict[i].val)
of pkEmbedded:
when A is B:
when A is void:
result = true
else:
result = x.embed == y.embed
result = x.embeddedRef == y.embeddedRef
proc `<`(x, y: string | seq[byte]): bool =
for i in 0 .. min(x.high, y.high):
@ -138,7 +139,7 @@ proc `<`(x, y: string | seq[byte]): bool =
if x[i] != y[i]: return false
x.len < y.len
proc `<`*[A, B](x: Preserve[A]; y: Preserve[B]): bool =
proc `<`*(x, y: Value): bool =
## Preserves have a total order over values. Check if `x` is ordered before `y`.
if x.embedded != y.embedded:
result = y.embedded
@ -186,19 +187,18 @@ proc `<`*[A, B](x: Preserve[A]; y: Preserve[B]): bool =
if x.dict[i].val != y.dict[i].val: return false
result = x.dict.len < y.dict.len
of pkEmbedded:
when (not A is void) and (A is B):
result = x.embed < y.embed
result = x.embeddedRef < y.embeddedRef
func cmp*[E](x, y: Preserve[E]): int =
func cmp*(x, y: Value): int =
## Compare by Preserves total ordering.
if x == y: 0
elif x < y: -1
else: 1
proc sort*[E](pr: var Preserve[E]) = sort(pr.sequence, cmp)
proc sort*(pr: var Value) = sort(pr.sequence, cmp)
## Sort a Preserves array by total ordering.
proc hash*[E](pr: Preserve[E]): Hash =
proc hash*(pr: Value): Hash =
## Produce a `Hash` of `pr` for use with a `HashSet` or `Table`.
var h = hash(pr.kind.int) !& hash(pr.embedded)
case pr.kind
@ -231,16 +231,10 @@ proc hash*[E](pr: Preserve[E]): Hash =
for (key, val) in pr.dict.items:
h = h !& hash(key) !& hash(val)
of pkEmbedded:
when E is void:
h = h !& hash(pr.embed)
else:
if pr.embed.isNil:
h = h !& hash(false)
else:
h = h !& hash(pr.embed)
h = h !& hash(cast[uint](addr pr.embeddedRef[]))
!$h
proc `[]`*(pr: Preserve; i: int): Preserve =
proc `[]`*(pr: Value; i: int): Value =
## Select an indexed value from ``pr``.
## Only valid for records and sequences.
case pr.kind
@ -249,16 +243,16 @@ proc `[]`*(pr: Preserve; i: int): Preserve =
else:
raise newException(ValueError, "Preserves value is not indexable")
proc `[]=`*(pr: var Preserve; i: Natural; val: Preserve) =
proc `[]=`*(pr: var Value; i: Natural; val: Value) =
## Assign an indexed value into ``pr``.
## Only valid for records and sequences.
case pr.kind
of pkRecord: pr.record[i] = val
of pkSequence: pr.sequence[i] = val
else:
raise newException(ValueError, "`Preserves value is not indexable")
raise newException(ValueError, "Preserves value is not indexable")
proc `[]=`*(pr: var Preserve; key, val: Preserve) =
proc `[]=`*(pr: var Value; key, val: Value) =
## Insert `val` by `key` in the Preserves dictionary `pr`.
for i in 0..pr.dict.high:
if key < pr.dict[i].key:
@ -269,7 +263,7 @@ proc `[]=`*(pr: var Preserve; key, val: Preserve) =
return
pr.dict.add((key, val, ))
proc incl*(pr: var Preserve; key: Preserve) =
proc incl*(pr: var Value; key: Value) =
## Include `key` in the Preserves set `pr`.
for i in 0..pr.set.high:
if key < pr.set[i]:
@ -277,7 +271,7 @@ proc incl*(pr: var Preserve; key: Preserve) =
return
pr.set.add(key)
proc excl*(pr: var Preserve; key: Preserve) =
proc excl*(pr: var Value; key: Value) =
## Exclude `key` from the Preserves set `pr`.
for i in 0..pr.set.high:
if pr.set[i] == key:

View File

@ -54,11 +54,11 @@ type
`ref`*: Ref
Definitions* = Table[Symbol, Definition]
`AtomKind`* {.preservesOr, pure.} = enum
`Boolean`, `Float`, `Double`, `SignedInteger`, `String`, `ByteString`,
`Symbol`
Definitions* = Table[Symbol, Definition]
DictionaryEntries* = Table[Preserve[void], NamedSimplePattern]
DictionaryEntries* = Table[Value, NamedSimplePattern]
NamedPatternKind* {.pure.} = enum
`named`, `anonymous`
`NamedPattern`* {.acyclic, preservesOr.} = ref object
@ -79,7 +79,7 @@ type
`interface`*: SimplePattern
SimplePatternLit* {.preservesRecord: "lit".} = object
`value`*: Preserve[void]
`value`*: Value
SimplePatternSeqof* {.acyclic, preservesRecord: "seqof".} = ref object
`pattern`*: SimplePattern
@ -198,7 +198,7 @@ proc `$`*(x: Ref | ModulePath | Bundle | CompoundPattern | Modules |
Schema |
Pattern |
Binding): string =
`$`(toPreserve(x))
`$`(toPreserves(x))
proc encode*(x: Ref | ModulePath | Bundle | CompoundPattern | Modules |
EmbeddedTypeName |
@ -212,4 +212,4 @@ proc encode*(x: Ref | ModulePath | Bundle | CompoundPattern | Modules |
Schema |
Pattern |
Binding): seq[byte] =
encode(toPreserve(x))
encode(toPreserves(x))

View File

@ -1,4 +1,4 @@
# SPDX-FileCopyrightText: 2021 ☭ Emery Hemingway
# SPDX-FileCopyrightText: ☭ Emery Hemingway
# SPDX-License-Identifier: Unlicense
import std/[strutils, tables]
@ -9,7 +9,6 @@ import npeg
import ../preserves, ./schema, ./pegs
type
Value = Preserve[void]
Stack = seq[tuple[node: Value, pos: int]]
ParseState = object
schema: SchemaField0
@ -57,7 +56,7 @@ template pushStack(n: Value) =
assert(p.stack.len > 0, capture[0].s)
proc toSymbolLit(s: string): Value =
initRecord[void](toSymbol"lit", toSymbol s)
initRecord(toSymbol"lit", toSymbol s)
proc match(text: string; p: var ParseState)
@ -75,7 +74,7 @@ const parser = peg("Schema", p: ParseState):
var r = popStack()
p.schema.embeddedType =
EmbeddedTypeName(orKind: EmbeddedTypeNameKind.Ref)
validate p.schema.embeddedType.`ref`.fromPreserve(r)
validate p.schema.embeddedType.`ref`.fromPreserves(r)
Include <- "include" * S * '"' * >(+Preserves.char) * '"':
var path: string
@ -93,14 +92,14 @@ const parser = peg("Schema", p: ParseState):
var
node = popStack()
def: Definition
if not fromPreserve(def, node):
if not def.fromPreserves(node):
raise newException(ValueError, "failed to convert " & $1 & " to a Definition: " & $node)
p.schema.definitions[Symbol $1] = def
p.stack.setLen(0)
OrDelim <- *LineComment * '/' * S * *LineComment
OrPattern <- ?OrDelim * AltPattern * +(S * OrDelim * AltPattern):
var node = initRecord(toSymbol("or"), toPreserve takeStackAt())
var node = initRecord(toSymbol("or"), takeStackAt().toPreserves)
pushStack node
AltPattern <-
@ -110,21 +109,21 @@ const parser = peg("Schema", p: ParseState):
AltLiteralPattern
AltNamed <- atId * ?Annotation * Pattern:
var n = toPreserve @[toPreserve $1] & takeStackAt()
var n = toPreserves(@[toPreserves $1] & takeStackAt())
pushStack n
AltRecord <- '<' * id * *NamedPattern * '>':
var n = toPreserve @[
toPreserve $1,
var n = toPreserves @[
toPreserves $1,
initRecord(
toSymbol"rec",
toSymbolLit $1,
initRecord(toSymbol"tuple", toPreserve takeStackAt()))]
initRecord(toSymbol"tuple", toPreserves takeStackAt()))]
pushStack n
AltRef <- Ref:
var r = popStack()
var n = toPreserve @[r[1].symbol.string.toPreserve, r]
var n = toPreserves @[r[1].symbol.string.toPreserves, r]
pushStack n
AltLiteralPattern <-
@ -138,13 +137,13 @@ const parser = peg("Schema", p: ParseState):
of "#f": "false"
of "#t": "true"
else: $1
var n = toPreserve @[
toPreserve id,
var n = toPreserves @[
toPreserves id,
initRecord(toSymbol"lit", parsePreserves $1)]
pushStack n
AndPattern <- ?'&' * S * NamedPattern * +('&' * S * NamedPattern):
var node = initRecord(toSymbol("and"), toPreserve takeStackAt())
var node = initRecord(toSymbol("and"), toPreserves takeStackAt())
pushStack node
Pattern <- SimplePattern | CompoundPattern
@ -230,7 +229,7 @@ const parser = peg("Schema", p: ParseState):
if capture.len == 2:
var n = initRecord(toSymbol"rec",
toSymbolLit $1,
initRecord(toSymbol"tuple", toPreserve takeStackAfter()))
initRecord(toSymbol"tuple", toPreserves takeStackAfter()))
pushStack n
else:
var n = initRecord(toSymbol"rec", takeStackAfter())
@ -243,12 +242,12 @@ const parser = peg("Schema", p: ParseState):
var n = initRecord(
toSymbol"rec",
toSymbolLit $1,
initRecord(toSymbol"tuplePrefix", toPreserve fields, tail))
initRecord(toSymbol"tuplePrefix", toPreserves fields, tail))
pushStack n
TuplePattern <-
'[' * S * *NamedPattern * ']':
var n = initRecord(toSymbol"tuple", toPreserve takeStackAfter())
var n = initRecord(toSymbol"tuple", toPreserves takeStackAfter())
pushStack n
VariableTuplePattern <-
@ -256,11 +255,11 @@ const parser = peg("Schema", p: ParseState):
var fields = takeStackAfter()
var tail = fields.pop
tail[1] = initRecord(toSymbol"seqof", tail[1])
var node = initRecord(toSymbol"tuplePrefix", toPreserve fields, tail)
var node = initRecord(toSymbol"tuplePrefix", toPreserves fields, tail)
pushStack node
DictionaryPattern <- '{' * *(S * >Value * S * ':' * S * NamedSimplePattern * ?',') * S * '}':
var dict = initDictionary(void)
var dict = initDictionary()
for i in countDown(pred capture.len, 1):
let key = toSymbol capture[i].s
dict[key] = initRecord("named", key, popStack())
@ -320,5 +319,5 @@ when isMainModule:
if txt != "":
let
scm = parsePreservesSchema(txt)
pr = toPreserve scm
pr = toPreserves scm
stdout.newFileStream.writeText(pr, textPreserves)

View File

@ -1,156 +0,0 @@
# SPDX-FileCopyrightText: ☭ 2021 Emery Hemingway
# SPDX-License-Identifier: Unlicense
import std/[sequtils, tables]
import spryvm/spryvm
import ../preserves
type
PreservesNode* = ref object of Value
preserve: Preserve[void]
EmbeddedNode* = ref object of PreservesNode
ByteStringNode* = ref object of StringVal
RecordNode* = ref object of Blok
SetNode = ref object of PreservesNode
method eval*(self: PreservesNode; spry: Interpreter): Node =
self
method `$`*(self: PreservesNode): string =
$self.preserve
method typeName*(self: PreservesNode): string =
"preserves-value"
method typeName*(self: EmbeddedNode): string =
"preserves-embedded-value"
method typeName*(self: ByteStringNode): string =
"preserves-bytestring"
method typeName*(self: RecordNode): string =
"preserves-record"
method typeName*(self: SetNode): string =
"preserves-set"
proc toSpry(pr: Preserve[void], spry: Interpreter): Node =
if pr.embedded:
result = EmbeddedNode(preserve: pr)
# TODO: need to be able to manipulate these
else:
case pr.kind
of pkBoolean:
result = boolVal(pr.bool, spry)
of pkFloat:
result = newValue(pr.float)
of pkDouble:
result = newValue(pr.double)
of pkRegister:
result = newValue(pr.register)
of pkBigInt:
raiseAssert "Arbitrary sized integers not supported by Spry implementation"
of pkString:
result = newValue(pr.string)
of pkByteString:
result = ByteStringNode(value: cast[string](pr.bytes))
of pkSymbol:
result =
if pr.symbol == Symbol"null": newNilVal()
else: newLitWord(spry, string pr.symbol)
of pkRecord:
var comp = RecordNode()
proc f(pr: Preserve[void]): Node = toSpry(pr, spry)
comp.nodes = map(pr.record, f)
result = comp
of pkSequence:
var blk = newBlok()
for e in pr.sequence: blk.add toSpry(e, spry)
result = blk
of pkSet:
result = SetNode(preserve: pr)
of pkDictionary:
var map = newMap()
for (key, val) in pr.dict.items:
map[toSpry(key, spry)] = toSpry(val, spry)
result = map
of pkEmbedded:
result = EmbeddedNode(preserve: pr)
proc toPreserveHook*(node: Node; E: typedesc): Preserve[E] =
if node of PreservesNode:
result = PreservesNode(node).preserve
elif node of RecordNode:
result = Preserve[E](kind: pkRecord)
var comp = RecordNode(node)
proc f(child: Node): Preserve[void] = toPreserve(child, void)
result.record = map(comp.nodes, f)
elif node of ByteStringNode:
result = toPreserve(cast[seq[byte]](ByteStringNode(node).value), E)
elif node of Blok:
var blk = Blok(node)
result = initSequence(blk.nodes.len, E)
for i, child in blk.nodes: result.sequence[i] = toPreserve(child, E)
elif node of Map:
result = initDictionary(E)
for key, val in Map(node).bindings:
result[toPreserve(key, E)] = toPreserve(val, E)
elif node of StringVal:
result = toPreserve(StringVal(node).value, E)
elif node of LitWord:
result = toSymbol(LitWord(node).word, E)
elif node of IntVal:
result = toPreserve(IntVal(node).value, E)
elif node of FloatVal:
result = toPreserve(FloatVal(node).value, E)
elif node of BoolVal:
result = toPreserve(BoolVal(node).value, E)
else: # node of NilVal:
result = toSymbol("null", E)
when isMainModule:
var
node: Node
pr = toPreserveHook(node, void)
proc addPreserves*(spry: Interpreter) =
nimFunc("parsePreserves"):
let node = evalArg(spry)
if node of StringVal:
let str = StringVal(node).value
result = PreservesNode(preserve: parsePreserves(str))
nimFunc("decodePreserves"):
let node = evalArg(spry)
if node of StringVal:
let str = StringVal(node).value
result = PreservesNode(preserve: decodePreserves(cast[seq[byte]](str)))
nimMeth("encodePreserves"):
let node = evalArgInfix(spry)
if node of PreservesNode:
var bin = encode PreservesNode(node).preserve
result = newValue(cast[string](bin))
nimFunc("fromPreserves"):
let node = evalArg(spry)
if node of PreservesNode:
let pr = PreservesNode(node).preserve
return toSpry(pr, spry)
nimMeth("toPreserves"):
let node = evalArgInfix(spry)
PreservesNode(preserve: node.toPreserve)
nimMeth("arity"):
let node = evalArgInfix(spry)
if node of RecordNode:
return newValue(pred SeqComposite(node).nodes.len)
nimMeth("label"):
let node = evalArgInfix(spry)
if node of RecordNode:
let rec = RecordNode(node)
return rec.nodes[rec.nodes.high]

View File

@ -1,2 +0,0 @@
multimethods:on
nilseqs:on

View File

@ -1,36 +1,36 @@
# SPDX-FileCopyrightText: 2021 ☭ Emery Hemingway
# SPDX-FileCopyrightText: ☭ Emery Hemingway
# SPDX-License-Identifier: Unlicense
import std/[parseutils, strtabs, tables, xmltree]
import std/[parseutils, strtabs, xmltree]
import ../preserves
proc toPreserveFromString*(s: string; E: typedesc): Preserve[E] =
proc toPreservesFromString*(s: string): Value =
# This is a bad and slow thing to do, but that is XML.
case s
of "false", "no", "off":
result = toPreserve(false, E)
result = toPreserves(false)
of "true", "yes", "on":
result = toPreserve(true, E)
result = toPreserves(true)
else:
var
n: BiggestInt
f: BiggestFloat
if parseBiggestInt(s, n) == s.len:
result = toPreserve(n, E)
result = toPreserves(n)
elif parseHex(s, n) == s.len:
result = toPreserve(n, E)
result = toPreserves(n)
elif parseFloat(s, f) == s.len:
result = toPreserve(f, E)
result = toPreserves(f)
else:
result = toPreserve(s, E)
result = toPreserves(s)
proc toPreserveHook*(xn: XmlNode; E: typedesc): Preserve[E] =
proc toPreservesHook*(xn: XmlNode): Value =
if xn.kind == xnElement:
result = Preserve[E](kind: pkRecord)
result = Value(kind: pkRecord)
if not xn.attrs.isNil:
var attrs = initDictionary(E)
var attrs = initDictionary()
for xk, xv in xn.attrs.pairs:
attrs[toSymbol(xk, E)] = toPreserveFromString(xv, E)
attrs[toSymbol(xk)] = toPreservesFromString(xv)
result.record.add(attrs)
var isText = xn.len > 0
# escaped text is broken up and must be concatenated
@ -39,20 +39,20 @@ proc toPreserveHook*(xn: XmlNode; E: typedesc): Preserve[E] =
isText = false
break
if isText:
result.record.add(toPreserve(xn.innerText, E))
result.record.add(toPreserves(xn.innerText))
else:
for child in xn.items:
case child.kind
of xnElement:
result.record.add(toPreserveHook(child, E))
result.record.add(toPreservesHook(child))
of xnText, xnVerbatimText, xnCData, xnEntity:
result.record.add(toPreserve(text(child), E))
result.record.add(toPreserves(text(child)))
of xnComment:
discard
result.record.add(toSymbol(xn.tag, E))
result.record.add(toSymbol(xn.tag))
# record labels are stored after the fields
proc toUnquotedString[E](pr: Preserve[E]): string {.inline.} =
proc toUnquotedString(pr: Value): string {.inline.} =
case pr.kind
of pkString:
pr.string
@ -60,7 +60,7 @@ proc toUnquotedString[E](pr: Preserve[E]): string {.inline.} =
if pr.bool: "true" else: "false"
else: $pr
proc fromPreserveHook*[E](xn: var XmlNode; pr: Preserve[E]): bool =
proc fromPreservesHook*(xn: var XmlNode; pr: Value): bool =
if pr.kind == pkRecord and pr.label.kind == pkSymbol:
xn = newElement($pr.label)
var i: int
@ -74,7 +74,7 @@ proc fromPreserveHook*[E](xn: var XmlNode; pr: Preserve[E]): bool =
xn.add newText(e.string)
else:
var child: XmlNode
result = fromPreserveHook(child, e)
result = fromPreservesHook(child, e)
if not result: return
xn.add child
inc i
@ -82,5 +82,5 @@ proc fromPreserveHook*[E](xn: var XmlNode; pr: Preserve[E]): bool =
when isMainModule:
var xn = newElement("foobar")
var pr = xn.toPreserveHook(void)
assert fromPreserveHook(xn, pr)
var pr = xn.toPreservesHook()
assert fromPreservesHook(xn, pr)

View File

@ -1 +1 @@
switch("path", "$projectDir/../src")
switch("path", "$projectDir/../src")

View File

@ -9,7 +9,7 @@ suite "BufferedDecoder":
test "half-string":
var
buf = newBufferedDecoder()
pr = Preserve[void](kind: pkByteString, bytes: newSeq[byte](23))
pr = Value(kind: pkByteString, bytes: newSeq[byte](23))
ok: bool
for i, _ in pr.bytes:
pr.bytes[i] = byte(i)

View File

@ -6,44 +6,47 @@ import preserves, preserves/xmlhooks
type
Route {.preservesRecord: "route".} = object
`transports`*: seq[Preserve[void]]
`pathSteps`* {.preservesTupleTail.}: seq[Preserve[void]]
`transports`*: seq[Value]
`pathSteps`* {.preservesTupleTail.}: seq[Value]
suite "conversions":
test "dictionary":
type Bar = tuple
s: string
type Foobar {.preservesDictionary.} = object
a, b: int
a: int
b: seq[int]
c {.preservesEmbedded.}: Bar
let
c = Foobar(a: 1, b: 2, c: ("ku", ))
b = toPreserve(c)
a = preserveTo(b, Foobar)
check($b == """{a: 1 b: 2 c: #!["ku"]}""")
check(a.isSome and (get(a) == c))
c = Foobar(a: 1, b: @[2], c: ("ku", ))
b = toPreserves(c)
a = preservesTo(b, Foobar)
check($b == """{a: 1 b: [2] c: #!["ku"]}""")
check(a.isSome)
if a.isSome: check(get(a) == c)
check(b.kind == pkDictionary)
test "records":
type Bar {.preservesRecord: "bar".} = object
s: string
type Foobar {.preservesRecord: "foo".} = object
a, b: int
a: int
b: seq[int]
c: Bar
let
tup = Foobar(a: 1, b: 2, c: Bar(s: "ku", ))
prs = toPreserve(tup)
tup = Foobar(a: 1, b: @[2], c: Bar(s: "ku", ))
prs = toPreserves(tup)
check(prs.kind == pkRecord)
check($prs == """<foo 1 2 <bar "ku">>""")
check(preserveTo(prs, Foobar) == some(tup))
check($prs == """<foo 1 [2] <bar "ku">>""")
check(preservesTo(prs, Foobar) == some(tup))
test "tables":
var a: Table[int, string]
for i, s in ["a", "b", "c"]: a[i] = s
let b = toPreserve(a)
let b = toPreserves(a)
check($b == """{0: "a" 1: "b" 2: "c"}""")
var c: Table[int, string]
check(fromPreserve(c, b))
check(fromPreserves(c, b))
check(a == c)
test "XML":
@ -61,17 +64,35 @@ suite "conversions":
<rect x="1" y="1" width="998" height="298" fill="none" stroke="blue" stroke-width="2"/>
</svg>
"""
var pr = toPreserve(b, void)
var pr = toPreserves(b)
checkpoint $pr
check fromPreserve(a, pr)
check fromPreserves(a, pr)
test "preservesTupleTail":
let pr = parsePreserves """<route [<tcp "localhost" 1024>] <ref {oid: "syndicate" sig: #x"69ca300c1dbfa08fba692102dd82311a"}>>"""
var route: Route
check route.fromPreserve(pr)
check route.fromPreserves(pr)
test "ebedded":
type
Foo {.preservesRecord: "foo".} = object
n: int
bar: Bar
Bar = ref object of RootObj
x: int
Baz = ref object of RootObj
x: int
let a = initRecord("foo", 9.toPreserves, embed Bar(x: 768))
checkpoint $a
check a.preservesTo(Foo).isSome
let b = initRecord("foo", 2.toPreserves, embed Baz(x: 999))
checkpoint $b
check not b.preservesTo(Foo).isSome
suite "toPreserve":
template check(p: Preserve; s: string) =
template check(p: Value; s: string) =
test s: check($p == s)
check false.toPreserve, "#f"
check [0, 1, 2, 3].toPreserve, "[0 1 2 3]"
check false.toPreserves, "#f"
check [0, 1, 2, 3].toPreserves, "[0 1 2 3]"

View File

@ -27,12 +27,12 @@ suite "parse":
for (txt, bin) in examples:
test txt:
checkpoint(txt)
let test = parsePreserves(txt, int)
let test = parsePreserves(txt)
checkpoint($test)
block:
let
a = test
b = decodePreserves(bin, int)
b = decodePreserves(bin)
check(a == b)
block:
let

View File

@ -57,7 +57,7 @@ for i, jsText in testVectors:
checkpoint(jsText)
let
control = parseJson jsText
x = control.toPreserve
x = control.toPreserves
checkpoint($x)
var stream = newStringStream()
stream.write(x)

View File

@ -4,29 +4,27 @@
import std/unittest
import preserves
type Value = Preserve[void]
const upstreamTestfile {.strdefine.} = ""
proc strip(pr: Preserve): Preserve = pr
proc strip(pr: Value): Value = pr
proc encodeBinary(pr: Value): Value =
result = encode(pr).toPreserve
result = encode(pr).toPreserves
checkpoint("encoded binary: " & $result)
proc looseEncodeBinary(pr: Value): Value =
result = encode(pr).toPreserve
result = encode(pr).toPreserves
checkpoint("loose encoded binary: " & $result)
proc annotatedBinary(pr: Value): Value =
result = encode(pr).toPreserve
result = encode(pr).toPreserves
checkpoint("annotated binary: " & $result)
proc decodeBinary(pr: Value): Value =
result = decodePreserves(pr.bytes)
proc encodeText(pr: Value): Value =
result = ($pr).toPreserve
result = ($pr).toPreserves
checkpoint("encoded text: " & result.string)
proc decodeText(pr: Value): Value =
@ -34,7 +32,7 @@ proc decodeText(pr: Value): Value =
checkpoint("decoded text " & $pr)
if upstreamTestfile != "":
let samples = readFile(upstreamTestfile).parsePreserves(void)
let samples = readFile(upstreamTestfile).parsePreserves()
assert samples.isRecord("TestCases")
var binary, annotatedValue, stripped, text, bytes: Value

View File

@ -10,7 +10,7 @@ suite "step":
"""
var o = some data
for i in [1.toPreserve, 1.toPreserve, "b".toPreserve]:
for i in [1.toPreserves, 1.toPreserves, "b".toPreserves]:
test $i:
o = step(get o, i)
check o.isSome