Condense contract and expand to mapEmbeds

This commit is contained in:
Emery Hemingway 2024-01-06 09:45:32 +02:00
parent 0acd369262
commit 93590f2c07
2 changed files with 33 additions and 98 deletions

View File

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

View File

@ -882,105 +882,40 @@ proc apply*(result: var Value; op: proc(_: var Value) {.gcsafe.}) {.gcsafe.} =
recurse(e.val)
cannonicalize(result)
proc mapEmbeds*[A, B](pr: sink Value; op: proc (v: A): B): Value =
## Convert `Value` to `Value` using an `A → B` procedure.
runnableExamples:
import std/tables
type MacGuffin = ref object
stuff: void
var registry = {20: new MacGuffin}.toTable
let
a = [ 20.embed ].toPreserves(int)
b = mapEmbeds(a) do (i: int) -> MacGuffin:
registry[i]
assert typeof(b[0].unembed) is MacGuffin
if pr.embedded:
var
e: A
pr = pr # TODO: avoid copy
pr.embedded = false
if not fromPreserves(e, pr):
raise newException(ValueError, "failed to map across embedded types")
result = embed op(e)
else:
case pr.kind
of pkBoolean, pkFloat, pkDouble, pkRegister, pkBigInt,
pkString, pkByteString, pkSymbol:
result = cast[Value](pr)
of pkRecord:
result = Value(kind: pr.kind)
result.record = map(pr.record) do (x: Value) -> Value:
mapEmbeds(x, op)
of pkSequence:
result = Value(kind: pr.kind)
result.sequence = map(pr.sequence) do (x: Value) -> Value:
mapEmbeds(x, op)
of pkSet:
result = Value(kind: pr.kind)
result.set = map(pr.set) do (x: Value) -> Value:
mapEmbeds(x, op)
of pkDictionary:
result = Value(kind: pr.kind)
result.dict = map(pr.dict) do (e: DictEntry[A]) -> DictEntry[B]:
(mapEmbeds(e.key, op), mapEmbeds(e.val, op))
of pkEmbedded:
result = embed op(pr.embed)
cannonicalize(result)
proc contract*[T](pr: sink Value; op: proc (v: T): Value {.gcsafe.}): Value {.gcsafe.} =
## Convert `Value` to `Value` using an `T → Value` procedure.
if not pr.embedded:
case pr.kind
of pkBoolean, pkFloat, pkDouble, pkRegister, pkBigInt,
pkString, pkByteString, pkSymbol:
result = cast[Value](pr)
of pkRecord:
result = Value(kind: pr.kind)
result.record = map(pr.record) do (x: Value) -> Value:
contract(x, op)
of pkSequence:
result = Value(kind: pr.kind)
result.sequence = map(pr.sequence) do (x: Value) -> Value:
contract(x, op)
of pkSet:
result = Value(kind: pr.kind)
result.set = map(pr.set) do (x: Value) -> Value:
contract(x, op)
of pkDictionary:
result = Value(kind: pr.kind)
result.dict = map(pr.dict) do (e: DictEntry) -> DictEntry:
(contract(e.key, op), contract(e.val, op))
of pkEmbedded:
proc mapEmbeds*[T](pr: sink Value; op: proc (x: T): Value {.gcsafe.}): Value {.gcsafe.} =
## Process all embeds in a `Value` that are of type `T`.
case pr.kind
of pkBoolean, pkFloat, pkDouble, pkRegister, pkBigInt,
pkString, pkByteString, pkSymbol:
result = cast[Value](pr)
of pkRecord:
result = Value(kind: pr.kind)
result.record = map(pr.record) do (x: Value) -> Value:
mapEmbeds(x, op)
of pkSequence:
result = Value(kind: pr.kind)
result.sequence = map(pr.sequence) do (x: Value) -> Value:
mapEmbeds(x, op)
of pkSet:
result = Value(kind: pr.kind)
result.set = map(pr.set) do (x: Value) -> Value:
mapEmbeds(x, op)
of pkDictionary:
result = Value(kind: pr.kind)
result.dict = map(pr.dict) do (e: DictEntry) -> DictEntry:
(mapEmbeds(e.key, op), mapEmbeds(e.val, op))
of pkEmbedded:
when T is Value: result = pr
else:
if pr.embeddedRef of T:
result = embed op(T pr.embeddedRef)
cannonicalize(result)
proc expand*(pr: sink Value; op: proc (v: Value): Value {.gcsafe.}): Value {.gcsafe.} =
## Convert `Value` to `Value` using an `Value → Value` procedure.
if pr.embedded:
result = op(pr)
else:
case pr.kind
of pkBoolean, pkFloat, pkDouble, pkRegister, pkBigInt,
pkString, pkByteString, pkSymbol, pkEmbedded:
result = pr
of pkRecord:
result = Value(kind: pr.kind)
result.record = map(pr.record) do (x: Value) -> Value:
expand(x, op)
of pkSequence:
result = Value(kind: pr.kind)
result.sequence = map(pr.sequence) do (x: Value) -> Value:
expand(x, op)
of pkSet:
result = Value(kind: pr.kind)
result.set = map(pr.set) do (x: Value) -> Value:
expand(x, op)
of pkDictionary:
result = Value(kind: pr.kind)
result.dict = map(pr.dict) do (e: DictEntry) -> DictEntry:
(expand(e.key, op), expand(e.val, op))
result = op(T pr.embeddedRef)
result.embedded = true
else: result = pr
when T is Value:
if result.embedded:
result = op(pr)
cannonicalize(result)
# TODO: is this necessary?
proc getOrDefault*[T, V](pr: Value; key: string; default: V): V =
## Retrieves the value of `pr[key]` if `pr` is a dictionary containing `key`