# SPDX-FileCopyrightText: 2021 ☭ Emery Hemingway # SPDX-License-Identifier: Unlicense import std/[base64, parseutils, sets, strutils, tables] import npeg import ../preserves, ./pegs type Frame = tuple[value: Preserve, pos: int] Stack = seq[Frame] proc shrink(stack: var Stack; n: int) = stack.setLen(stack.len - n) template pushStack(v: Preserve) = stack.add((v, capture[0].si)) proc parsePreserves*(text: string): Preserve {.gcsafe.} = const pegParser = peg("Document", stack: Stack): # Override rules from pegs.nim Document <- Preserves.Document Preserves.Record <- Preserves.Record: var record: seq[Preserve] labelOff: int while stack[labelOff].pos < capture[0].si: inc labelOff for i in labelOff.succ..stack.high: record.add(move stack[i].value) record.add(move stack[labelOff].value) stack.shrink record.len pushStack Preserve(kind: pkRecord, record: move record) Preserves.Sequence <- Preserves.Sequence: var sequence: seq[Preserve] for frame in stack.mitems: if frame.pos > capture[0].si: sequence.add(move frame.value) stack.shrink sequence.len pushStack Preserve(kind: pkSequence, sequence: move sequence) Preserves.Dictionary <- Preserves.Dictionary: var prs = Preserve(kind: pkDictionary) for i in countDown(stack.high.pred, 0, 2): if stack[i].pos < capture[0].si: break prs[move stack[i].value] = stack[i.succ].value stack.shrink prs.dict.len*2 pushStack prs Preserves.Set <- Preserves.Set: var prs = Preserve(kind: pkSet) for frame in stack.mitems: if frame.pos > capture[0].si: prs.incl(move frame.value) stack.shrink prs.set.len pushStack prs Preserves.Boolean <- Preserves.Boolean: case $0 of "#f": pushStack Preserve(kind: pkBoolean) of "#t": pushStack Preserve(kind: pkBoolean, bool: true) else: discard Preserves.Float <- Preserves.Float: pushStack Preserve(kind: pkFloat, float: parseFloat($1)) Preserves.Double <- Preserves.Double: pushStack Preserve(kind: pkDouble) let i = stack.high discard parseBiggestFloat($0, stack[i].value.double) Preserves.SignedInteger <- Preserves.SignedInteger: pushStack Preserve(kind: pkSignedInteger, int: parseInt($0)) Preserves.String <- Preserves.String: pushStack Preserve(kind: pkString, string: unescape($0)) Preserves.charByteString <- Preserves.charByteString: let s = unescape($1) pushStack Preserve(kind: pkByteString, bytes: cast[seq[byte]](s)) Preserves.hexByteString <- Preserves.hexByteString: pushStack Preserve(kind: pkByteString, bytes: cast[seq[byte]](parseHexStr($1))) Preserves.b64ByteString <- Preserves.b64ByteString: pushStack Preserve(kind: pkByteString, bytes: cast[seq[byte]](base64.decode($1))) Preserves.Symbol <- Preserves.Symbol: pushStack Preserve(kind: pkSymbol, symbol: $0) Preserves.Embedded <- Preserves.Embedded: pushStack Preserve( kind: pkEmbedded, embedded: stack.pop.value) Preserves.Compact <- Preserves.Compact: pushStack decodePreserves(stack.pop.value.bytes) var stack: Stack let match = pegParser.match(text, stack) if not match.ok: raise newException(ValueError, "failed to parse Preserves:\n" & text[match.matchMax..text.high]) assert(stack.len == 1) stack.pop.value when isMainModule: assert(parsePreserves("#f") == Preserve())