diff --git a/preserves.nimble b/preserves.nimble index 662b1b2..a852a14 100644 --- a/preserves.nimble +++ b/preserves.nimble @@ -1,6 +1,6 @@ # Package -version = "20231225" +version = "20231227" author = "Emery Hemingway" description = "data model and serialization format" license = "Unlicense" diff --git a/src/preserves.nim b/src/preserves.nim index 0395459..382ee38 100644 --- a/src/preserves.nim +++ b/src/preserves.nim @@ -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): diff --git a/src/preserves/Tupfile b/src/preserves/Tupfile index 8660033..4ba31e6 100644 --- a/src/preserves/Tupfile +++ b/src/preserves/Tupfile @@ -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)/ : foreach $(DOT_FILES) |> dot -Tsvg -LO %f > %o |> ../../%B-Grammer-Graph.svg diff --git a/src/preserves/private/parsing.nim b/src/preserves/private/parsing.nim index 43ee4db..c5b76ca 100644 --- a/src/preserves/private/parsing.nim +++ b/src/preserves/private/parsing.nim @@ -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) diff --git a/src/preserves/private/values.nim b/src/preserves/private/values.nim index 31444e3..6958ca0 100644 --- a/src/preserves/private/values.nim +++ b/src/preserves/private/values.nim @@ -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 diff --git a/tests/test_parser.nim b/tests/test_parser.nim index 06f1c3d..d587ac2 100644 --- a/tests/test_parser.nim +++ b/tests/test_parser.nim @@ -39,3 +39,5 @@ suite "parse": a = encode test b = bin check(cast[string](a).toHex == b.toHex) + if test.isAtomic: + discard parsePreservesAtom(txt)