diff --git a/src/preserves/expressions.nim b/src/preserves/expressions.nim new file mode 100644 index 0000000..1cba7a4 --- /dev/null +++ b/src/preserves/expressions.nim @@ -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 diff --git a/tests/test_p_exprs.nim b/tests/test_p_exprs.nim new file mode 100644 index 0000000..3d898e6 --- /dev/null +++ b/tests/test_p_exprs.nim @@ -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", """ + + """, """ + 3> + """ + + testExpr "r", "<>", "" + + testExpr "begin", + """(begin (println! (+ 1 2)) (+ 3 4))""", + """> >""" + + testExpr "g", + """()""", """""" + + testExpr "groups", + """[() () ()]""", """[, , ]""" + + testExpr "loop", """ + { + setUp(); + # Now enter the loop + loop: { + greet("World"); + } + tearDown(); + } + """, """ +

+ # Now enter the loop + loop

+ > + tearDown

+ > + """ + + testExpr "+", """ + [1 + 2.0, print "Hello", predicate: #t, foo, #:remote, bar] + """, """ + [1 + 2.0

print "Hello"

predicate

#t

+ foo

#:remote

bar] + """ + + testExpr "set", + """#{1 2 3}""", """""" + + testExpr "group-set", + """#{(read) (read) (read)}""", + """ >""" + + testExpr "block", """ + { + optional name: string, + address: Address, + } + """, """ + string

+ address

Address

+ > + """