Compare commits
5 Commits
Author | SHA1 | Date |
---|---|---|
Emery Hemingway | f65e206864 | |
Emery Hemingway | c40d2c6443 | |
Emery Hemingway | 3b9c164737 | |
Emery Hemingway | ca0cebcefd | |
Emery Hemingway | 8f42f97e13 |
|
@ -1,6 +1,6 @@
|
|||
# Package
|
||||
|
||||
version = "20240422"
|
||||
version = "20240506"
|
||||
author = "Emery Hemingway"
|
||||
description = "data model and serialization format"
|
||||
license = "Unlicense"
|
||||
|
|
|
@ -0,0 +1,92 @@
|
|||
# SPDX-FileCopyrightText: ☭ Emery Hemingway
|
||||
# SPDX-License-Identifier: Unlicense
|
||||
|
||||
import
|
||||
npeg,
|
||||
../preserves, ./pegs
|
||||
|
||||
type
|
||||
Frame = tuple[value: Value, pos: int]
|
||||
Stack = seq[Frame]
|
||||
|
||||
proc shrink(stack: var Stack; n: int) = stack.setLen(stack.len - n)
|
||||
|
||||
template pushStack(v: Value) = stack.add((v, capture[0].si))
|
||||
|
||||
template collectEntries(result: var seq[Value]; stack: var Stack) =
|
||||
for frame in stack.mitems:
|
||||
if frame.pos > capture[0].si:
|
||||
result.add frame.value.move
|
||||
stack.shrink result.len
|
||||
|
||||
proc parseExpressions*(text: string): seq[Value] =
|
||||
let parser = peg("Document", stack: Stack):
|
||||
|
||||
ws <- *{ ' ', '\t', '\r', '\n' }
|
||||
|
||||
Document <- *Expr * ws * !1
|
||||
|
||||
Annotation <-
|
||||
('@' * SimpleExpr) |
|
||||
('#' * {'\x20', '\x09', '\x21'} * @{'\r','\n'})
|
||||
|
||||
Trailer <- *(ws * Annotation)
|
||||
|
||||
Expr <- ws * (Punct | SimpleExpr) * Trailer
|
||||
|
||||
Punct <- {',', ';'} | +':':
|
||||
pushStack initRecord("p", toSymbol $0)
|
||||
|
||||
SimpleExpr <-
|
||||
Atom |
|
||||
Compound |
|
||||
Embedded |
|
||||
Annotated
|
||||
|
||||
Embedded <- "#:" * SimpleExpr:
|
||||
pushstack stack.pop.value.embed
|
||||
|
||||
Annotated <- Annotation * SimpleExpr
|
||||
|
||||
Compound <- Sequence | Record | Block | Group | Set
|
||||
|
||||
Sequence <- '[' * *Expr * ws * ']':
|
||||
var pr = Value(kind: pkSequence)
|
||||
collectEntries(pr.sequence, stack)
|
||||
pushStack pr
|
||||
|
||||
Record <- '<' * *Expr * ws * '>':
|
||||
var pr = Value(kind: pkRecord)
|
||||
collectEntries(pr.record, stack)
|
||||
pr.record.add toSymbol"r"
|
||||
pushStack pr
|
||||
|
||||
Block <- '{' * *Expr * ws * '}':
|
||||
var pr = Value(kind: pkRecord)
|
||||
collectEntries(pr.record, stack)
|
||||
pr.record.add toSymbol"b"
|
||||
pushStack pr
|
||||
|
||||
Group <- '(' * *Expr * ws * ')':
|
||||
var pr = Value(kind: pkRecord)
|
||||
collectEntries(pr.record, stack)
|
||||
pr.record.add toSymbol"g"
|
||||
pushStack pr
|
||||
|
||||
Set <- "#{" * *Expr * ws * '}':
|
||||
var pr = Value(kind: pkRecord)
|
||||
collectEntries(pr.record, stack)
|
||||
pr.record.add toSymbol"s"
|
||||
pushStack pr
|
||||
|
||||
Atom <- Preserves.Atom:
|
||||
pushStack parsePreserves($0)
|
||||
|
||||
var stack: Stack
|
||||
let match = parser.match(text, stack)
|
||||
if not match.ok:
|
||||
raise newException(ValueError, "failed to parse Preserves Expressions:\n" & text[match.matchMax..text.high])
|
||||
|
||||
result.setLen stack.len
|
||||
for i, _ in result:
|
||||
result[i] = move stack[i].value
|
|
@ -11,7 +11,11 @@ grammar "Preserves":
|
|||
|
||||
ws <- *{ ' ', '\t', '\r', '\n' }
|
||||
commas <- *(ws * ',') * ws
|
||||
delimiter <- { ' ', '\t', '\r', '\n', '<', '>', '[', ']', '{', '}', '#', ':', '"', '|', '@', ';', ',' } | !1
|
||||
delimiter <- {
|
||||
' ', '\t', '\r', '\n',
|
||||
'<', '>', '[', ']', '{', '}', '(', ')',
|
||||
'#', ':', '"', '|', '@', ';', ','
|
||||
} | !1
|
||||
|
||||
Document <- Value * ws * !1
|
||||
|
||||
|
@ -19,10 +23,10 @@ grammar "Preserves":
|
|||
|
||||
Collection <- Sequence | Dictionary | Set
|
||||
|
||||
Value <-
|
||||
(ws * (Record | Collection | Atom | Embedded | Compact)) |
|
||||
(ws * Annotation) |
|
||||
(ws * '#' * @'\n' * Value)
|
||||
Value <- ws * (
|
||||
Record | Collection | Atom | Embedded | Compact |
|
||||
Annotation |
|
||||
('#' * @'\n' * Value) )
|
||||
|
||||
Record <- '<' * +Value * ws * '>'
|
||||
|
||||
|
|
|
@ -15,11 +15,12 @@ proc readVarint(s: Stream): uint =
|
|||
c = uint s.readUint8
|
||||
result = result or (c shl shift)
|
||||
|
||||
proc decodePreserves*(s: Stream): Value =
|
||||
proc decodePreserves*(s: Stream): Value {.gcsafe.}
|
||||
|
||||
proc decodePreserves(s: Stream; tag: uint8): Value =
|
||||
## Decode a Preserves value from a binary-encoded stream.
|
||||
if s.atEnd: raise newException(IOError, "End of Preserves stream")
|
||||
const endMarker = 0x84
|
||||
let tag = s.readUint8()
|
||||
case tag
|
||||
of 0x80: result = Value(kind: pkBoolean, bool: false)
|
||||
of 0x81: result = Value(kind: pkBoolean, bool: true)
|
||||
|
@ -99,30 +100,38 @@ proc decodePreserves*(s: Stream): Value =
|
|||
of 0xb4:
|
||||
result = Value(kind: pkRecord)
|
||||
var label = decodePreserves(s)
|
||||
while s.peekUint8() != endMarker:
|
||||
result.record.add decodePreserves(s)
|
||||
var tag = s.readUint8()
|
||||
while tag != endMarker:
|
||||
result.record.add decodePreserves(s, tag)
|
||||
tag = s.readUint8()
|
||||
result.record.add(move label)
|
||||
discard s.readUint8()
|
||||
of 0xb5:
|
||||
result = Value(kind: pkSequence)
|
||||
while s.peekUint8() != endMarker:
|
||||
result.sequence.add decodePreserves(s)
|
||||
discard s.readUint8()
|
||||
var tag = s.readUint8()
|
||||
while tag != endMarker:
|
||||
result.sequence.add decodePreserves(s, tag)
|
||||
tag = s.readUint8()
|
||||
of 0xb6:
|
||||
result = Value(kind: pkSet)
|
||||
while s.peekUint8() != endMarker:
|
||||
incl(result, decodePreserves(s))
|
||||
discard s.readUint8()
|
||||
var tag = s.readUint8()
|
||||
while tag != endMarker:
|
||||
incl(result, decodePreserves(s, tag))
|
||||
tag = s.readUint8()
|
||||
of 0xb7:
|
||||
result = Value(kind: pkDictionary)
|
||||
while s.peekUint8() != endMarker:
|
||||
result[decodePreserves(s)] = decodePreserves(s)
|
||||
discard s.readUint8()
|
||||
var tag = s.readUint8()
|
||||
while tag != endMarker:
|
||||
result[decodePreserves(s, tag)] = decodePreserves(s)
|
||||
tag = s.readUint8()
|
||||
of endMarker:
|
||||
raise newException(ValueError, "invalid Preserves stream")
|
||||
else:
|
||||
raise newException(ValueError, "invalid Preserves tag byte 0x" & tag.toHex(2))
|
||||
|
||||
proc decodePreserves*(s: Stream): Value {.gcsafe.} =
|
||||
## Decode a Preserves value from a binary-encoded stream.
|
||||
s.decodePreserves s.readUint8()
|
||||
|
||||
proc decodePreserves*(s: string): Value =
|
||||
## Decode a string of binary-encoded Preserves.
|
||||
decodePreserves(s.newStringStream)
|
||||
|
|
|
@ -249,10 +249,13 @@ proc `[]=`*(pr: var Value; key, val: Value) =
|
|||
|
||||
proc incl*(pr: var Value; key: Value) =
|
||||
## Include `key` in the Preserves set `pr`.
|
||||
# TODO: binary search
|
||||
for i in 0..pr.set.high:
|
||||
if key < pr.set[i]:
|
||||
insert(pr.set, [key], i)
|
||||
return
|
||||
elif key == pr.set[i]:
|
||||
return
|
||||
pr.set.add(key)
|
||||
|
||||
proc excl*(pr: var Value; key: Value) =
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
# SPDX-FileCopyrightText: ☭ Emery Hemingway
|
||||
# SPDX-License-Identifier: Unlicense
|
||||
|
||||
import
|
||||
std/unittest,
|
||||
preserves, preserves/expressions
|
||||
|
||||
template testExpr(name, code, cntrl: string) {.dirty.} =
|
||||
test name:
|
||||
checkpoint code
|
||||
let
|
||||
pr = parsePreserves cntrl
|
||||
exprs = parseExpressions code
|
||||
checkpoint $(exprs.toPreserves)
|
||||
check exprs.len == 1
|
||||
let px = exprs[0]
|
||||
check px == pr
|
||||
|
||||
suite "expression":
|
||||
|
||||
testExpr "date", """
|
||||
<date 1821 (lookup-month "February") 3>
|
||||
""", """
|
||||
<r date 1821 <g lookup-month "February"> 3>
|
||||
"""
|
||||
|
||||
testExpr "r", "<>", "<r>"
|
||||
|
||||
testExpr "begin",
|
||||
"""(begin (println! (+ 1 2)) (+ 3 4))""",
|
||||
"""<g begin <g println! <g + 1 2>> <g + 3 4>>"""
|
||||
|
||||
testExpr "g",
|
||||
"""()""", """<g>"""
|
||||
|
||||
testExpr "groups",
|
||||
"""[() () ()]""", """[<g>, <g>, <g>]"""
|
||||
|
||||
testExpr "loop", """
|
||||
{
|
||||
setUp();
|
||||
# Now enter the loop
|
||||
loop: {
|
||||
greet("World");
|
||||
}
|
||||
tearDown();
|
||||
}
|
||||
""", """
|
||||
<b
|
||||
setUp <g> <p |;|>
|
||||
# Now enter the loop
|
||||
loop <p |:|> <b
|
||||
greet <g "World"> <p |;|>
|
||||
>
|
||||
tearDown <g> <p |;|>
|
||||
>
|
||||
"""
|
||||
|
||||
testExpr "+", """
|
||||
[1 + 2.0, print "Hello", predicate: #t, foo, #:remote, bar]
|
||||
""", """
|
||||
[1 + 2.0 <p |,|> print "Hello" <p |,|> predicate <p |:|> #t <p |,|>
|
||||
foo <p |,|> #:remote <p |,|> bar]
|
||||
"""
|
||||
|
||||
testExpr "set",
|
||||
"""#{1 2 3}""", """<s 1 2 3>"""
|
||||
|
||||
testExpr "group-set",
|
||||
"""#{(read) (read) (read)}""",
|
||||
"""<s <g read> <g read> <g read>>"""
|
||||
|
||||
testExpr "block", """
|
||||
{
|
||||
optional name: string,
|
||||
address: Address,
|
||||
}
|
||||
""", """
|
||||
<b
|
||||
optional name <p |:|> string <p |,|>
|
||||
address <p |:|> Address <p |,|>
|
||||
>
|
||||
"""
|
Loading…
Reference in New Issue