248 lines
7.1 KiB
Nim
248 lines
7.1 KiB
Nim
|
# SPDX-FileCopyrightText: ☭ Emery Hemingway
|
||
|
# SPDX-License-Identifier: Unlicense
|
||
|
|
||
|
import std/[hashes, options, sets, sequtils, tables]
|
||
|
|
||
|
type
|
||
|
PreserveKind* = enum
|
||
|
pkBoolean, pkFloat, pkDouble, pkSignedInteger, pkString, pkByteString, pkSymbol,
|
||
|
pkRecord, pkSequence, pkSet, pkDictionary, pkEmbedded
|
||
|
|
||
|
const
|
||
|
atomKinds* = {pkBoolean, pkFloat, pkDouble, pkSignedInteger, pkString, pkByteString, pkSymbol}
|
||
|
compoundKinds* = {pkRecord, pkSequence, pkSet, pkDictionary}
|
||
|
|
||
|
type Symbol* = distinct string
|
||
|
proc `<`*(x, y: Symbol): bool {.borrow.}
|
||
|
proc `==`*(x, y: Symbol): bool {.borrow.}
|
||
|
proc hash*(s: Symbol): Hash {.borrow.}
|
||
|
proc len*(s: Symbol): int {.borrow.}
|
||
|
|
||
|
type
|
||
|
Preserve*[E] = object
|
||
|
embedded*: bool
|
||
|
## Flag to mark embedded Preserves
|
||
|
case kind*: PreserveKind
|
||
|
of pkBoolean:
|
||
|
bool*: bool
|
||
|
of pkFloat:
|
||
|
float*: float32
|
||
|
of pkDouble:
|
||
|
double*: float64
|
||
|
of pkSignedInteger:
|
||
|
int*: BiggestInt
|
||
|
of pkString:
|
||
|
string*: string
|
||
|
of pkByteString:
|
||
|
bytes*: seq[byte]
|
||
|
of pkSymbol:
|
||
|
symbol*: Symbol
|
||
|
of pkRecord:
|
||
|
record*: seq[Preserve[E]] # label is last
|
||
|
of pkSequence:
|
||
|
sequence*: seq[Preserve[E]]
|
||
|
of pkSet:
|
||
|
set*: seq[Preserve[E]]
|
||
|
# TODO: HashSet
|
||
|
of pkDictionary:
|
||
|
dict*: seq[DictEntry[E]]
|
||
|
# TODO: Tables
|
||
|
of pkEmbedded:
|
||
|
embed*: E
|
||
|
|
||
|
DictEntry*[E] = tuple[key: Preserve[E], val: Preserve[E]]
|
||
|
|
||
|
func `==`*[A, B](x: Preserve[A]; y: Preserve[B]): bool =
|
||
|
## Check `x` and `y` for equivalence.
|
||
|
if x.kind == y.kind and x.embedded == y.embedded:
|
||
|
case x.kind
|
||
|
of pkBoolean:
|
||
|
result = x.bool == y.bool
|
||
|
of pkFloat:
|
||
|
result = x.float == y.float
|
||
|
of pkDouble:
|
||
|
result = x.double == y.double
|
||
|
of pkSignedInteger:
|
||
|
result = x.int == y.int
|
||
|
of pkString:
|
||
|
result = x.string == y.string
|
||
|
of pkByteString:
|
||
|
result = x.bytes == y.bytes
|
||
|
of pkSymbol:
|
||
|
result = x.symbol == y.symbol
|
||
|
of pkRecord:
|
||
|
result = x.record.len == y.record.len
|
||
|
for i in 0..x.record.high:
|
||
|
if not result: break
|
||
|
result = result and (x.record[i] == y.record[i])
|
||
|
of pkSequence:
|
||
|
for i, val in x.sequence:
|
||
|
if y.sequence[i] != val: return false
|
||
|
result = true
|
||
|
of pkSet:
|
||
|
result = x.set.len == y.set.len
|
||
|
for i in 0..x.set.high:
|
||
|
if not result: break
|
||
|
result = result and (x.set[i] == y.set[i])
|
||
|
of pkDictionary:
|
||
|
result = x.dict.len == y.dict.len
|
||
|
for i in 0..x.dict.high:
|
||
|
if not result: break
|
||
|
result = result and
|
||
|
(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
|
||
|
|
||
|
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 false
|
||
|
x.len < y.len
|
||
|
|
||
|
proc `<`*[A, B](x: Preserve[A]; y: Preserve[B]): bool =
|
||
|
## Preserves have a total order over values. Check if `x` is ordered before `y`.
|
||
|
if x.embedded != y.embedded:
|
||
|
result = y.embedded
|
||
|
elif x.kind != y.kind:
|
||
|
result = x.kind < y.kind
|
||
|
else:
|
||
|
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 pkString:
|
||
|
result = x.string < y.string
|
||
|
of pkByteString:
|
||
|
result = x.bytes < y.bytes
|
||
|
of pkSymbol:
|
||
|
result = x.symbol < y.symbol
|
||
|
of pkRecord:
|
||
|
if x.record[x.record.high] < y.record[y.record.high]: return true
|
||
|
for i in 0..<min(x.record.high, y.record.high):
|
||
|
if x.record[i] < y.record[i]: return true
|
||
|
if x.record[i] == y.record[i]: return false
|
||
|
result = x.record.len < y.record.len
|
||
|
of pkSequence:
|
||
|
for i in 0..min(x.sequence.high, y.sequence.high):
|
||
|
if x.sequence[i] < y.sequence[i]: return true
|
||
|
if x.sequence[i] != y.sequence[i]: return false
|
||
|
result = x.sequence.len < y.sequence.len
|
||
|
of pkSet:
|
||
|
for i in 0..min(x.set.high, y.set.high):
|
||
|
if x.set[i] < y.set[i]: return true
|
||
|
if x.set[i] != y.set[i]: return false
|
||
|
result = x.set.len < y.set.len
|
||
|
of pkDictionary:
|
||
|
for i in 0..min(x.dict.high, y.dict.high):
|
||
|
if x.dict[i].key < y.dict[i].key: return true
|
||
|
if x.dict[i].key == y.dict[i].key:
|
||
|
if x.dict[i].val < y.dict[i].val: return true
|
||
|
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
|
||
|
|
||
|
func cmp*[E](x, y: Preserve[E]): 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)
|
||
|
## Sort a Preserves array by total ordering.
|
||
|
|
||
|
proc hash*[E](pr: Preserve[E]): 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
|
||
|
of pkBoolean:
|
||
|
h = h !& hash(pr.bool)
|
||
|
of pkFloat:
|
||
|
h = h !& hash(pr.float)
|
||
|
of pkDouble:
|
||
|
h = h !& hash(pr.double)
|
||
|
of pkSignedInteger:
|
||
|
h = h !& hash(pr.int)
|
||
|
of pkString:
|
||
|
h = h !& hash(pr.string)
|
||
|
of pkByteString:
|
||
|
h = h !& hash(pr.bytes)
|
||
|
of pkSymbol:
|
||
|
h = h !& hash(string pr.symbol)
|
||
|
of pkRecord:
|
||
|
for val in pr.record:
|
||
|
h = h !& hash(val)
|
||
|
of pkSequence:
|
||
|
for val in pr.sequence:
|
||
|
h = h !& hash(val)
|
||
|
of pkSet:
|
||
|
for val in pr.set.items:
|
||
|
h = h !& hash(val)
|
||
|
of pkDictionary:
|
||
|
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
|
||
|
|
||
|
proc `[]`*(pr: Preserve; i: int): Preserve =
|
||
|
## Select an indexed value from ``pr``.
|
||
|
## Only valid for records and sequences.
|
||
|
case pr.kind
|
||
|
of pkRecord: pr.record[i]
|
||
|
of pkSequence: pr.sequence[i]
|
||
|
else:
|
||
|
raise newException(ValueError, "Preserves value is not indexable")
|
||
|
|
||
|
proc `[]=`*(pr: var Preserve; i: Natural; val: Preserve) =
|
||
|
## 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")
|
||
|
|
||
|
proc `[]=`*(pr: var Preserve; key, val: Preserve) =
|
||
|
## Insert `val` by `key` in the Preserves dictionary `pr`.
|
||
|
for i in 0..pr.dict.high:
|
||
|
if key < pr.dict[i].key:
|
||
|
insert(pr.dict, [(key, val, )], i)
|
||
|
return
|
||
|
elif key == pr.dict[i].key:
|
||
|
pr.dict[i].val = val
|
||
|
return
|
||
|
pr.dict.add((key, val, ))
|
||
|
|
||
|
proc incl*(pr: var Preserve; key: Preserve) =
|
||
|
## Include `key` in the Preserves set `pr`.
|
||
|
for i in 0..pr.set.high:
|
||
|
if key < pr.set[i]:
|
||
|
insert(pr.set, [key], i)
|
||
|
return
|
||
|
pr.set.add(key)
|
||
|
|
||
|
proc excl*(pr: var Preserve; key: Preserve) =
|
||
|
## Exclude `key` from the Preserves set `pr`.
|
||
|
for i in 0..pr.set.high:
|
||
|
if pr.set[i] == key:
|
||
|
delete(pr.set, i..i)
|
||
|
break
|