# SPDX-FileCopyrightText: ☭ Emery Hemingway # SPDX-License-Identifier: Unlicense import std/[base64, endians, math, sequtils, streams, strutils, unicode] import bigints import ./values const hexAlphabet = "0123456789abcdef" type TextMode* = enum textPreserves, textJson 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] 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 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, '|') 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: 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, '"') of pkDouble: 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, '"') of pkRegister: write(stream, $pr.register) of pkBigInt: write(stream, $pr.bigint) of pkString: write(stream, '"') writeEscaped(stream, pr.string, '"') write(stream, '"') 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: write(stream, hexAlphabet[b.int shr 4]) write(stream, hexAlphabet[b.int and 0xf]) write(stream, '"') of pkSymbol: writeSymbol(stream, pr.symbol.string) of pkRecord: assert(pr.record.len > 0) write(stream, '<') writeText(stream, pr.record[pr.record.high], mode) for i in 0..') 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, "…") proc `$`*(sym: Symbol): string = var stream = newStringStream() writeSymbol(stream, sym.string) result = move stream.data proc `$`*[E](pr: Preserve[E]): string = ## Generate the textual representation of ``pr``. var stream = newStringStream() writeText(stream, pr, textPreserves) result = move stream.data