Compare commits

..

19 Commits

Author SHA1 Message Date
Emery Hemingway 560a6417a3 pop: break endless loop 2024-06-10 17:14:02 +03:00
Emery Hemingway 6f1c26e34f Build system refactor 2024-06-08 17:43:18 +03:00
Emery Hemingway a33840707e Fix buffering example 2024-06-08 16:15:44 +03:00
Emery Hemingway 51d591d865 preserves_schema_nim: import compiler from "$nim" 2024-06-04 09:46:57 +03:00
Emery Hemingway cc5df36d8d Replace Nimble with an SBOM 2024-06-01 12:35:35 +03:00
Emery Hemingway c7dc205537 pegs: refer to npeg syntax in the documentation 2024-05-25 10:23:33 +03:00
Emery Hemingway ed065fcc2d sugar: preserve most Nim atomics with % 2024-05-23 15:58:40 +03:00
Emery Hemingway 13fe49e791 Add default.nix 2024-05-22 22:53:45 +03:00
Emery Hemingway 1fee875909 decoding: parse single-byte boolean streams 2024-05-22 20:50:48 +03:00
Emery Hemingway cd6812ae07 encoding: sort dictionaries by encoded keys 2024-05-22 19:49:07 +03:00
Emery Hemingway 4ebca473df schemaparse: definition annotations come after line comments 2024-05-22 19:17:06 +03:00
Emery Hemingway 9d328b3d0c schemaparse: do not capture annotations 2024-05-22 19:16:47 +03:00
Emery Hemingway 921acb6b21 schemaparse: ignore line comments in dictionaries 2024-05-22 18:44:16 +03:00
Emery Hemingway ea6c8118e8 schemaparse: fix parsing of annotated named record fields 2024-05-22 18:32:56 +03:00
Emery Hemingway f65e206864 Fix set inclusion 2024-05-06 13:28:33 +02:00
Emery Hemingway c40d2c6443 Decode from non-seekable streams 2024-05-06 13:28:21 +02:00
Emery Hemingway 3b9c164737 Add P-Expressions reader 2024-04-26 13:17:09 +02:00
Emery Hemingway ca0cebcefd PEG: add '(', ')' to delimeters 2024-04-26 13:06:57 +02:00
Emery Hemingway 8f42f97e13 Simplify PEG 2024-04-26 11:18:38 +02:00
24 changed files with 525 additions and 80 deletions

1
.gitignore vendored
View File

@ -1,4 +1,5 @@
/nim.cfg
/preserves.json
*.dot
*.html
*.run

View File

@ -11,11 +11,13 @@ To parse or produce Preserves one should write a [schema](https://preserves.dev/
To debug the `toPreserves` and `fromPreserves` routines compile with `-d:tracePreserves`.
## Utilities
* preserves_schema_nim
* preserves_encode
* preserves_decode
* preserves_from_json
* preserves_to_json
* preserves-schema-nim
* preserves-encode
* preserves-decode
* preserves-from-json
* preserves-to-json
* preserves-from-xml
* preserves-to-xml
### Installation
`preserves_encode` is a multi-call binary that implements `preserves_encode`, `preserves_decode`, `preserves_from_json`, and `preserves_to_json`, so the appropriate symlinks should be created during packaging.
`preserves_encode` is a multi-call binary that implements `preserves-encode`, `preserves-decode`, `preserves-from-json`, and `preserves-to-json`, so the appropriate symlinks should be created during packaging.

13
Tupfile
View File

@ -1,2 +1,13 @@
include_rules
: lock.json |> !nim_cfg |> | ./<lock>
: sbom.json |> !sbom-to-nix |> | ./<lock>
: src/preserves.nim |> !nim-doc |>
run ./Tuprules.jq sbom.json
GROUP = &(BIN_DIR)/<preserves_encode>
!link = |> ^o symlink %o^ ln -s preserves_encode %o |> | $(GROUP)
: {bin} |> !link |> &(BIN_DIR)/preserves-decode
: {bin} |> !link |> &(BIN_DIR)/preserves-from-json
: {bin} |> !link |> &(BIN_DIR)/preserves-from-xml
: {bin} |> !link |> &(BIN_DIR)/preserves-to-json
: {bin} |> !link |> &(BIN_DIR)/preserves-to-xml

12
Tuprules.jq Executable file
View File

@ -0,0 +1,12 @@
#! /usr/bin/env -S jq --raw-output --from-file
.metadata.component.properties as $props |
$props |
( map( select(.name | .[0:10] == "nim:binDir") ) +
map( select(.name | .[0:10] == "nim:srcDir") ) |
map( .value )
) + ["."] | .[0] as $binDir |
$props |
map( select(.name | .[0:8] == "nim:bin:") ) |
map( ": \($binDir)/\(.value).nim |> !nim_bin |> &(BIN_DIR)/\(.name[8:]) &(BIN_DIR)/<preserves> {bin}" ) |
join("\n")

23
default.nix Normal file
View File

@ -0,0 +1,23 @@
{
pkgs ? import <nixpkgs> { },
}:
let
inherit (pkgs) lib buildNimPackage nim;
in
buildNimPackage {
pname = "preserves-nim";
version = "unstable";
lockFile = ./lock.json;
src = if lib.inNixShell then null else lib.cleanSource ./.;
postInstall = ''
pushd $out/bin
for link in preserves_decode preserves_from_json preserves_to_json;
do ln -s preserves_encode $link
done
mv preserves_schemac preserves-schemac
popd
'';
}

View File

@ -1,2 +1,2 @@
NIM_FLAGS += --path:$(TUP_CWD)/../nim
NIM_FLAGS += --path:$(TUP_CWD)/../npeg/src
NIM_FLAGS += --path:$(TUP_CWD)/src
NIM_LOCK_EXCLUDES += "preserves"

View File

@ -1,14 +1,61 @@
# Package
# Emulate Nimble from CycloneDX data at sbom.json.
version = "20240422"
author = "Emery Hemingway"
description = "data model and serialization format"
license = "Unlicense"
srcDir = "src"
import std/json
bin = @["preserves/preserves_schema_nim", "preserves/private/preserves_encode", "preserves/schemac"]
proc lookupComponent(sbom: JsonNode; bomRef: string): JsonNode =
for c in sbom{"components"}.getElems.items:
if c{"bom-ref"}.getStr == bomRef:
return c
result = newJNull()
let
sbom = (getPkgDir() & "/sbom.json").readFile.parseJson
comp = sbom{"metadata", "component"}
bomRef = comp{"bom-ref"}.getStr
# Dependencies
version = comp{"version"}.getStr
author = comp{"authors"}[0]{"name"}.getStr
description = comp{"description"}.getStr
license = comp{"licenses"}[0]{"license", "id"}.getStr
requires "nim >= 2.0.0", "compiler >= 2.0.0", "https://github.com/zevv/npeg.git >= 1.2.1", "https://github.com/ehmry/nim-bigints.git >= 20231006"
for prop in comp{"properties"}.getElems.items:
let (key, val) = (prop{"name"}.getStr, prop{"value"}.getStr)
case key
of "nim:skipDirs:":
add(skipDirs, val)
of "nim:skipFiles:":
add(skipFiles, val)
of "nim:skipExt":
add(skipExt, val)
of "nim:installDirs":
add(installDirs, val)
of "nim:installFiles":
add(installFiles, val)
of "nim:installExt":
add(installExt, val)
of "nim:binDir":
add(binDir, val)
of "nim:srcDir":
add(srcDir, val)
of "nim:backend":
add(backend, val)
else:
if key.startsWith "nim:bin:":
namedBin[key[8..key.high]] = val
for depend in sbom{"dependencies"}.items:
if depend{"ref"}.getStr == bomRef:
for depRef in depend{"dependsOn"}.items:
let dep = sbom.lookupComponent(depRef.getStr)
var spec = dep{"name"}.getStr
for extRef in dep{"externalReferences"}.elems:
if extRef{"type"}.getStr == "vcs":
spec = extRef{"url"}.getStr
break
let ver = dep{"version"}.getStr
if ver != "":
if ver.allCharsInSet {'0'..'9', '.'}: spec.add " == "
else: spec.add '#'
spec.add ver
requires spec
break

162
sbom.json Normal file
View File

@ -0,0 +1,162 @@
{
"bomFormat": "CycloneDX",
"specVersion": "1.6",
"metadata": {
"component": {
"type": "application",
"bom-ref": "pkg:nim/preserves",
"name": "preserves",
"description": "data model and serialization format",
"version": "20240610",
"authors": [
{
"name": "Emery Hemingway"
}
],
"licenses": [
{
"license": {
"id": "Unlicense"
}
}
],
"properties": [
{
"name": "nim:skipExt",
"value": "nim"
},
{
"name": "nim:bin:preserves-encode",
"value": "preserves/private/preserves_encode"
},
{
"name": "nim:bin:preserves-schema-nim",
"value": "preserves/preserves_schema_nim"
},
{
"name": "nim:bin:preserves-schemac",
"value": "preserves/preserves_schemac"
},
{
"name": "nim:srcDir",
"value": "src"
},
{
"name": "nim:backend",
"value": "c"
}
]
}
},
"components": [
{
"type": "library",
"bom-ref": "pkg:nim/npeg",
"name": "npeg",
"version": "1.2.2",
"externalReferences": [
{
"url": "https://github.com/zevv/npeg/archive/ec0cc6e64ea4c62d2aa382b176a4838474238f8d.tar.gz",
"type": "source-distribution"
},
{
"url": "https://github.com/zevv/npeg.git",
"type": "vcs"
}
],
"properties": [
{
"name": "nix:fod:method",
"value": "fetchzip"
},
{
"name": "nix:fod:path",
"value": "/nix/store/xpn694ibgipj8xak3j4bky6b3k0vp7hh-source"
},
{
"name": "nix:fod:rev",
"value": "ec0cc6e64ea4c62d2aa382b176a4838474238f8d"
},
{
"name": "nix:fod:sha256",
"value": "1fi9ls3xl20bmv1ikillxywl96i9al6zmmxrbffx448gbrxs86kg"
},
{
"name": "nix:fod:url",
"value": "https://github.com/zevv/npeg/archive/ec0cc6e64ea4c62d2aa382b176a4838474238f8d.tar.gz"
},
{
"name": "nix:fod:ref",
"value": "1.2.2"
},
{
"name": "nix:fod:srcDir",
"value": "src"
}
]
},
{
"type": "library",
"bom-ref": "pkg:nim/bigints",
"name": "bigints",
"version": "20231006",
"externalReferences": [
{
"url": "https://github.com/ehmry/nim-bigints/archive/86ea14d31eea9275e1408ca34e6bfe9c99989a96.tar.gz",
"type": "source-distribution"
},
{
"url": "https://github.com/ehmry/nim-bigints.git",
"type": "vcs"
}
],
"properties": [
{
"name": "nix:fod:method",
"value": "fetchzip"
},
{
"name": "nix:fod:path",
"value": "/nix/store/jvrm392g8adfsgf36prgwkbyd7vh5jsw-source"
},
{
"name": "nix:fod:rev",
"value": "86ea14d31eea9275e1408ca34e6bfe9c99989a96"
},
{
"name": "nix:fod:sha256",
"value": "15pcpmnk1bnw3k8769rjzcpg00nahyrypwbxs88jnwr4aczp99j4"
},
{
"name": "nix:fod:url",
"value": "https://github.com/ehmry/nim-bigints/archive/86ea14d31eea9275e1408ca34e6bfe9c99989a96.tar.gz"
},
{
"name": "nix:fod:ref",
"value": "20231006"
},
{
"name": "nix:fod:srcDir",
"value": "src"
}
]
}
],
"dependencies": [
{
"ref": "pkg:nim/preserves",
"dependsOn": [
"pkg:nim/npeg",
"pkg:nim/bigints"
]
},
{
"ref": "pkg:nim/npeg",
"dependsOn": []
},
{
"ref": "pkg:nim/bigints",
"dependsOn": []
}
]
}

View File

@ -1,2 +0,0 @@
include_rules
: preserves.nim |> !nim_check |>

View File

@ -152,6 +152,7 @@ proc pop*(pr: var Value; key: Value; val: var Value): bool =
val = move pr.dict[i].val
delete(pr.dict, i .. i)
return true
inc i
proc `[]`*(pr, key: Value): Value {.deprecated: "use step instead".} =
## Select a value by `key` from `pr`.

View File

@ -1,10 +0,0 @@
include_rules
NIM_FLAGS += --path:$(TUP_CWD)/..
NIM_FLAGS_preserves_schemac += -d:npegDotDir="../.."
: foreach preserves_schema_nim.nim schemaparse.nim |> !nim_bin |> $(BIN_DIR)/%B | $(BIN_DIR)/<%B>
DOT_FILES = ../../Atom.dot ../../Document.dot ../../Schema.dot
: preserves_schemac.nim |> !nim_bin |> $(BIN_DIR)/preserves-schemac | $(DOT_FILES) $(BIN_DIR)/<preserves-schemac>
: foreach $(DOT_FILES) |> dot -Tsvg -LO %f > %o |> ../../%B-Grammer-Graph.svg
: foreach *hooks.nim |> !nim_run |>

View File

@ -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

View File

@ -2,6 +2,7 @@
# SPDX-License-Identifier: Unlicense
## NPEG rules for Preserves.
## For an explanation of the syntax see https://github.com/zevv/npeg/blob/master/README.md#syntax.
import npeg, npeg/lib/utf8
@ -11,7 +12,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 +24,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 * '>'

View File

@ -10,7 +10,7 @@ import std/[hashes, sets, strutils, tables]
# Cannot use std/macros, must use compiler modules because
# we are generating code at run-time.
import compiler/[ast, idents, renderer, lineinfos]
import "$nim" / compiler/[ast, idents, renderer, lineinfos]
import ../preserves, ./schema

View File

@ -1,9 +0,0 @@
include_rules
GROUP = $(BIN_DIR)/<preserves_encode>
: preserves_encode.nim |> !nim |> $(BIN_DIR)/preserves_encode | $(GROUP) {bin}
!link = |> ^o symlink %o^ ln -s preserves_encode %o |> | $(GROUP)
: {bin} |> !link |> $(BIN_DIR)/preserves_decode
: {bin} |> !link |> $(BIN_DIR)/preserves_from_json
: {bin} |> !link |> $(BIN_DIR)/preserves_from_xml
: {bin} |> !link |> $(BIN_DIR)/preserves_to_json
: {bin} |> !link |> $(BIN_DIR)/preserves_to_xml

View File

@ -13,14 +13,15 @@ type BufferedDecoder* = object
proc newBufferedDecoder*(maxSize = 4096): BufferedDecoder =
## Create a new `newBufferedDecoder`.
runnableExamples:
import std/options, pkg/preserves
var
buf = newBufferedDecoder()
bin = encode(parsePreserves("<foobar>"))
buf.feed(bin[0..2])
buf.feed(bin[3..bin.high])
var (success, pr) = decode(buf)
assert success
assert $pr == "<foobar>"
var v = decode(buf)
assert v.isSome
assert $v.get == "<foobar>"
BufferedDecoder(
stream: newStringStream(newStringOfCap(maxSize)),
maxSize: maxSize,

View File

@ -15,14 +15,18 @@ 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)
of 0x80: return Value(kind: pkBoolean, bool: false)
of 0x81: return Value(kind: pkBoolean, bool: true)
else: discard
if s.atEnd:
raise newException(IOError, "End of Preserves stream")
case tag
of 0x85:
discard decodePreserves(s)
result = decodePreserves(s)
@ -99,30 +103,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)

View File

@ -1,7 +1,7 @@
# SPDX-FileCopyrightText: ☭ Emery Hemingway
# SPDX-License-Identifier: Unlicense
import std/[assertions, endians, streams]
import std/[algorithm, assertions, endians, streams]
import bigints
import ./values
@ -96,10 +96,22 @@ proc write*(str: Stream; pr: Value) =
str.write(val)
str.write(0x84'u8)
of pkDictionary:
var
keyIndices = newSeqOfCap[(string, int)](pr.dict.len)
keyBuffer = newStringStream()
for i in 0..pr.dict.high:
keyBuffer.write(pr.dict[i][0])
keyIndices.add((keyBuffer.data.move, i))
keyBuffer.setPosition(0)
# add each encoded key and its index to the seq
sort(keyIndices) do (a, b: (string, int)) -> int:
cmp(a[0], b[0])
# sort the seq by encoded keys
str.write(0xb7'u8)
for (key, value) in pr.dict.items:
str.write(key)
str.write(value)
for (keyBytes, i) in keyIndices:
str.write(keyBytes)
str.write(pr.dict[i][1])
# encode the values in sorted key order
str.write(0x84'u8)
of pkEmbedded:
# str.write(0x86'u8)

View File

@ -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) =

View File

@ -1,6 +1,7 @@
import
preserves, std/tables
std/tables,
../preserves
type
Ref* {.preservesRecord: "ref".} = object

View File

@ -86,7 +86,7 @@ const parser = peg("Schema", p: ParseState):
match(readFile path, state)
p.schema = move state.schema
Definition <- ?Annotation * *LineComment * id * '=' * S * (OrPattern | AndPattern | Pattern):
Definition <- *LineComment * ?Annotation * id * '=' * S * (OrPattern | AndPattern | Pattern):
if p.schema.definitions.hasKey(Symbol $1):
raise newException(ValueError, "duplicate definition of " & $0)
var
@ -254,7 +254,7 @@ const parser = peg("Schema", p: ParseState):
var node = initRecord(toSymbol"tuplePrefix", toPreserves fields, tail)
pushStack node
DictionaryPattern <- '{' * *(S * >Value * S * ':' * S * NamedSimplePattern * ?',') * S * '}':
DictionaryPattern <- '{' * S * *(*LineComment * >Value * S * ':' * S * NamedSimplePattern * ?',' * S) * '}':
var dict = initDictionary()
for i in countDown(pred capture.len, 1):
let key = toSymbol capture[i].s
@ -263,12 +263,12 @@ const parser = peg("Schema", p: ParseState):
pushStack n
NamedPattern <- ((atId * ?Annotation * SimplePattern) | Pattern):
if capture.len == 2:
if capture.len > 1:
var n = initRecord(toSymbol"named", toSymbol $1, popStack())
pushStack n
NamedSimplePattern <- ((atId * ?Annotation * SimplePattern) | SimplePattern):
if capture.len == 2:
if capture.len > 1:
var n = initRecord(toSymbol"named", toSymbol $1, popStack())
pushStack n
@ -287,7 +287,8 @@ const parser = peg("Schema", p: ParseState):
Value <- Preserves.Value:
discard
Annotation <- '@' * (Preserves.String | Preserves.Record) * S
Annotation <- '@' * (Preserves.String | Preserves.Record) * S:
discard
S <- *{ ' ', '\t', '\r', '\n' }

View File

@ -3,4 +3,5 @@
import ../preserves, ./private/macros
proc `%`*(n: SomeInteger): Value {.inline.} = n.toPreserves
proc `%`*(v: bool|SomeFloat|SomeInteger|string|seq[byte]|Symbol): Value {.inline.} = v.toPreserves
# Preserve an atomic Nim value.

View File

@ -1,7 +1,3 @@
include_rules
NIM_FLAGS_test_samples += -d:upstreamTestfile="$(TUP_CWD)/../../preserves/tests/samples.pr"
: foreach t*.nim |> !nim_run |> | ../<test>
: $(BIN_DIR)/<preserves-schemac> \
|> $(BIN_DIR)/preserves-schemac --no-bundle ../../preserves/doc/demo.prs | xxd > %o \
|> demo.xxd

83
tests/test_p_exprs.nim Normal file
View File

@ -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 |,|>
>
"""