Add Atom type

Type for holding constant Preserves values create at compile-time.
This is a prerequisite for making embedded values "ref RootObj".
It is also requesite for making Value a ref object.
This commit is contained in:
Emery Hemingway 2023-12-27 17:21:11 +02:00
parent 441bd253b0
commit c2bce1404a
6 changed files with 186 additions and 13 deletions

View File

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

View File

@ -209,6 +209,8 @@ iterator pairs*[E](pr: Preserve[E]): DictEntry[E] =
assert(pr.kind == pkDictionary, "not a dictionary")
for i in 0..pr.dict.high: yield pr.dict[i]
func isAtomic*(pr: Preserve): bool = pr.kind in atomKinds
func isBoolean*(pr: Preserve): bool {.inline.} = pr.kind == pkBoolean
## Check if ``pr`` is a Preserve boolean.
@ -414,8 +416,8 @@ proc toPreserve*[T](x: T; E = void): Preserve[E] {.gcsafe.} =
v.embedded = true
template fieldToPreserve(key: string; val: typed): Preserve {.used.} =
when x.dot(key).hasCustomPragma(preservesLiteral):
const lit = parsePreserves(x.dot(key).getCustomPragmaVal(preservesLiteral))
cast[Preserve[E]](lit)
const atom = x.dot(key).getCustomPragmaVal(preservesLiteral).parsePreservesAtom
atom.toPreserveHook(E)
else:
toPreserve(val, E)
when T.hasCustomPragma(unpreservable):
@ -465,6 +467,28 @@ proc toPreserve*[T](x: T; E = void): Preserve[E] {.gcsafe.} =
# the hook doesn't compile but produces a useful error
trace T, " -> ", result
proc toPreserveHook*(a: Atom; E: typedesc): Preserve[E] =
result = Preserve[E](kind: a.kind)
case a.kind
of pkBoolean:
result.bool = a.bool
of pkFloat:
result.float = a.float
of pkDouble:
result.double = a.double
of pkRegister:
result.register = a.register
of pkBigInt:
result.bigint = a.bigint
of pkString:
result.string = a.string
of pkByteString:
result.bytes = a.bytes
of pkSymbol:
result.symbol = a.symbol
else:
discard
proc toPreserveHook*[T](set: HashSet[T]; E: typedesc): Preserve[E] =
## Hook for preserving ``HashSet``.
result = Preserve[E](kind: pkSet, set: newSeqOfCap[Preserve[E]](set.len))
@ -489,6 +513,69 @@ func containsNativeEmbeds[E](pr: Preserve[E]): bool =
elif pr.kind == pkEmbedded:
result = true
proc fromAtom*[T](v: var T; a: ATom): bool =
if T is Atom:
v = a
result = true
if T is Preserve:
v = a.toPreservesHook
result = true
elif T is enum:
if a.kind == pkSymbol:
try:
v = parseEnum[T](string a.symbol)
result = true
except ValueError: discard
elif T is bool:
if a.kind == pkBoolean:
v = a.bool
result = true
elif T is SomeInteger:
if a.kind == pkRegister:
result = a.register.T < high(T)
if result:
v = T a.register
elif a.kind == pkBigInt:
var o = toInt[T](a.bigint)
result = o.isSome
if result: v = o.get
elif T is seq[byte]:
if a.kind == pkByteString:
v = a.bytes
result = true
elif T is float32:
if a.kind == pkFloat:
v = a.float
result = true
elif T is float64:
case a.kind
of pkFloat:
v = a.float
result = true
of pkDouble:
v = a.double
result = true
else: discard
elif T is Ordinal | SomeInteger:
if a.kind == pkRegister:
v = T(a.register)
result = int(v) == a.register
elif a.kind == pkBigInt:
var o = toInt[T](a.bigint)
if o.isSome:
v = get o
result = true
elif T is string:
if a.kind == pkString:
v = a.string
result = true
elif T is Symbol:
if a.kind == pkSymbol:
v = a.symbol
result = true
elif T is distinct:
result = fromAtom(v.distinctBase, a)
proc fromPreserve*[T, E](v: var T; pr: Preserve[E]): bool {.gcsafe.} =
## Inplace version of `preserveTo`. Returns ``true`` on
## a complete match, otherwise returns ``false``.
@ -609,8 +696,8 @@ proc fromPreserve*[T, E](v: var T; pr: Preserve[E]): bool {.gcsafe.} =
elif T is object:
template fieldFromPreserve(key: string; val: typed; pr: Preserve[E]): bool {.used.} =
when v.dot(key).hasCustomPragma(preservesLiteral):
const lit = parsePreserves(v.dot(key).getCustomPragmaVal(preservesLiteral))
pr == lit
const atom = v.dot(key).getCustomPragmaVal(preservesLiteral).parsePreservesAtom
pr == atom.toPreserveHook(E)
else:
fromPreserve(val, pr)
when T.hasCustomPragma(unpreservable):

View File

@ -2,6 +2,6 @@ include_rules
NIM_FLAGS += --path:$(TUP_CWD)/..
: foreach preserves_schema_nim.nim schemaparse.nim |> !nim_bin |> $(BIN_DIR)/%B | $(BIN_DIR)/<%B>
DOT_FILES = ../../Document.dot ../../Schema.dot
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

View File

@ -102,11 +102,9 @@ proc pushHexNibble[T](result: var T; c: char) =
else: return
result = (result shl 4) or n
proc parsePreserves*(text: string): Preserve[void] =
## Parse a text-encoded Preserves `string` to a `Preserve` value.
runnableExamples:
assert parsePreserves"[ 1 2 3 ]" == [ 1, 2, 3 ].toPreserve
const pegParser = peg("Document", stack: Stack):
proc parsePreserves*(text: string): Value =
## Parse a text-encoded Preserves `string` to a Preserves `Value`.
let pegParser = peg("Document", stack: Stack):
# Override rules from pegs.nim
Document <- Preserves.Document
@ -219,7 +217,7 @@ proc parsePreserves*(text: string): Preserve[void] =
pushStack val
Preserves.Compact <- Preserves.Compact:
pushStack decodePreserves(stack.pop.value.bytes, void)
pushStack decodePreserves(stack.pop.value.bytes)
var stack: Stack
let match = pegParser.match(text, stack)
@ -229,6 +227,69 @@ proc parsePreserves*(text: string): Preserve[void] =
stack.pop.value
proc parsePreserves*(text: string; E: typedesc): Preserve[E] =
## Parse a text-encoded Preserves `string` to a `Preserve[E]` value for embedded type `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):
# Override rules from pegs.nim
Atom <- ?"#!" * Preserves.Atom
Preserves.Boolean <- Preserves.Boolean:
case $0
of "#f": a = Atom(kind: pkBoolean)
of "#t": a = Atom(kind: pkBoolean, bool: true)
else: discard
Preserves.Float <- Preserves.Float:
a = Atom(kind: pkFloat, float: parseFloat($1))
Preserves.Double <- Preserves.Double:
a = Atom(kind: pkDouble)
discard parseBiggestFloat($0, a.double)
Preserves.FloatRaw <- Preserves.FloatRaw:
var reg: uint32
for c in $1: pushHexNibble(reg, c)
a = Atom(kind: pkFloat, float: cast[float32](reg))
Preserves.DoubleRaw <- Preserves.DoubleRaw:
var reg: uint64
for c in $1: pushHexNibble(reg, c)
a = Atom(kind: pkDouble, double: cast[float64](reg))
Preserves.SignedInteger <- Preserves.SignedInteger:
var
big = initBigInt($0)
small = toInt[int](big)
if small.isSome:
a = Atom(kind: pkRegister, register: small.get)
else:
a = Atom(kind: pkBigInt, bigint: big)
Preserves.String <- Preserves.String:
a = Atom(kind: pkString, string: newStringOfCap(len($1)))
unescape(a.string, $1)
if validateUtf8(a.string) != -1:
raise newException(ValueError, "Preserves text contains an invalid UTF-8 sequence")
Preserves.charByteString <- Preserves.charByteString:
a = Atom(kind: pkByteString, bytes: newSeqOfCap[byte](len($1)))
unescape(a.bytes, $1)
Preserves.hexByteString <- Preserves.hexByteString:
a = Atom(kind: pkByteString, bytes: cast[seq[byte]](parseHexStr(joinWhitespace($1))))
Preserves.b64ByteString <- Preserves.b64ByteString:
a = Atom(kind: pkByteString, bytes: cast[seq[byte]](base64.decode(joinWhitespace($1))))
Preserves.Symbol <- Preserves.Symbol:
var buf = newStringOfCap(len($1))
unescape(buf, $1)
a = Atom(kind: pkSymbol, symbol: Symbol buf)
if not pegParser.match(text, result).ok:
raise newException(ValueError, "failed to parse Preserves atom: " & text)

View File

@ -21,6 +21,29 @@ proc hash*(s: Symbol): Hash {.borrow.}
proc len*(s: Symbol): int {.borrow.}
type
Atom* = object
## Atomic Preserves value.
## Useful when a `const Value` is required.
case kind*: PreserveKind
of pkBoolean:
bool*: bool
of pkFloat:
float*: float32
of pkDouble:
double*: float64
of pkRegister:
register*: int
of pkBigInt:
bigint*: BigInt
of pkString:
string*: string
of pkByteString:
bytes*: seq[byte]
of pkSymbol:
symbol*: Symbol
else:
discard
Preserve*[E] = object
embedded*: bool
## Flag to mark embedded Preserves

View File

@ -39,3 +39,5 @@ suite "parse":
a = encode test
b = bin
check(cast[string](a).toHex == b.toHex)
if test.isAtomic:
discard parsePreservesAtom(txt)