2023-12-19 20:07:44 +00:00
|
|
|
# SPDX-FileCopyrightText: ☭ Emery Hemingway
|
|
|
|
# SPDX-License-Identifier: Unlicense
|
|
|
|
|
2023-12-24 12:17:33 +00:00
|
|
|
import std/[base64, endians, math, sequtils, streams, strutils, unicode]
|
2023-12-24 19:07:05 +00:00
|
|
|
import bigints
|
2023-12-19 20:07:44 +00:00
|
|
|
import ./values
|
|
|
|
|
2023-12-22 20:16:26 +00:00
|
|
|
const hexAlphabet = "0123456789abcdef"
|
|
|
|
|
2023-12-19 20:07:44 +00:00
|
|
|
type TextMode* = enum textPreserves, textJson
|
|
|
|
|
2023-12-24 19:07:05 +00:00
|
|
|
template writeEscaped(stream: Stream; text: string; delim: char) =
|
|
|
|
const escaped = { '"', '\\', '\b', '\f', '\n', '\r', '\t' }
|
|
|
|
var
|
|
|
|
i: int
|
|
|
|
c: char
|
|
|
|
while i < text.len:
|
|
|
|
c = text[i]
|
2023-12-23 23:10:21 +00:00
|
|
|
case c
|
|
|
|
of delim:
|
|
|
|
write(stream, '\\')
|
|
|
|
write(stream, delim)
|
|
|
|
of '\\': write(stream, "\\\\")
|
|
|
|
of '\b': write(stream, "\\b")
|
|
|
|
of '\f': write(stream, "\\f")
|
|
|
|
of '\n': write(stream, "\\n")
|
|
|
|
of '\r': write(stream, "\\r")
|
|
|
|
of '\t': write(stream, "\\t")
|
|
|
|
of { '\x00'..'\x1f', '\x7f' } - escaped:
|
|
|
|
# do not use \x__ notation because
|
|
|
|
# it is a subset of \u____.
|
|
|
|
write(stream, "\\u00")
|
|
|
|
write(stream, c.uint8.toHex(2))
|
|
|
|
else: write(stream, c)
|
|
|
|
inc i
|
2023-12-24 19:07:05 +00:00
|
|
|
|
|
|
|
proc writeSymbol(stream: Stream; sym: string) =
|
|
|
|
if sym.len > 0 and sym[0] in {'A'..'z'} and not sym.anyIt(char(it) in { '\x00'..'\x19', '"', '\\', '|' }):
|
|
|
|
write(stream, sym)
|
|
|
|
else:
|
|
|
|
write(stream, '|')
|
|
|
|
writeEscaped(stream, sym, '|')
|
|
|
|
write(stream, '|')
|
|
|
|
|
2023-12-19 20:07:44 +00:00
|
|
|
proc writeText*[E](stream: Stream; pr: Preserve[E]; mode = textPreserves) =
|
|
|
|
## Encode Preserves to a `Stream` as text.
|
|
|
|
if pr.embedded: write(stream, "#!")
|
|
|
|
case pr.kind:
|
|
|
|
of pkBoolean:
|
|
|
|
case pr.bool
|
|
|
|
of false: write(stream, "#f")
|
|
|
|
of true: write(stream, "#t")
|
|
|
|
of pkFloat:
|
2023-12-22 20:16:26 +00:00
|
|
|
case pr.float.classify:
|
|
|
|
of fcNormal, fcZero, fcNegZero:
|
|
|
|
write(stream, $pr.float)
|
|
|
|
write(stream, 'f')
|
|
|
|
else:
|
|
|
|
var buf: array[4, byte]
|
|
|
|
bigEndian32(addr buf[0], addr pr.float)
|
|
|
|
write(stream, "#xf\"")
|
|
|
|
for b in buf:
|
|
|
|
write(stream, hexAlphabet[b shr 4])
|
|
|
|
write(stream, hexAlphabet[b and 0xf])
|
|
|
|
write(stream, '"')
|
2023-12-19 20:07:44 +00:00
|
|
|
of pkDouble:
|
2023-12-22 20:16:26 +00:00
|
|
|
case pr.double.classify:
|
|
|
|
of fcNormal, fcZero, fcNegZero:
|
|
|
|
write(stream, $pr.double)
|
|
|
|
else:
|
|
|
|
var buf: array[8, byte]
|
|
|
|
bigEndian64(addr buf[0], addr pr.double)
|
|
|
|
write(stream, "#xd\"")
|
|
|
|
for b in buf:
|
|
|
|
write(stream, hexAlphabet[b shr 4])
|
|
|
|
write(stream, hexAlphabet[b and 0xf])
|
|
|
|
write(stream, '"')
|
2023-12-22 15:55:23 +00:00
|
|
|
of pkRegister:
|
|
|
|
write(stream, $pr.register)
|
|
|
|
of pkBigInt:
|
|
|
|
write(stream, $pr.bigint)
|
2023-12-19 20:07:44 +00:00
|
|
|
of pkString:
|
2023-12-24 19:07:05 +00:00
|
|
|
write(stream, '"')
|
|
|
|
writeEscaped(stream, pr.string, '"')
|
|
|
|
write(stream, '"')
|
2023-12-19 20:07:44 +00:00
|
|
|
of pkByteString:
|
|
|
|
if pr.bytes.allIt(char(it) in {' '..'!', '#'..'~'}):
|
|
|
|
write(stream, "#\"")
|
|
|
|
write(stream, cast[string](pr.bytes))
|
|
|
|
write(stream, '"')
|
|
|
|
else:
|
|
|
|
if pr.bytes.len > 64:
|
|
|
|
write(stream, "#[") #]#
|
|
|
|
write(stream, base64.encode(pr.bytes))
|
|
|
|
write(stream, ']')
|
|
|
|
else:
|
|
|
|
write(stream, "#x\"")
|
|
|
|
for b in pr.bytes:
|
2023-12-22 20:16:26 +00:00
|
|
|
write(stream, hexAlphabet[b.int shr 4])
|
|
|
|
write(stream, hexAlphabet[b.int and 0xf])
|
2023-12-19 20:07:44 +00:00
|
|
|
write(stream, '"')
|
|
|
|
of pkSymbol:
|
2023-12-24 19:07:05 +00:00
|
|
|
writeSymbol(stream, pr.symbol.string)
|
2023-12-19 20:07:44 +00:00
|
|
|
of pkRecord:
|
|
|
|
assert(pr.record.len > 0)
|
|
|
|
write(stream, '<')
|
|
|
|
writeText(stream, pr.record[pr.record.high], mode)
|
|
|
|
for i in 0..<pr.record.high:
|
|
|
|
write(stream, ' ')
|
|
|
|
writeText(stream, pr.record[i], mode)
|
|
|
|
write(stream, '>')
|
|
|
|
of pkSequence:
|
|
|
|
write(stream, '[')
|
|
|
|
var insertSeperator: bool
|
|
|
|
case mode
|
|
|
|
of textPreserves:
|
|
|
|
for val in pr.sequence:
|
|
|
|
if insertSeperator: write(stream, ' ')
|
|
|
|
else: insertSeperator = true
|
|
|
|
writeText(stream, val, mode)
|
|
|
|
of textJson:
|
|
|
|
for val in pr.sequence:
|
|
|
|
if insertSeperator: write(stream, ',')
|
|
|
|
else: insertSeperator = true
|
|
|
|
writeText(stream, val, mode)
|
|
|
|
write(stream, ']')
|
|
|
|
of pkSet:
|
|
|
|
write(stream, "#{")
|
|
|
|
var insertSeperator: bool
|
|
|
|
for val in pr.set.items:
|
|
|
|
if insertSeperator: write(stream, ' ')
|
|
|
|
else: insertSeperator = true
|
|
|
|
writeText(stream, val, mode)
|
|
|
|
write(stream, '}')
|
|
|
|
of pkDictionary:
|
|
|
|
write(stream, '{')
|
|
|
|
var insertSeperator: bool
|
|
|
|
case mode
|
|
|
|
of textPreserves:
|
|
|
|
for (key, value) in pr.dict.items:
|
|
|
|
if insertSeperator: write(stream, ' ')
|
|
|
|
else: insertSeperator = true
|
|
|
|
writeText(stream, key, mode)
|
|
|
|
write(stream, ": ")
|
|
|
|
writeText(stream, value, mode)
|
|
|
|
of textJson:
|
|
|
|
for (key, value) in pr.dict.items:
|
|
|
|
if insertSeperator: write(stream, ',')
|
|
|
|
else: insertSeperator = true
|
|
|
|
writeText(stream, key, mode)
|
|
|
|
write(stream, ':')
|
|
|
|
writeText(stream, value, mode)
|
|
|
|
write(stream, '}')
|
|
|
|
of pkEmbedded:
|
|
|
|
write(stream, "#!")
|
|
|
|
when compiles($pr.embed) and not E is void:
|
|
|
|
write(stream, $pr.embed)
|
|
|
|
else:
|
|
|
|
write(stream, "…")
|
|
|
|
|
2023-12-24 19:07:05 +00:00
|
|
|
proc `$`*(sym: Symbol): string =
|
|
|
|
var stream = newStringStream()
|
|
|
|
writeSymbol(stream, sym.string)
|
|
|
|
result = move stream.data
|
|
|
|
|
2023-12-19 20:07:44 +00:00
|
|
|
proc `$`*[E](pr: Preserve[E]): string =
|
|
|
|
## Generate the textual representation of ``pr``.
|
|
|
|
var stream = newStringStream()
|
|
|
|
writeText(stream, pr, textPreserves)
|
|
|
|
result = move stream.data
|