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) patterns.dropType(typ)
proc `?`*[T](val: T): Pattern {.inline.} = proc `?`*[T](val: T): Pattern {.inline.} =
patterns.grab[T](val) patterns.drop[T](val)
proc `?:`*(typ: static typedesc): Pattern {.inline.} = proc `?:`*(typ: static typedesc): Pattern {.inline.} =
patterns.grabType(typ) 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.} = proc `?:`*(typ: static typedesc; bindings: sink openArray[(Value, Pattern)]): Pattern {.inline.} =
patterns.grab(typ, bindings) patterns.grab(typ, bindings)
proc `??`*(pat: Pattern; bindings: openArray[(int, Pattern)]): Pattern {.inline.} =
patterns.inject(pat, bindings)
type type
PublishProc = proc (turn: var Turn; v: Value; h: Handle) {.closure.} PublishProc = proc (turn: var Turn; v: Value; h: Handle) {.closure.}
RetractProc = proc (turn: var Turn; h: Handle) {.closure.} RetractProc = proc (turn: var Turn; h: Handle) {.closure.}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,103 +1,87 @@
# SPDX-FileCopyrightText: ☭ Emery Hemingway # SPDX-FileCopyrightText: ☭ Emery Hemingway
# SPDX-License-Identifier: Unlicense # 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 import ./test_schema
test "patterns": suite "example":
let var pat: Pattern
pat = ?Observe(pattern: !Foo) ?? {0: grab()} check pat.fromPreserves parsePreserves"""
text = """<rec Observe [<rec rec [<lit foo> <arr [<bind <_>> <_> <_>]>]> <_>]>""" <group <arr> {
check($pat == text) 0: <lit 1>
1: <bind <group <arr> {
0: <bind <_>>
1: <_>
}>>
2: <_>
}>
"""
let const A = "[1 2 3]"
worte = @["alles", "in", "ordnung"] test A:
observer = Observe(pattern: inject(?:Foo, { 0: ?worte })).toPreserves let v = parsePreserves A
have = capture(pat, observer).toPreserves.unpackLiterals check:
want = [worte.toPreserves].toPreserves not pat.matches(v)
check(have == want)
type Obj {.preservesDictionary.} = object const B = "[1 [2 3] 4]"
a, b, c: int test B:
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":
let let
obsA = parsePreserves"""<Observe <rec later-than [<lit 1704113731.419243>]> #f>""" v = parsePreserves B
obsB = parsePreserves"""<Observe <rec Observe [<rec rec [<lit later-than> <arr [<rec lit [<bind <_>>]>]>]> <_>]> #f>""" c = parsePreserves "[[2 3] 2]"
patA = """<rec later-than [<lit 1704113731.419243>]>""".parsePreserves.preservesTo(Pattern).get check pat.matches(v)
patB = """<rec Observe [<rec rec [<lit later-than> <arr [<rec lit [<bind <_>>]>]>]> <_>]>""".parsePreserves.preservesTo(Pattern).get 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: const D = "[1 [2 3 4] 5]"
check patC.matches obsA test D:
test $patB:
checkpoint $obsA
check patB.matches obsA
test "TransportConnection":
let let
pat = TransportConnection ?: { 2: ?:Rejected} v = parsePreserves D
text = """<rec connect-transport [<_> <_> <rec rejected [<bind <_>>]>]>""" c = parsePreserves "[[2 3 4] 2]"
check $pat == text 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 # SPDX-License-Identifier: Unlicense
import std/times import std/times
import syndicate, syndicate/drivers/timers import syndicate, syndicate/drivers/timers, preserves
var passCount = 0
runActor("timer-test") do (turn: var Turn): runActor("timer-test") do (turn: var Turn):
let timers = newDataspace(turn) let timers = newDataspace(turn)
@ -10,13 +12,21 @@ runActor("timer-test") do (turn: var Turn):
onPublish(turn, timers, ?LaterThan(seconds: 1356100000)): onPublish(turn, timers, ?LaterThan(seconds: 1356100000)):
echo "now in 13th bʼakʼtun" echo "now in 13th bʼakʼtun"
inc passCount
after(turn, timers, initDuration(seconds = 3)) do (turn: var Turn): after(turn, timers, initDuration(seconds = 3)) do (turn: var Turn):
echo "third timer expired" echo "third timer expired"
stopActor(turn) assert passCount == 3
inc passCount
after(turn, timers, initDuration(seconds = 1)) do (turn: var Turn): after(turn, timers, initDuration(seconds = 1)) do (turn: var Turn):
echo "first timer expired" echo "first timer expired"
assert passCount == 1
inc passCount
after(turn, timers, initDuration(seconds = 2)) do (turn: var Turn): after(turn, timers, initDuration(seconds = 2)) do (turn: var Turn):
echo "second timer expired" echo "second timer expired"
assert passCount == 2
inc passCount
doAssert passCount == 4, $passCount