# SPDX-FileCopyrightText: ☭ Emery Hemingway # SPDX-License-Identifier: Unlicense import std/[assertions, base64, endians, sequtils, streams, strutils] when not defined(nimNoLibc): import std/math 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 writeFloatBytes(stream: Stream; f: float) = var buf: array[8, byte] bigEndian64(addr buf[0], addr f) write(stream, "#xd\"") for b in buf: write(stream, hexAlphabet[b shr 4]) write(stream, hexAlphabet[b and 0xf]) write(stream, '"') proc writeText*(stream: Stream; pr: Value; 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: when defined(nimNoLibc): writeFloatBytes(stream, pr.float) # IEE754-to-decimal is non-trivial else: if pr.float.classify in {fcNormal, fcZero, fcNegZero}: write(stream, $pr.float) else: writeFloatBytes(stream, pr.float) 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: if not pr.embedded: write(stream, "#:") if pr.embeddedRef.isNil: write(stream, "") else: when compiles($pr.embed): write(stream, $pr.embed) else: write(stream, "…") proc `$`*(sym: Symbol): string = var stream = newStringStream() writeSymbol(stream, sym.string) result = move stream.data proc `$`*(pr: Value): string = ## Generate the textual representation of ``pr``. var stream = newStringStream() writeText(stream, pr, textPreserves) result = move stream.data