Compare commits

...

4 Commits

Author SHA1 Message Date
Emery Hemingway 06898e4ec1 Update dataspace patterns protocol
Skeletons do not check for the presence of ignored entries in
pattern matches, this will cause bugs!
2024-04-22 10:54:44 +02:00
Emery Hemingway 2aaa588f6a drivers/timers: map deadlines to facets 2024-04-19 17:36:07 +02:00
Emery Hemingway e0b569e465 Update noise schema 2024-04-19 17:36:07 +02:00
Emery Hemingway 13d3995507 Update http driver to latest schema 2024-04-19 17:36:07 +02:00
13 changed files with 450 additions and 434 deletions

View File

@ -19,7 +19,7 @@ proc `!`*(typ: static typedesc): Pattern {.inline.} =
patterns.dropType(typ)
proc `?`*[T](val: T): Pattern {.inline.} =
patterns.grab[T](val)
patterns.drop[T](val)
proc `?:`*(typ: static typedesc): Pattern {.inline.} =
patterns.grabType(typ)
@ -30,9 +30,6 @@ proc `?:`*(typ: static typedesc; bindings: sink openArray[(int, Pattern)]): Patt
proc `?:`*(typ: static typedesc; bindings: sink openArray[(Value, Pattern)]): Pattern {.inline.} =
patterns.grab(typ, bindings)
proc `??`*(pat: Pattern; bindings: openArray[(int, Pattern)]): Pattern {.inline.} =
patterns.inject(pat, bindings)
type
PublishProc = proc (turn: var Turn; v: Value; h: Handle) {.closure.}
RetractProc = proc (turn: var Turn; h: Handle) {.closure.}

View File

@ -1,3 +1,4 @@
include_rules
NIM_FLAGS += --path:$(TUP_CWD)/..
: foreach *.nim |> !nim_check |>
: patterns.nim |> !nim_bin |>

View File

@ -16,8 +16,6 @@ when defined(posix):
proc echo(args: varargs[string, `$`]) {.used.} =
stderr.writeLine(args)
proc `$`(b: seq[byte]): string = cast[string](b)
proc badRequest(conn: Connection; msg: string) =
conn.send(SupportedVersion & " 400 " & msg, endOfMessage = true)
@ -33,6 +31,9 @@ proc extractQuery(s: var string): Table[Symbol, seq[QueryValue]] =
proc parseRequest(conn: Connection; text: string): (int, HttpRequest) =
## Parse an `HttpRequest` request out of a `text` from a `Connection`.
result[1].host = RequestHost(orKind: RequestHostKind.absent)
result[1].body = RequestBody(orKind: RequestBodyKind.absent)
var
token: string
off: int
@ -91,13 +92,13 @@ proc parseRequest(conn: Connection; text: string): (int, HttpRequest) =
for e in vals.mitems:
e = e.toLowerAscii
if k == Symbol"host":
result[1].host = e
result[1].host = RequestHost(orKind: RequestHostKind.`present`, present: e)
if v == "": v = move e
else:
v.add ", "
v.add e
if k == Symbol"host":
result[1].host = v
result[1].host = RequestHost(orKind: RequestHostKind.`present`, present: v)
result[1].headers[k] = v
result[0] = off
@ -140,6 +141,9 @@ proc send(ses: Session; chunk: Chunk) =
of ChunkKind.bytes:
ses.send(chunk.bytes)
func `==`(s: string; rh: RequestHost): bool =
rh.orKind == RequestHostKind.present and rh.present == s
proc match(b: HttpBinding, r: HttpRequest): bool =
## Check if `HttpBinding` `b` matches `HttpRequest` `r`.
result =
@ -241,9 +245,8 @@ proc service(turn: var Turn; exch: Exchange) =
if handler.isNone:
stop(turn)
else:
let
cap = newCap(turn, exch)
ctx = publish(turn, handler.get, HttpContext(
let cap = newCap(turn, exch)
publish(turn, handler.get, HttpContext(
req: exch.req,
res: embed cap,
))

View File

@ -1,9 +1,9 @@
# SPDX-FileCopyrightText: ☭ Emery Hemingway
# SPDX-License-Identifier: Unlicense
import std/times
import std/[tables, times]
import preserves
import ../../syndicate, ../bags, ../protocols/[timer, dataspace]
import ../../syndicate, ../protocols/[timer, dataspace]
when defined(solo5):
import solo5_dispatcher
@ -27,7 +27,7 @@ when defined(solo5):
## Owning facet of driver.
target: Cap
## Destination for LaterThan assertions.
deadlines: Bag[float]
deadlines: Table[float, Facet]
## Deadlines that other actors are observing.
proc spawnTimerDriver(facet: Facet; cap: Cap): TimerDriver =
@ -35,11 +35,12 @@ when defined(solo5):
proc await(driver: TimerDriver; deadline: float) {.solo5dispatch.} =
yieldUntil(deadline)
if deadline in driver.deadlines:
let facet = driver.deadlines.getOrDefault(deadline)
if not facet.isNil:
# check if the deadline is still observed
proc turnWork(turn: var Turn) =
discard publish(turn, driver.target, LaterThan(seconds: deadline))
run(driver.facet, turnWork)
run(facet, turnWork)
else:
import std/[oserrors, posix, sets]
@ -84,7 +85,7 @@ else:
## Owning facet of driver.
target: Cap
## Destination for LaterThan assertions.
deadlines: Bag[float]
deadlines: Table[float, Facet]
## Deadlines that other actors are observing.
timers: HashSet[cint]
# TODO: use a single timer descriptor
@ -100,7 +101,7 @@ else:
proc earliestFloat(driver: TimerDriver): float =
assert driver.deadlines.len > 0
result = high float
for deadline in driver.deadlines:
for deadline in driver.deadlines.keys:
if deadline < result:
result = deadline
@ -119,11 +120,12 @@ else:
# Check if the timer is expired which
# could happen before waiting.
wait(FD fd, Read)
if deadline in driver.deadlines:
let facet = driver.deadlines.getOrDefault(deadline)
if not facet.isNil:
# Check if the deadline is still observed.
proc turnWork(turn: var Turn) =
discard publish(turn, driver.target, LaterThan(seconds: deadline))
run(driver.facet, turnWork)
run(facet, turnWork)
discard close(fd)
driver.timers.excl(fd)
@ -132,16 +134,15 @@ proc spawnTimerDriver*(turn: var Turn; ds: Cap): Actor {.discardable.} =
## dataspace observations of timeouts on `ds`.
linkActor(turn, "timers") do (turn: var Turn):
let driver = spawnTimerDriver(turn.facet, ds)
let pat = inject(grab Observe(pattern: dropType LaterThan), {0: grabLit()})
let pat = observePattern(!LaterThan, {@[0.toPreserves]: grabLit()})
during(turn, ds, pat) do (deadline: float):
if change(driver.deadlines, deadline, +1) == cdAbsentToPresent:
discard trampoline(whelp await(driver, deadline))
driver.deadlines[deadline] = turn.facet
discard trampoline(whelp await(driver, deadline))
do:
discard change(driver.deadlines, deadline, -1, clamp = true)
# TODO: retract assertions that are unobserved.
driver.deadlines.del deadline
proc after*(turn: var Turn; ds: Cap; dur: Duration; act: TurnAction) =
## Execute `act` after some duration of time.
var later = wallFloat() + dur.inMilliseconds.float / 1_000.0
onPublish(turn, ds, grab LaterThan(seconds: later)):
onPublish(turn, ds, ?LaterThan(seconds: later)):
act(turn)

View File

@ -1,70 +1,46 @@
# SPDX-FileCopyrightText: ☭ Emery Hemingway
# SPDX-License-Identifier: Unlicense
import std/[algorithm, assertions, options, sequtils, tables, typetraits]
import std/[assertions, options, tables, typetraits]
import preserves
import ./protocols/dataspacePatterns
import ./protocols/[dataspacePatterns, dataspace]
from ./actors import Cap
export dataspacePatterns.`$`, PatternKind, DCompoundKind, AnyAtomKind
export dataspacePatterns.`$`, AnyAtomKind, GroupTypeKind, PatternKind
type
AnyAtom = dataspacePatterns.AnyAtom
DBind = dataspacePatterns.DBind
DCompound = dataspacePatterns.DCompound
DCompoundArr = dataspacePatterns.DCompoundArr
DCompoundDict = dataspacePatterns.DCompoundDict
DCompoundRec = dataspacePatterns.DCompoundRec
DLit = dataspacePatterns.DLit
Pattern* = dataspacePatterns.Pattern
iterator orderedEntries*(dict: DCompoundDict): (Value, Pattern) =
## Iterate a `DCompoundDict` in Preserves order.
## Values captured from a dictionary are represented as an
## array of values ordered by their former key, so using an
## ordered iterator is sometimes essential.
var keys = dict.entries.keys.toSeq
sort(keys, preserves.cmp)
for k in keys:
yield(k, dict.entries.getOrDefault(k))
# getOrDefault doesn't raise and we know the keys will match
proc toPattern(b: sink PatternBind): Pattern =
Pattern(orKind: PatternKind.`bind`, `bind`: b)
proc toPattern(d: sink DBind): Pattern =
Pattern(orKind: PatternKind.DBind, dbind: d)
proc toPattern(l: sink PatternLit): Pattern =
Pattern(orKind: PatternKind.`lit`, lit: l)
proc toPattern(d: sink DLit): Pattern =
Pattern(orKind: PatternKind.DLit, dlit: d)
proc toPattern(g: sink PatternGroup): Pattern =
Pattern(orKind: PatternKind.`group`, group: g)
proc toPattern(aa: sink AnyAtom): Pattern =
DLit(value: aa).toPattern
proc toPattern(a: sink AnyAtom): Pattern =
PatternLit(value: a).toPattern
proc toPattern(d: sink DCompound): Pattern =
Pattern(orKind: PatternKind.DCompound, dcompound: d)
proc grab*(p: sink Pattern): Pattern =
PatternBind(pattern: p).toPattern
proc toPattern(d: sink DCompoundRec): Pattern =
DCompound(orKind: DCompoundKind.rec, rec: d).toPattern
proc toPattern(d: sink DCompoundArr): Pattern =
DCompound(orKind: DCompoundKind.arr, arr: d).toPattern
proc toPattern(d: sink DCompoundDict): Pattern =
DCompound(orKind: DCompoundKind.dict, dict: d).toPattern
proc drop*(): Pattern {.inline.} = Pattern(orKind: PatternKind.DDiscard)
proc drop*(): Pattern = Pattern(orKind: PatternKind.`discard`)
## Create a pattern to match any value without capture.
proc grab*(): Pattern {.inline.} = DBind(pattern: drop()).toPattern
proc grab*(): Pattern = drop().grab()
## Create a pattern to capture any value.
proc grab*(pr: Value): Pattern =
proc drop*(pr: Value): Pattern =
## Convert a `Preserve` value to a `Pattern`.
runnableExamples:
from std/unittest import check
import preserves
check:
$(grab parsePreserves"""<foo "bar" #"00" [0 1 2.0] {maybe: #t} <_>>""") ==
"""<rec foo [<lit "bar"> <lit #"00"> <arr [<lit 0> <lit 1> <lit 2.0>]> <dict {maybe: <lit #t>}> <_>]>"""
$("""<foo "bar" #"00" [0 1 2.0] {maybe: #t} <_>>""".parsePreserves.drop) ==
"""<group <rec foo> {0: <lit "bar"> 1: <lit #"00"> 2: <group <arr> {0: <lit 0> 1: <lit 1> 2: <lit 2.0>}> 3: <group <dict> {maybe: <lit #t>}> 4: <_>}>"""
case pr.kind
of pkBoolean:
@ -73,43 +49,65 @@ proc grab*(pr: Value): Pattern =
AnyAtom(orKind: AnyAtomKind.`double`, double: pr.float).toPattern
of pkRegister:
AnyAtom(orKind: AnyAtomKind.`int`, int: pr.register).toPattern
of pkBigInt:
raiseAssert "cannot make a pattern over a big integer"
of pkString:
AnyAtom(orKind: AnyAtomKind.`string`, string: pr.string).toPattern
of pkByteString:
AnyAtom(orKind: AnyAtomKind.`bytes`, bytes: pr.bytes).toPattern
of pkSymbol:
AnyAtom(orKind: AnyAtomKind.`symbol`, symbol: pr.symbol).toPattern
of pkRecord:
if (pr.isRecord("_") and pr.arity == 0) or (pr.isRecord("bind") and pr.arity == 1):
if pr.isRecord("_", 0):
drop()
elif pr.isRecord("bind", 1):
pr.fields[0].drop
else:
DCompoundRec(
label: pr.label,
fields: map[Value, Pattern](pr.fields, grab)).toPattern
var group = PatternGroup(`type`: GroupType(orKind: GroupTypeKind.rec))
group.`type`.rec.label = pr.label
var i: int
for v in pr.fields:
group.entries[toPreserves i] = drop v
inc i
group.toPattern
of pkSequence:
DCompoundArr(items: map(pr.sequence, grab)).toPattern
var group = PatternGroup(`type`: GroupType(orKind: GroupTypeKind.arr))
for i, v in pr.sequence:
group.entries[toPreserves i] = drop v
group.toPattern
of pkSet:
raiseAssert "cannot construct a pattern over a set literal"
of pkDictionary:
var dict = DCompoundDict()
for key, val in pr.pairs: dict.entries[key] = grab val
dict.toPattern
var group = PatternGroup(`type`: GroupType(orKind: GroupTypeKind.dict))
for key, val in pr.pairs:
group.entries[key] = drop val
group.toPattern
of pkEmbedded:
if pr.embeddedRef.isNil: drop()
else:
AnyAtom(orKind: AnyAtomKind.`embedded`, embedded: pr.embeddedRef).toPattern
else:
raise newException(ValueError, "cannot generate a pattern for unhandled Value type")
#else:
# raise newException(ValueError, "cannot generate a pattern for unhandled Value type")
proc grab*[T](x: T): Pattern =
proc drop*[T](x: T): Pattern =
## Construct a `Pattern` from value of type `T`.
## This proc is called `drop` because the value `x` is matched but discarded.
runnableExamples:
from std/unittest import check
check:
$grab(true) == "<lit #t>"
$grab(3.14) == "<lit 3.14>"
$grab([0, 1, 2, 3]) == "<arr [<lit 0> <lit 1> <lit 2> <lit 3>]>"
grab(x.toPreserves)
$drop(true) == "<lit #t>"
$drop(3.14) == "<lit 3.14>"
$drop([0, 1, 2, 3]) == "<group <arr> {0: <lit 0> 1: <lit 1> 2: <lit 2> 3: <lit 3>}>"
drop(x.toPreserves)
proc grab*[T](x: T): Pattern {.
deprecated: "use drop unless you wish to capture the provided value".} =
PatternBind(pattern: drop x).toPattern
proc grabType*(typ: static typedesc): Pattern =
## Derive a `Pattern` from type `typ`.
@ -120,40 +118,41 @@ proc grabType*(typ: static typedesc): Pattern =
from std/unittest import check
check:
$grabType(array[3, int]) ==
"""<arr [<bind <_>> <bind <_>> <bind <_>>]>"""
"""<group <arr> {0: <bind <_>> 1: <bind <_>> 2: <bind <_>> 3: <bind <_>>}>"""
type
Point = tuple[x: int; y: int]
Rect {.preservesRecord: "rect".} = tuple[a: Point; B: Point]
ColoredRect {.preservesDictionary.} = tuple[color: string; rect: Rect]
check:
$(grabType Point) ==
"<arr [<bind <_>> <bind <_>>]>"
"<group <arr> {0: <bind <_>> 1: <bind <_>>}>"
$(grabType Rect) ==
"<rec rect [<arr [<bind <_>> <bind <_>>]> <arr [<bind <_>> <bind <_>>]>]>"
"<group <rec rect> {0: <group <arr> {0: <bind <_>> 1: <bind <_>>}> 1: <group <arr> {0: <bind <_>> 1: <bind <_>>}>}>"
$(grabType ColoredRect) ==
"<dict {color: <bind <_>> rect: <rec rect [<arr [<bind <_>> <bind <_>>]> <arr [<bind <_>> <bind <_>>]>]>}>"
"<group <dict> {color: <bind <_>> rect: <group <rec rect> {0: <group <arr> {0: <bind <_>> 1: <bind <_>>}> 1: <group <arr> {0: <bind <_>> 1: <bind <_>>}>}>}>"
when typ is ref:
grabType(pointerBase(typ))
elif typ.hasPreservesRecordPragma:
var rec = DCompoundRec(label: typ.recordLabel.toSymbol)
var group = PatternGroup(`type`: GroupType(orKind: GroupTypeKind.`rec`))
group.`type`.rec.label = typ.recordLabel.toSymbol
for _, f in fieldPairs(default typ):
add(rec.fields, grabType(typeof f))
result = rec.toPattern
group.entries[group.entries.len.toPreserves] = grabType(typeof f)
group.toPattern
elif typ.hasPreservesDictionaryPragma:
var dict = DCompoundDict()
var group = PatternGroup(`type`: GroupType(orKind: GroupTypeKind.`dict`))
for key, val in fieldPairs(default typ):
dict.entries[key.toSymbol] = grabType(typeof val)
dict.toPattern
group.entries[key.toSymbol] = grabType(typeof val)
group.toPattern
elif typ is tuple:
var arr = DCompoundArr()
var group = PatternGroup(`type`: GroupType(orKind: GroupTypeKind.`arr`))
for _, f in fieldPairs(default typ):
add(arr.items, grabType(typeof f))
arr.toPattern
group.entries[group.entries.len.toPreserves] = grabType(typeof f)
group.toPattern
elif typ is array:
var arr = DCompoundArr()
arr.items.setLen(len(typ))
for e in arr.items.mitems: e = grab()
arr.toPattern
var group = PatternGroup(`type`: GroupType(orKind: GroupTypeKind.`arr`))
for i in 0..len(typ):
group.entries[toPreserves i] = grab()
group.toPattern
else:
grab()
@ -166,59 +165,47 @@ proc dropType*(typ: static typedesc): Pattern =
when typ is ref:
dropType(pointerBase(typ))
elif typ.hasPreservesRecordPragma:
var rec = DCompoundRec(label: typ.recordLabel.toSymbol)
rec.fields.setLen(fieldCount typ)
for i, _ in rec.fields:
rec.fields[i] = drop()
result = rec.toPattern
var group = PatternGroup(`type`: GroupType(orKind: GroupTypeKind.`rec`))
group.`type`.rec.label = typ.recordLabel.toSymbol
let high = typ.fieldCount.pred
if high >= 0: group.entries[high.toPreserves] = drop()
group.toPattern
elif typ.hasPreservesDictionaryPragma:
DCompoundDict().toPattern
elif typ is tuple:
var arr = DCompoundArr()
arr.items.setLen(len typ)
for i, _ in arr.items:
arr.items[i] = drop()
arr.toPattern
elif typ is array:
var arr = DCompoundArr()
arr.items.setLen(len(typ))
for e in arr.items.mitems: e = drop()
arr.toPattern
PatternGroup(`type`: GroupType(orKind: GroupTypeKind.`dict`)).toPattern
elif typ is tuple or typ is array:
var group = PatternGroup(`type`: GroupType(orKind: GroupTypeKind.`arr`))
let high = typ.fieldCount.pred
if high >= 0: group.entries[high.toPreserves] = drop()
group.toPattern
else:
drop()
proc lookup(bindings: openArray[(int, Pattern)]; i: int): Pattern =
for (j, b) in bindings:
if i == j: return b
return drop()
proc bindEntries(group: var PatternGroup; bindings: openArray[(int, Pattern)]) =
## Set `bindings` for a `group`.
for (i, pat) in bindings: group.entries[toPreserves i] = pat
proc grab*(typ: static typedesc; bindings: sink openArray[(int, Pattern)]): Pattern =
## Construct a `Pattern` from type `typ` with pattern `bindings` by integer offset.
when typ is ptr | ref:
grab(pointerBase(typ), bindings)
elif typ.hasPreservesRecordPragma:
var rec = DCompoundRec(label: typ.recordLabel.toSymbol)
rec.fields.setLen(fieldCount typ)
var i: int
for _, f in fieldPairs(default typ):
rec.fields[i] = lookup(bindings, i)
inc i
result = rec.toPattern
var group = PatternGroup(`type`: GroupType(orKind: GroupTypeKind.`rec`))
group.`type`.rec.label = typ.recordLabel.toSymbol
bindEntries(group, bindings)
group.toPattern
elif typ is tuple:
var arr = DCompoundArr()
arr.items.setLen(fieldCount typ)
var i: int
for _, f in fieldPairs(default typ):
arr.items[i] = lookup(bindings, i)
inc i
result = arr.toPattern
var group = PatternGroup(`type`: GroupType(orKind: GroupTypeKind.`arr`))
bindEntries(group, bindings)
group.toPattern
else:
{.error: "grab with indexed bindings not implemented for " & $typ.}
proc grab*(typ: static typedesc; bindings: sink openArray[(Value, Pattern)]): Pattern =
## Construct a `Pattern` from type `typ` with dictionary field `bindings`.
when typ.hasPreservesDictionaryPragma:
DCompoundDict(entries: bindings.toTable).toPattern
var group = PatternGroup(`type`: GroupType(orKind: GroupTypeKind.`dict`))
for key, val in bindinds: group.entries[key] = val
group.toPattern
else:
{.error: "grab with dictionary bindings not implemented for " & $typ.}
@ -226,11 +213,11 @@ proc grabLit*(): Pattern =
runnableExamples:
from std/unittest import check
check:
$grabLit() == """<rec lit [<bind <_>>]>"""
grabType(dataspacePatterns.DLit)
$grabLit() == """<group <rec lit> {0: <bind <_>>}>"""
grabType(dataspacePatterns.PatternLit)
proc grabDict*(): Pattern =
grabType(dataspacePatterns.DCompoundDict)
grabType(dataspacePatterns.GroupTypeDict)
proc unpackLiterals*(pr: Value): Value =
result = pr
@ -238,64 +225,42 @@ proc unpackLiterals*(pr: Value): Value =
if pr.isRecord("lit", 1) or pr.isRecord("dict", 1) or pr.isRecord("arr", 1) or pr.isRecord("set", 1):
pr = pr.record[0]
proc inject*(pat: Pattern; bindings: openArray[(int, Pattern)]): Pattern =
## Construct a `Pattern` from `pat` with injected overrides from `bindings`.
proc inject*(pattern: sink Pattern; p: Pattern; path: varargs[Value, toPreserves]): Pattern =
## Inject `p` inside `pattern` at `path`.
## Injects are made at offsets indexed by the discard (`<_>`) patterns in `pat`.
proc inject(pat: Pattern; bindings: openArray[(int, Pattern)]; offset: var int): Pattern =
case pat.orKind
of PatternKind.DDiscard:
result = pat
for (off, injection) in bindings:
if off == offset:
result = injection
break
inc offset
of PatternKind.DBind:
let bindOff = offset
result = pat
result.dbind.pattern = inject(pat.dbind.pattern, bindings, offset)
if result.orKind == PatternKind.DBind:
for (off, injection) in bindings:
if (off == bindOff) and (result.dbind.pattern == injection):
result = result.dbind.pattern
break # promote the injected pattern over the bind
of PatternKind.DLit:
result = pat
of PatternKind.DCompound:
result = pat
case pat.dcompound.orKind
of DCompoundKind.rec:
var fields = mapIt(pat.dcompound.rec.fields):
inject(it, bindings, offset)
result = DCompoundRec(label: result.dcompound.rec.label, fields: fields).toPattern
of DCompoundKind.arr:
var items = mapIt(pat.dcompound.arr.items):
inject(it, bindings, offset)
result = DCompoundArr(items: items).toPattern
of DCompoundKind.dict:
var dict = DCompoundDict()
for key, val in pat.dcompound.dict.entries:
dict.entries[key] = inject(val, bindings, offset)
result = toPattern(dict)
var offset = 0
inject(pat, bindings, offset)
proc inject*(pat: Pattern; bindings: openArray[(Value, Pattern)]): Pattern =
## Inject `bindings` into a dictionary pattern.
assert pat.orKind == PatternKind.DCompound
assert pat.dcompound.orKind == DCompoundKind.dict
result = pat
for (key, val) in bindings:
result.dcompound.dict.entries[key] = val
proc inject(pat: var Pattern; path: openarray[Value]) =
if len(path) == 0:
pat = p
elif pat.orKind != PatternKind.`group`:
raise newException(ValueError, "cannot inject along specified path")
else:
inject(pat.group.entries[path[0]], path[1..path.high])
result = pattern
inject(result, path)
proc grabRecord*(label: Value, fields: varargs[Pattern]): Pattern =
runnableExamples:
from std/unittest import check
import syndicate/actors, preserves
import preserves
check:
$grabRecord("Says".toSymbol, grab(), grab()) ==
"""<rec Says [<bind <_>> <bind <_>>]>"""
DCompoundRec(label: label, fields: fields.toSeq).toPattern
"""<group <rec Says> {0: <bind <_>> 1: <bind <_>>}>"""
var group = PatternGroup(`type`: GroupType(orKind: GroupTypeKind.`rec`))
group.`type`.rec.label = label
for i, f in fields: group.entries[toPreserves i] = f
group.toPattern
proc grabRecord*(label: Value, fields: sink openArray[(int, Pattern)]): Pattern =
runnableExamples:
from std/unittest import check
import preserves
check:
$grabRecord("Says".toSymbol, {3: grab(), 4: grab()}) ==
"""<group <rec Says> {3: <bind <_>> 4: <bind <_>>}>"""
var group = PatternGroup(`type`: GroupType(orKind: GroupTypeKind.`rec`))
group.`type`.rec.label = label
for (i, p) in fields: group.entries[toPreserves i] = p
group.toPattern
proc grabRecord*(label: string, fields: varargs[Pattern]): Pattern =
## Sugar for creating record patterns.
@ -304,47 +269,61 @@ proc grabRecord*(label: string, fields: varargs[Pattern]): Pattern =
proc grabDictionary*(bindings: sink openArray[(Value, Pattern)]): Pattern =
## Construct a pattern that grabs some dictionary pairs.
DCompoundDict(entries: bindings.toTable).toPattern
var group = PatternGroup(`type`: GroupType(orKind: GroupTypeKind.`dict`))
for (key, val) in bindings: group.entries[key] = val
group.toPattern
proc grabDictionary*(bindings: sink openArray[(string, Pattern)]): Pattern =
## Construct a pattern that grabs some dictionary pairs.
## Keys are converted from strings to symbols.
result = DCompoundDict().toPattern
for (key, val) in bindings.items:
result.dcompound.dict.entries[key.toSymbol] = val
var group = PatternGroup(`type`: GroupType(orKind: GroupTypeKind.`dict`))
for (key, val) in bindings: group.entries[toSymbol key] = val
group.toPattern
proc depattern(comp: DCompound; values: var seq[Value]; index: var int): Value
proc depattern(group: PatternGroup; values: var seq[Value]; index: var int): Value
proc depattern(pat: Pattern; values: var seq[Value]; index: var int): Value =
case pat.orKind
of PatternKind.DDiscard:
of PatternKind.`discard`:
discard
of PatternKind.DBind:
of PatternKind.`bind`:
if index < values.len:
result = move values[index]
inc index
of PatternKind.DLit:
result = pat.dlit.value.toPreserves
of PatternKind.DCompound:
result = depattern(pat.dcompound, values, index)
of PatternKind.`lit`:
result = pat.`lit`.value.toPreserves
of PatternKind.`group`:
result = depattern(pat.group, values, index)
proc depattern(comp: DCompound; values: var seq[Value]; index: var int): Value =
case comp.orKind
of DCompoundKind.rec:
result = initRecord(comp.rec.label, comp.rec.fields.len)
for i, f in comp.rec.fields:
result[i] = depattern(f, values, index)
of DCompoundKind.arr:
result = initSequence(comp.arr.items.len)
for i, e in comp.arr.items:
result[i] = depattern(e, values, index)
of DCompoundKind.dict:
proc depattern(group: PatternGroup; values: var seq[Value]; index: var int): Value =
case group.`type`.orKind
of GroupTypeKind.rec:
result = initRecord(group.`type`.rec.label, group.entries.len)
var i: int
for key, val in group.entries:
if i.fromPreserves key:
result[i] = depattern(val, values, index)
of GroupTypeKind.arr:
result = initSequence(group.entries.len)
var i: int
for key, val in group.entries:
if i.fromPreserves key:
result[i] = depattern(val, values, index)
of GroupTypeKind.dict:
result = initDictionary(Cap)
for key, val in comp.dict.entries:
for key, val in group.entries:
result[key] = depattern(val, values, index)
proc depattern*(pat: Pattern; values: sink seq[Value]): Value =
## Convert a `Pattern` to a `Value` while replacing binds with `values`.
runnableExamples:
from std/unittest import check
import preserves
type Foo {.preservesRecord: "foo".} = object
a, b: int
let pat = grabType Foo
let val = depattern(pat, @[1.toPreserves, 5.toPreserves])
check $val == "<foo 1 5>"
var index: int
depattern(pat, values, index)
@ -359,44 +338,73 @@ proc fromPreservesHook*[T](lit: var Literal[T]; pr: Value): bool =
proc toPreservesHook*[T](lit: Literal[T]): Value =
lit.value.grab.toPreserves
func isGroup(pat: Pattern): bool =
pat.orKind == PatternKind.`group`
func isMetaDict(pat: Pattern): bool =
pat.orKind == PatternKind.`group` and
pat.group.type.orKind == GroupTypeKind.dict
proc metaApply(result: var Pattern; pat: Pattern; path: openarray[Value], offset: int) =
if offset == path.len:
result = pat
elif result.isGroup and result.group.entries[1.toPreserves].isMetaDict:
if offset == path.high:
result.group.entries[1.toPreserves].group.entries[path[offset]] = pat
else:
metaApply(result.group.entries[1.toPreserves].group.entries[path[offset]], pat, path, succ offset)
else:
assert result.isGroup, "non-group: " & $result
assert result.group.entries[1.toPreserves].isMetaDict, "non-meta-dict: " & $result.group.entries[1.toPreserves]
raise newException(ValueError, "cannot inject into non-group pattern " & $result)
proc observePattern*(pat: Pattern; injects: openarray[(seq[Value], Pattern)]): Pattern =
result = dropType Observe
var meta = pat.toPreserves.drop
for (path, pat) in injects:
metaApply(meta, pat, path, 0)
result.group.entries[0.toPreserves] = meta
type
Path* = seq[Value]
Paths* = seq[Path]
Captures* = seq[Value]
Analysis* = tuple
presentPaths: Paths
constPaths: Paths
constValues: seq[Value]
capturePaths: Paths
func walk(result: var Analysis; path: var Path; p: Pattern)
func walk(result: var Analysis; path: var Path; key: int|Value; pat: Pattern) =
path.add(key.toPreserves)
func walk(result: var Analysis; path: var Path; key: Value; pat: Pattern) =
path.add(key)
walk(result, path, pat)
discard path.pop
func walk(result: var Analysis; path: var Path; p: Pattern) =
case p.orKind
of PatternKind.DCompound:
case p.dcompound.orKind
of DCompoundKind.rec:
for k, e in p.dcompound.rec.fields: walk(result, path, k, e)
of DCompoundKind.arr:
for k, e in p.dcompound.arr.items: walk(result, path, k, e)
of DCompoundKind.dict:
for k, e in p.dcompound.dict.orderedEntries: walk(result, path, k, e)
of PatternKind.DBind:
of PatternKind.group:
for k, v in p.group.entries: walk(result, path, k, v)
of PatternKind.`bind`:
result.capturePaths.add(path)
walk(result, path, p.dbind.pattern)
of PatternKind.DDiscard: discard
of PatternKind.DLit:
walk(result, path, p.`bind`.pattern)
of PatternKind.`discard`:
result.presentPaths.add(path)
of PatternKind.`lit`:
result.constPaths.add(path)
result.constValues.add(p.dlit.value.toPreserves)
result.constValues.add(p.`lit`.value.toPreserves)
func analyse*(p: Pattern): Analysis =
var path: Path
walk(result, path, p)
func checkPresence*(v: Value; present: Paths): bool =
result = true
for path in present:
if not result: break
result = step(v, path).isSome
func projectPaths*(v: Value; paths: Paths): Option[Captures] =
var res = newSeq[Value](paths.len)
for i, path in paths:
@ -408,30 +416,27 @@ func projectPaths*(v: Value; paths: Paths): Option[Captures] =
proc matches*(pat: Pattern; pr: Value): bool =
let analysis = analyse(pat)
assert analysis.constPaths.len == analysis.constValues.len
for i, path in analysis.constPaths:
let v = step(pr, path)
if v.isNone: return false
if analysis.constValues[i] != v.get: return false
for path in analysis.capturePaths:
if isNone step(pr, path): return false
true
result = checkPresence(pr, analysis.presentPaths)
if result:
for i, path in analysis.constPaths:
let v = step(pr, path)
if v.isNone: return false
if analysis.constValues[i] != v.get: return false
for path in analysis.capturePaths:
if step(pr, path).isNone: return false
func capture*(pat: Pattern; pr: Value): seq[Value] =
proc capture*(pat: Pattern; pr: Value): seq[Value] =
let analysis = analyse(pat)
assert analysis.constPaths.len == analysis.constValues.len
for i, path in analysis.constPaths:
let v = step(pr, path)
if v.isNone : return @[]
if analysis.constValues[i] != v.get: return @[]
for path in analysis.capturePaths:
let v = step(pr, path)
if v.isNone: return @[]
result.add(get v)
if checkPresence(pr, analysis.presentPaths):
for i, path in analysis.constPaths:
let v = step(pr, path)
if v.isNone : return @[]
if analysis.constValues[i] != v.get: return @[]
for path in analysis.capturePaths:
let v = step(pr, path)
if v.isNone: return @[]
result.add(get v)
when isMainModule:
let txt = readAll stdin
if txt != "":
let
v = parsePreserves(txt)
pat = grab v
stdout.writeLine(pat)
stdout.writeLine stdin.readAll.parsePreserves.grab

View File

@ -29,58 +29,58 @@ type
`embedded`* {.preservesEmbedded.}: EmbeddedRef
DLit* {.preservesRecord: "lit".} = object
`value`*: AnyAtom
DBind* {.preservesRecord: "bind".} = object
`pattern`*: Pattern
DDiscard* {.preservesRecord: "_".} = object
DCompoundKind* {.pure.} = enum
GroupTypeKind* {.pure.} = enum
`rec`, `arr`, `dict`
DCompoundRec* {.preservesRecord: "rec".} = object
GroupTypeRec* {.preservesRecord: "rec".} = object
`label`*: Value
`fields`*: seq[Pattern]
DCompoundArr* {.preservesRecord: "arr".} = object
`items`*: seq[Pattern]
GroupTypeArr* {.preservesRecord: "arr".} = object
GroupTypeDict* {.preservesRecord: "dict".} = object
`GroupType`* {.preservesOr.} = object
case orKind*: GroupTypeKind
of GroupTypeKind.`rec`:
`rec`*: GroupTypeRec
DCompoundDict* {.preservesRecord: "dict".} = object
`entries`*: Table[Value, Pattern]
of GroupTypeKind.`arr`:
`arr`*: GroupTypeArr
`DCompound`* {.preservesOr.} = object
case orKind*: DCompoundKind
of DCompoundKind.`rec`:
`rec`* {.preservesEmbedded.}: DCompoundRec
of DCompoundKind.`arr`:
`arr`* {.preservesEmbedded.}: DCompoundArr
of DCompoundKind.`dict`:
`dict`* {.preservesEmbedded.}: DCompoundDict
of GroupTypeKind.`dict`:
`dict`*: GroupTypeDict
PatternKind* {.pure.} = enum
`DDiscard`, `DBind`, `DLit`, `DCompound`
`discard`, `bind`, `lit`, `group`
PatternDiscard* {.preservesRecord: "_".} = object
PatternBind* {.preservesRecord: "bind".} = object
`pattern`*: Pattern
PatternLit* {.preservesRecord: "lit".} = object
`value`*: AnyAtom
PatternGroup* {.preservesRecord: "group".} = object
`type`*: GroupType
`entries`*: Table[Value, Pattern]
`Pattern`* {.acyclic, preservesOr.} = ref object
case orKind*: PatternKind
of PatternKind.`DDiscard`:
`ddiscard`*: DDiscard
of PatternKind.`discard`:
`discard`*: PatternDiscard
of PatternKind.`DBind`:
`dbind`* {.preservesEmbedded.}: DBind
of PatternKind.`bind`:
`bind`* {.preservesEmbedded.}: PatternBind
of PatternKind.`DLit`:
`dlit`* {.preservesEmbedded.}: DLit
of PatternKind.`lit`:
`lit`* {.preservesEmbedded.}: PatternLit
of PatternKind.`DCompound`:
`dcompound`* {.preservesEmbedded.}: DCompound
of PatternKind.`group`:
`group`* {.preservesEmbedded.}: PatternGroup
proc `$`*(x: AnyAtom | DLit | DBind | DDiscard | DCompound | Pattern): string =
proc `$`*(x: AnyAtom | GroupType | Pattern): string =
`$`(toPreserves(x))
proc encode*(x: AnyAtom | DLit | DBind | DDiscard | DCompound | Pattern): seq[
byte] =
proc encode*(x: AnyAtom | GroupType | Pattern): seq[byte] =
encode(toPreserves(x))

View File

@ -47,7 +47,7 @@ type
HttpRequest* {.preservesRecord: "http-request".} = object
`sequenceNumber`*: BiggestInt
`host`*: string
`host`*: RequestHost
`port`*: BiggestInt
`method`*: Symbol
`path`*: seq[string]
@ -115,6 +115,17 @@ type
`req`*: HttpRequest
`res`* {.preservesEmbedded.}: Value
RequestHostKind* {.pure.} = enum
`present`, `absent`
`RequestHost`* {.preservesOr.} = object
case orKind*: RequestHostKind
of RequestHostKind.`present`:
`present`*: string
of RequestHostKind.`absent`:
`absent`* {.preservesLiteral: "#f".}: bool
PathPatternElementKind* {.pure.} = enum
`label`, `wildcard`, `rest`
`PathPatternElement`* {.preservesOr.} = object
@ -149,6 +160,7 @@ proc `$`*(x: HostPattern | HttpListener | MethodPattern | MimeType | QueryValue
HttpService |
HttpBinding |
HttpContext |
RequestHost |
PathPatternElement |
Chunk |
PathPattern): string =
@ -163,6 +175,7 @@ proc encode*(x: HostPattern | HttpListener | MethodPattern | MimeType |
HttpService |
HttpBinding |
HttpContext |
RequestHost |
PathPatternElement |
Chunk |
PathPattern): seq[byte] =

View File

@ -48,6 +48,17 @@ type
`absent`*: SecretKeyFieldAbsent
SessionItemKind* {.pure.} = enum
`Initiator`, `Packet`
`SessionItem`* {.preservesOr.} = object
case orKind*: SessionItemKind
of SessionItemKind.`Initiator`:
`initiator`* {.preservesEmbedded.}: Initiator
of SessionItemKind.`Packet`:
`packet`*: Packet
NoiseProtocolKind* {.pure.} = enum
`present`, `invalid`, `absent`
NoiseProtocolPresent* {.preservesDictionary.} = object
@ -83,6 +94,9 @@ type
`service`*: ServiceSelector
ServiceSelector* = Value
Initiator* {.preservesRecord: "initiator".} = object
`initiatorSession`* {.preservesEmbedded.}: EmbeddedRef
NoiseStepDetail* = ServiceSelector
NoiseSpecKey* = seq[byte]
NoiseSpecPreSharedKeys* = Option[Value]
@ -105,17 +119,21 @@ type
proc `$`*(x: NoiseDescriptionDetail | NoisePreSharedKeys | SecretKeyField |
SessionItem |
NoiseProtocol |
NoisePathStepDetail |
NoiseServiceSpec |
Initiator |
NoiseSpec |
Packet): string =
`$`(toPreserves(x))
proc encode*(x: NoiseDescriptionDetail | NoisePreSharedKeys | SecretKeyField |
SessionItem |
NoiseProtocol |
NoisePathStepDetail |
NoiseServiceSpec |
Initiator |
NoiseSpec |
Packet): seq[byte] =
encode(toPreserves(x))

View File

@ -251,7 +251,7 @@ proc recv(relay: Relay; buf: openarray[byte]; slice: Slice[int]) =
var pr = decode(relay.wireBuf)
if pr.isSome: dispatch(relay, pr.get)
proc recv(relay: Relay; buf: openarray[byte]) =
proc recv(relay: Relay; buf: openarray[byte]) {.used.} =
feed(relay.wireBuf, buf)
var pr = decode(relay.wireBuf)
if pr.isSome: dispatch(relay, pr.get)
@ -393,11 +393,12 @@ when defined(posix):
entity.alive = false
close(entity.sock)
onStop(turn, kill)
publish(turn, ds, TransportConnection(
var ass = TransportConnection(
`addr`: ta.toPreserves,
control: newCap(entity, turn),
resolved: entity.relay.peer.accepted,
))
)
publish(turn, ds, ass)
run(entity.relay.facet, setup)
let buf = new seq[byte]
entity.alive = true
@ -549,31 +550,28 @@ proc connectRoute(turn: var Turn; ds: Cap; route: Route; transOff: int) =
onPublish(turn, ds, acceptPat) do (origin: Cap):
walk(turn, ds, origin, route, transOff, 0)
type StepCallback = proc (turn: var Turn; step: Value; origin, next: Cap) {.closure.}
type StepCallback = proc (turn: var Turn; step: Value; origin: Cap; res: Resolved) {.closure.}
proc spawnStepResolver(turn: var Turn; ds: Cap; stepType: Value; cb: StepCallback) =
let stepPat = grabRecord(stepType, grab())
let pat = ?Observe(pattern: ResolvedPathStep?:{1: stepPat}) ?? {0: grabLit(), 1: grab()}
let pat = observePattern(
ResolvedPathStep?:{1: grabRecord(stepType)},
{ @[0.toPreserve]: grabLit(), @[1.toPreserve]: grab() },
)
during(turn, ds, pat) do (origin: Cap; stepDetail: Literal[Value]):
let step = toRecord(stepType, stepDetail.value)
proc duringCallback(turn: var Turn; ass: Value; h: Handle): TurnAction =
var res = ass.preservesTo Resolved
if res.isSome:
if res.get.orKind == ResolvedKind.accepted and
res.get.accepted.responderSession of Cap:
cb(turn, step, origin, res.get.accepted.responderSession.Cap)
else:
publish(turn, ds, ResolvedPathStep(
origin: origin, pathStep: step, resolved: res.get))
var res: Resolved
if res.fromPreserves ass:
cb(turn, stepDetail.value, origin, res)
proc action(turn: var Turn) =
stop(turn)
result = action
publish(turn, origin, Resolve(
step: step, observer: newCap(turn, during(duringCallback))))
step: stepDetail.value, observer: newCap(turn, during(duringCallback))))
proc spawnRelays*(turn: var Turn; ds: Cap) =
## Spawn actors that manage routes and appeasing gatekeepers.
let transPat = ?Observe(pattern: !TransportConnection) ?? { 0: grab() }
## Spawn actors that manage routes and appease gatekeepers.
let transPat = observePattern(!TransportConnection, { @[0.toPreserves]: grab() })
# Use a generic pattern and type matching
# in the during handler because it is easy.
@ -600,20 +598,21 @@ proc spawnRelays*(turn: var Turn; ds: Cap) =
resolved: rejected(embed e),
))
let resolvePat = ?Observe(pattern: !ResolvePath) ?? {0: grab()}
let resolvePat = observePattern(!ResolvePath, {@[0.toPreserves]: grab()})
during(turn, ds, resolvePat) do (route: Literal[Route]):
for i, transAddr in route.value.transports:
connectRoute(turn, ds, route.value, i)
spawnStepResolver(turn, ds, "ref".toSymbol) do (
turn: var Turn, step: Value, origin: Cap, next: Cap):
turn: var Turn, step: Value, origin: Cap, res: Resolved):
publish(turn, ds, ResolvedPathStep(
origin: origin, pathStep: step, resolved: next.accepted))
origin: origin, pathStep: step, resolved: res))
type BootProc* = proc (turn: var Turn; ds: Cap) {.closure.}
proc resolve*(turn: var Turn; ds: Cap; route: Route; bootProc: BootProc) =
## Resolve `route` within `ds` and call `bootProc` with resolved capabilities.
let pat = ResolvePath ?: {0: ?route, 3: ?:ResolvedAccepted}
during(turn, ds, ResolvePath ?: {0: ?route, 3: ?:ResolvedAccepted}) do (dst: Cap):
bootProc(turn, dst)

View File

@ -9,32 +9,28 @@ import ./actors, ./bags, ./patterns
import ./protocols/dataspacePatterns
type
DCompound = dataspacePatterns.DCompound
Pattern = dataspacePatterns.Pattern
Path = seq[Value]
ClassKind = enum classNone, classRecord, classSequence, classDictionary
Class = object
kind: ClassKind
label: Value
arity: int
func classOf(v: Value): Class =
case v.kind
of pkRecord: Class(kind: classRecord, label: v.label, arity: v.arity)
of pkSequence: Class(kind: classSequence, arity: v.len)
of pkRecord: Class(kind: classRecord, label: v.label)
of pkSequence: Class(kind: classSequence)
of pkDictionary: Class(kind: classDictionary)
else: Class(kind: classNone)
proc classOf(p: Pattern): Class =
if p.orKind == PatternKind.DCompound:
case p.dcompound.orKind
of DCompoundKind.rec:
Class(kind: classRecord,
label: p.dcompound.rec.label,
arity: p.dcompound.rec.fields.len)
of DCompoundKind.arr:
Class(kind: classSequence, arity: p.dcompound.arr.items.len)
of DCompoundKind.dict:
if p.orKind == PatternKind.group:
case p.group.type.orKind
of GroupTypeKind.rec:
Class(kind: classRecord, label: p.group.`type`.rec.label)
of GroupTypeKind.arr:
Class(kind: classSequence)
of GroupTypeKind.dict:
Class(kind: classDictionary)
else:
Class(kind: classNone)
@ -69,13 +65,14 @@ type
LeafProc = proc (l: Leaf; v: Value) {.closure.}
ObserverProc = proc (turn: var Turn; group: ObserverGroup; vs: seq[Value]) {.closure.}
proc getLeaves(cont: Continuation; constPaths: Paths): LeafMap =
proc getLeaves(cont: Continuation; presentPaths, constPaths: Paths): LeafMap =
result = cont.leafMap.getOrDefault(constPaths)
if result.isNil:
new result
cont.leafMap[constPaths] = result
assert not cont.isEmpty
for ass in cont.cache:
# TODO: check presence
let key = projectPaths(ass, constPaths)
if key.isSome:
var leaf = result.getOrDefault(get key)
@ -165,25 +162,13 @@ proc getOrNew[A, B, C](t: var Table[A, TableRef[B, C]], k: A): TableRef[B, C] =
result = newTable[B, C]()
t[k] = result
iterator pairs(dc: DCompound): (Value, Pattern) =
case dc.orKind
of DCompoundKind.rec:
for i, p in dc.rec.fields:
yield (i.toPreserves, p,)
of DCompoundKind.arr:
for i, p in dc.arr.items:
yield (i.toPreserves, p,)
of DCompoundKind.dict:
for pair in dc.dict.entries.pairs:
yield pair
proc extendWalk(node: Node; popCount: Natural; stepIndex: Value; pat: Pattern; path: var Path): tuple[popCount: Natural, nextNode: Node] =
case pat.orKind
of PatternKind.DDiscard, PatternKind.DLit:
of PatternKind.`discard`, PatternKind.lit:
result = (popCount, node)
of PatternKind.DBind:
result = extendWalk(node, popCount, stepIndex, pat.dbind.pattern, path)
of PatternKind.DCompound:
of PatternKind.`bind`:
result = extendWalk(node, popCount, stepIndex, pat.`bind`.pattern, path)
of PatternKind.`group`:
let
selector: Selector = (popCount, stepIndex,)
table = node.edges.getOrNew(selector)
@ -198,7 +183,7 @@ proc extendWalk(node: Node; popCount: Natural; stepIndex: Value; pat: Pattern; p
if v.isSome and class == classOf(get v):
result.nextNode.continuation.cache.incl a
result.popCount = 0
for step, p in pat.dcompound.pairs:
for step, p in pat.group.entries:
add(path, step)
result = extendWalk(result.nextNode, result.popCount, step, p, path)
discard pop(path)
@ -231,7 +216,7 @@ proc add*(index: var Index; turn: var Turn; pattern: Pattern; observer: Cap) =
let
cont = index.root.extend(pattern)
analysis = analyse pattern
constValMap = cont.getLeaves(analysis.constPaths)
constValMap = cont.getLeaves(analysis.presentPaths, analysis.constPaths)
leaf = constValMap.getLeaf(analysis.constValues)
endpoints = leaf.getEndpoints(analysis.capturePaths)
# TODO if endpoints.cachedCaptures.len > 0:

View File

@ -1,6 +1,6 @@
# Package
version = "20240408"
version = "20240421"
author = "Emery Hemingway"
description = "Syndicated actors for conversational concurrency"
license = "Unlicense"

View File

@ -1,103 +1,87 @@
# SPDX-FileCopyrightText: ☭ Emery Hemingway
# SPDX-License-Identifier: Unlicense
import std/[options, tables, unittest]
import std/[options, sequtils, tables, unittest]
import preserves, syndicate, syndicate/protocols/gatekeeper
import preserves, syndicate, syndicate/protocols/[gatekeeper, timer]
import ./test_schema
test "patterns":
let
pat = ?Observe(pattern: !Foo) ?? {0: grab()}
text = """<rec Observe [<rec rec [<lit foo> <arr [<bind <_>> <_> <_>]>]> <_>]>"""
check($pat == text)
suite "example":
var pat: Pattern
check pat.fromPreserves parsePreserves"""
<group <arr> {
0: <lit 1>
1: <bind <group <arr> {
0: <bind <_>>
1: <_>
}>>
2: <_>
}>
"""
let
worte = @["alles", "in", "ordnung"]
observer = Observe(pattern: inject(?:Foo, { 0: ?worte })).toPreserves
have = capture(pat, observer).toPreserves.unpackLiterals
want = [worte.toPreserves].toPreserves
check(have == want)
const A = "[1 2 3]"
test A:
let v = parsePreserves A
check:
not pat.matches(v)
type Obj {.preservesDictionary.} = object
a, b, c: int
test "dictionaries":
let pat = ?:Obj
var source = initDictionary(Cap)
source["b".toSymbol] = 2.toPreserves
source["c".toSymbol] = 3.toPreserves
source["a".toSymbol] = 1.toPreserves
let values = capture(pat, source)
check values.len == 3
check values[0] == 1.toPreserves
check values[1] == 2.toPreserves
check values[2] == 3.toPreserves
type
File {.preservesDictionary.} = object
name: string
path: string
size: BiggestInt
`type`: string
Files = Table[Symbol, File]
Fields = Table[Symbol, string]
Request {.preservesRecord: "request".} = object
seq: BiggestInt
fields: Fields
files: Files
test "literals":
const txt = """<rec request [<lit 3> <dict {artists: <lit "kyyyyym"> date: <lit "2023-10-14"> notes: <lit "Lots of stuff"> title: <lit "Domes show">}> <dict {front-cover: <dict {name: <lit "ADULT_TIME_Glielmi.jpg"> path: <lit "/tmp/652adad1b3d2b666dcc8d857.jpg"> size: <lit 255614> type: <lit "image/jpeg">}>}>]>"""
var pr = parsePreserves(txt)
var capture: Literal[Request]
check capture.fromPreserves(pr)
suite "captures":
for txt in [
"#f",
"#t",
"0",
"-1",
"foo",
"<foo>",
"[0, 1, 2]",
]:
test txt:
let
pr = parsePreserves txt
pat = grab pr
checkpoint $pat
check pat.matches pr
suite "protocol":
test "Observe":
let pat = ?:Observe
const text = """<rec Observe [<bind <_>> <bind <_>>]>"""
check $pat == text
test "later-than":
const B = "[1 [2 3] 4]"
test B:
let
obsA = parsePreserves"""<Observe <rec later-than [<lit 1704113731.419243>]> #f>"""
obsB = parsePreserves"""<Observe <rec Observe [<rec rec [<lit later-than> <arr [<rec lit [<bind <_>>]>]>]> <_>]> #f>"""
patA = """<rec later-than [<lit 1704113731.419243>]>""".parsePreserves.preservesTo(Pattern).get
patB = """<rec Observe [<rec rec [<lit later-than> <arr [<rec lit [<bind <_>>]>]>]> <_>]>""".parsePreserves.preservesTo(Pattern).get
v = parsePreserves B
c = parsePreserves "[[2 3] 2]"
check pat.matches(v)
check pat.capture(v).toPreserves == c
patC = grab obsA
const C = "[1 [2] 5]"
test C:
let v = parsePreserves C
check:
not pat.matches(v)
test $patC:
check patC.matches obsA
test $patB:
checkpoint $obsA
check patB.matches obsA
test "TransportConnection":
const D = "[1 [2 3 4] 5]"
test D:
let
pat = TransportConnection ?: { 2: ?:Rejected}
text = """<rec connect-transport [<_> <_> <rec rejected [<bind <_>>]>]>"""
check $pat == text
v = parsePreserves D
c = parsePreserves "[[2 3 4] 2]"
check pat.matches(v)
check pat.capture(v).toPreserves == c
const E = "[1 [<x> <y>] []]"
test E:
let
v = parsePreserves E
c = parsePreserves "[[<x> <y>] <x>]"
check pat.matches(v)
check pat.capture(v).toPreserves == c
suite "meta":
test "pattern-of-pattern":
let
pat = grabRecord("foo".toSymbol, {666: drop()})
meta = pat.toPreserves.drop()
check $meta == "<group <rec group> {0: <group <rec rec> {0: <lit foo>}> 1: <group <dict> {666: <_>}>}>"
test "observe":
let
val = Observe(pattern: LaterThan ?: {0: drop 12.24}).toPreserves
pat = grab(val)
check pat.matches(val)
check pat.capture(val) == @[val]
let
meta = observePattern(!LaterThan, {@[0.toPreserves]: grabLit()})
res = parsePreserves "[12.24]"
check meta.matches(val)
check meta.capture(val).toPreserves == res
test "connect-transport":
let pat = parsePreserves"""
<group <rec connect-transport> {0: <group <rec unix> {0: <lit "/run/user/1000/dataspace">}> 2: <group <rec accepted> {0: <bind <_>>}>}>
""".preservesTo(Pattern).get
let val = parsePreserves"""
<connect-transport <unix "/run/user/1000/dataspace"> #:#f <accepted #:#f>>
"""
check pat.matches(val)
check pat.capture(val).toPreserves == parsePreserves "[#:#f]"

View File

@ -2,7 +2,9 @@
# SPDX-License-Identifier: Unlicense
import std/times
import syndicate, syndicate/drivers/timers
import syndicate, syndicate/drivers/timers, preserves
var passCount = 0
runActor("timer-test") do (turn: var Turn):
let timers = newDataspace(turn)
@ -10,13 +12,21 @@ runActor("timer-test") do (turn: var Turn):
onPublish(turn, timers, ?LaterThan(seconds: 1356100000)):
echo "now in 13th bʼakʼtun"
inc passCount
after(turn, timers, initDuration(seconds = 3)) do (turn: var Turn):
echo "third timer expired"
stopActor(turn)
assert passCount == 3
inc passCount
after(turn, timers, initDuration(seconds = 1)) do (turn: var Turn):
echo "first timer expired"
assert passCount == 1
inc passCount
after(turn, timers, initDuration(seconds = 2)) do (turn: var Turn):
echo "second timer expired"
assert passCount == 2
inc passCount
doAssert passCount == 4, $passCount