Compare commits

...

75 Commits

Author SHA1 Message Date
Emery Hemingway 760ce84a8c WiP Noise tunnel 2024-01-20 19:46:06 +02:00
Emery Hemingway 2e027afc42 relays: split connecting and resolving 2024-01-20 19:46:06 +02:00
Emery Hemingway 686719273c patterns: support embedded literals 2024-01-20 19:46:06 +02:00
Emery Hemingway fe2255bcc9 Make publish and spawn discardable 2024-01-19 00:04:06 +02:00
Emery Hemingway 2fa2276dfe patterns: do not match inner types at dropType 2024-01-18 19:22:56 +02:00
Emery Hemingway a4ba81a481 Cleanup relays 2024-01-16 19:59:34 +02:00
Emery Hemingway 75d1e33bff actors/timers: use sleepAsync
The asyncdispatch.addTimer proc seems to leak file descriptors.
2024-01-14 12:35:39 +02:00
Emery Hemingway 6b642645f9 Make sync work 2024-01-14 12:35:39 +02:00
Emery Hemingway 0e5637a6c3 Patterns: selectively grab dictionary fields 2024-01-14 12:13:30 +02:00
Emery Hemingway 3e11884a91 Update Preserves lock 2024-01-08 12:59:11 +02:00
Emery Hemingway 7721138bf4 Use new Preserves embeddeding 2024-01-08 00:11:59 +02:00
Emery Hemingway 59ece65f3b patterns: drop unspecified type elements at T ?: {N:pat}
Emit a <_> pattern for unspecified elements rather than patterns
that would match metadata like record label and arity.
2024-01-06 16:48:12 +02:00
Emery Hemingway 6d2a401a2b Use mapEmbeds 2024-01-06 14:44:14 +02:00
Emery Hemingway 19121e514c stop building $(BIN_DIR)/mint 2024-01-06 14:30:23 +02:00
Emery Hemingway df52f72263 Update schemas to get optionals 2024-01-06 13:59:38 +02:00
Emery Hemingway e48c62f448 Shuffle imports 2024-01-06 13:56:12 +02:00
Emery Hemingway 3cc3a48c82 fixup! WiP! Value transition 2024-01-01 20:46:48 +02:00
Emery Hemingway c1c5333778 Protocol changes 2024-01-01 20:29:54 +02:00
Emery Hemingway 1e107131d8 WiP! Value transition 2024-01-01 20:20:44 +02:00
Emery Hemingway a0355637d8 Fix patterns grab recursion 2023-12-31 20:06:12 +02:00
Emery Hemingway c0cff79313 Migrate to non-generic Preserves 2023-12-31 19:17:44 +02:00
Emery Hemingway b5aa2a7b8f Grab values with `?` and types with `?:`
In this case it must be explicit if a proc is taking a value or a
type.
2023-12-30 17:57:37 +02:00
Emery Hemingway 8f6da89d69 Preserves now has a register type 2023-12-30 13:19:56 +02:00
Emery Hemingway 0954180321 Fork hashlib to remove hardware-specific optimizations 2023-11-30 11:13:25 +02:00
Emery Hemingway 97d24aa971 Update build metadata 2023-11-30 10:44:41 +02:00
Emery Hemingway 8a7cc884fe Export newFacet 2023-11-08 15:06:59 +00:00
Emery Hemingway 23c69f63a5 Export depattern proc 2023-11-07 17:33:27 +00:00
Emery Hemingway 8bc0ee2ae5 relays: publish gatekeeper rather then pass by future 2023-11-02 15:47:53 +00:00
Emery Hemingway 090b4d77ef Move projectPath into Preserves library 2023-10-28 00:25:58 +01:00
Emery Hemingway 00609f3b6f Fix protocol 2023-10-28 00:25:13 +01:00
Emery Hemingway 577490701a Fix runActor exit 2023-10-26 13:13:03 +01:00
Emery Hemingway 843252ad61 Patterns: preserve the value of Literal 2023-10-26 13:12:31 +01:00
Emery Hemingway ac2576f005 Update lockfile 2023-10-21 18:41:29 +01:00
Emery Hemingway 311b614979 Move route finding into syndicate/relays 2023-10-21 17:38:59 +01:00
Emery Hemingway 5b373e3047 Let the skeletons have empty nodes sometimes 2023-10-20 13:33:26 +01:00
Emery Hemingway ec8e166099 Cleanup timer patterns 2023-10-20 13:32:58 +01:00
Emery Hemingway a987f875a9 Print the pattern of problematic captures 2023-10-20 13:30:24 +01:00
Emery Hemingway 57b99b20e7 Update Preserves dependency 2023-10-19 18:18:23 +01:00
Emery Hemingway 4a6e95bbce patterns: add Literal[T] type for literal capture 2023-10-15 12:11:10 +01:00
Emery Hemingway 3a04fc195b relays: add resolve proc 2023-10-14 00:47:16 +01:00
Emery Hemingway 6fcb76b1e9 Add lockfile 2023-10-13 23:37:31 +01:00
Emery Hemingway 552e51899c Move relays out of top module, clean exports there 2023-10-05 18:01:23 +01:00
Emery Hemingway b2994b6d05 Build metadata updates 2023-10-05 17:57:59 +01:00
Emery Hemingway d86ef24c01 Move mint utility to syndicate_utils 2023-08-25 18:31:49 +01:00
Emery Hemingway dcd6bfe99b patterns: fix a regression in field injection 2023-08-23 10:35:12 +01:00
Emery Hemingway 35670b2727 Update for Nim-2.0.0 2023-08-16 14:53:46 +01:00
Emery Hemingway 73d29da071 Remove broken patterns example 2023-08-16 09:38:23 +01:00
Emery Hemingway 66f435a279 Regenerate protocols 2023-08-16 09:36:43 +01:00
Emery Hemingway 703bd7baea Update tests for Nim-2.0.0 2023-08-16 09:36:11 +01:00
Emery Hemingway ce6d97c1d3 Replace ByteAddress with uint 2023-08-05 20:29:26 +01:00
Emery Hemingway f78308765e Associate relay futures with actor turns 2023-08-01 11:05:52 +01:00
Emery Hemingway ba2ea5d08b Expose bindings values in during, onPublish, and onMessage 2023-08-01 11:05:07 +01:00
Emery Hemingway 9c5e26e8f1 Patterns sugar 2023-07-26 11:01:56 +01:00
Emery Hemingway 8fc9608199 patterns: grabRecord, grabDictionary 2023-07-25 18:58:55 +01:00
Emery Hemingway ce8e800187 Rename Ref to Cap 2023-07-25 18:17:55 +01:00
Emery Hemingway 4e0a36ef31 skeletons: implement assertion removal 2023-07-25 18:17:55 +01:00
Emery Hemingway 16cc5aaf98 patterns: sort dictionary keys during analysis 2023-07-23 08:38:40 +01:00
Emery Hemingway 7b2d59e4cd Make runActor sleep on timers to keep asyncdispatch active 2023-07-23 08:29:45 +01:00
Emery Hemingway 7f903a14d7 Correct addCallback for returning Future values 2023-07-23 08:29:08 +01:00
Emery Hemingway 4b29fc009b Return an Actor from spawn 2023-07-22 11:32:52 +01:00
Emery Hemingway 248d34ce69 Refactor syndicate macros
This unifies some code generation for onPublish, onMessage, and
during. The message macro now accepts a body without arguments
in the same way as the other two.
2023-07-21 20:35:53 +01:00
Emery Hemingway 146b30ed42 Refactor skeletons 2023-07-21 13:24:43 +01:00
Emery Hemingway ca12c1ae03 Rewrite timers 2023-07-20 19:17:57 +01:00
Emery Hemingway 9614955320 Tracing 2023-07-20 19:17:57 +01:00
Emery Hemingway 7fec2d61ac Actors lineages share a handle allocator
For multiple actors to communicate through the same relay they
must use unique assertion handles.
2023-07-20 19:17:57 +01:00
Emery Hemingway 76d550602f Document relays a bit 2023-07-20 19:17:57 +01:00
Emery Hemingway 91a218f7fb Actors lineages share a handle allocator
For multiple actors to communicate through the same relay they
must use unique assertion handles.
2023-07-12 15:16:20 +01:00
Emery Hemingway b1b0477b8a Add addCallback variant that passes on the value of a Future 2023-07-12 15:15:39 +01:00
Emery Hemingway 170f49693c Reuse caller turn symbol in DSL macros 2023-06-30 10:05:42 +01:00
Emery Hemingway 219286a84a Recommend runDataspace, not bootDataspace 2023-06-30 09:51:49 +01:00
Emery Hemingway 8bb9fb16d7 patterns: inject variant for dictionaries 2023-06-11 21:22:10 +01:00
Emery Hemingway fc94fa39d8 terminate: fix off-by-inifinity bug 2023-06-11 21:21:07 +01:00
Emery Hemingway 2f4552e7fe patterns: raise uncatchable error for set patterns 2023-06-10 21:27:54 +01:00
Emery Hemingway 0089e1f413 connectStdio: do not constrain receive buffer size 2023-06-10 21:26:20 +01:00
Emery Hemingway 7a36a6e8a4 patterns: unpackLiterals more 2023-06-10 01:00:12 +01:00
43 changed files with 2215 additions and 1303 deletions

4
.gitignore vendored
View File

@ -1 +1,3 @@
/.direnv
/nim.cfg
*.check
*.run

View File

@ -49,12 +49,12 @@ assert present.fromPreserve(pr) == true
## The Syndicate DSL
The Syndicate DSL can be entered using `bootDataspace` which calls a Nim body with a [dataspace](https://synit.org/book/glossary.html#dataspace) [Ref](https://synit.org/book/glossary.html#reference) and a [turn](https://synit.org/book/glossary.html#turn). The `Ref` is something that we can observe and publish assertions at, and a `Turn` is special type for temporal scoping and transactional semantics. Assertions can be published using the `Assertion` or equivalent `Preserve[Ref]` type, but using Nim types is preferred because they can be reliably consistent with schema.
The Syndicate DSL can be entered using `runActor` which calls a Nim body with a [dataspace](https://synit.org/book/glossary.html#dataspace) [Ref](https://synit.org/book/glossary.html#reference) and a [turn](https://synit.org/book/glossary.html#turn). The `Ref` is something that we can observe and publish assertions at, and a `Turn` is special type for temporal scoping and transactional semantics. Assertions can be published using the `Assertion` or equivalent `Preserve[Ref]` type, but using Nim types is preferred because they can be reliably consistent with schema.
### Publish
``` nim
bootDataspace("main") do (dataspace: Ref; turn: var Turn):
runActor("main") do (dataspace: Ref; turn: var Turn):
let presenceHandle = publish(turn, dataspace, Present(username: "Judy"))
# publish <Present "Judy"> to the dataspace
# the assertion can be later retracted by handle
@ -67,7 +67,7 @@ bootDataspace("main") do (dataspace: Ref; turn: var Turn):
We can react to assertions and messages within dataspaces using [patterns](https://synit.org/book/glossary.html#dataspace-pattern). Patterns are constructed using a Nim type and the `?` operator. Again a Nim type is used rather than a raw Preserves for schema consistency.
``` nim
bootDataspace("main") do (dataspace: Ref; turn: var Turn):
runActor("main") do (dataspace: Ref; turn: var Turn):
during(turn, dataspace, ?Present) do (who: string):
# This body is active when the ?Present pattern is matched.
# The Present type contains two atomic values that can be matched
@ -92,10 +92,7 @@ bootDataspace("main") do (dataspace: Ref; turn: var Turn):
### [test_chat](./tests/test_chat.nim)
Simple chat demo that is compatible with [chat.py](https://git.syndicate-lang.org/syndicate-lang/syndicate-py/src/branch/main/chat.py).
```sh
nim c -r tests/test_chat.nim \
--cap:'<ref {oid: "syndicate" sig: #x"69ca300c1dbfa08fba692102dd82311a"}>' \
--transport:'<tcp "127.0.0.1" 666>' \
--user:fnord
SYNDICATE_ROUTE='<route [<unix "/run/user/1000/dataspace">] [<ref {oid: "syndicate" sig: #x"69ca300c1dbfa08fba692102dd82311a"}>]>' nim c -r tests/test_chat.nim --user:fnord
```
### [xdg_open_ng](https://git.syndicate-lang.org/ehmry/xdg_open_ng)

2
Tupfile Normal file
View File

@ -0,0 +1,2 @@
include_rules
: lock.json |> !nim_cfg |> | ./<lock>

View File

@ -1 +1,3 @@
include depends.tup
NIM = $(DIRENV) $(NIM)
NIM_GROUPS += $(TUP_CWD)/<lock>

View File

@ -1,7 +1,4 @@
include ../preserves-nim/depends.tup
include ../taps/depends.tup
NIM_FLAGS += --path:$(TUP_CWD)/../nim
NIM_FLAGS += --path:$(TUP_CWD)/../preserves-nim/src
NIM_FLAGS += --path:$(TUP_CWD)/../taps/src
NIM_FLAGS += --path:$(TUP_CWD)/../hashlib
NIM_FLAGS += --path:$(TUP_CWD)/../noiseprotocol/src
NIM_GROUPS += $(TUP_CWD)/<protocol>

63
lock.json Normal file
View File

@ -0,0 +1,63 @@
{
"depends": [
{
"method": "fetchzip",
"packages": [
"bigints"
],
"path": "/nix/store/jvrm392g8adfsgf36prgwkbyd7vh5jsw-source",
"ref": "20231006",
"rev": "86ea14d31eea9275e1408ca34e6bfe9c99989a96",
"sha256": "15pcpmnk1bnw3k8769rjzcpg00nahyrypwbxs88jnwr4aczp99j4",
"srcDir": "src",
"url": "https://github.com/ehmry/nim-bigints/archive/86ea14d31eea9275e1408ca34e6bfe9c99989a96.tar.gz"
},
{
"method": "fetchzip",
"packages": [
"hashlib"
],
"path": "/nix/store/fav82xdbicvlk34nmcbl89zx99lr3mbs-source",
"rev": "f9455d4be988e14e3dc7933eb7cc7d7c4820b7ac",
"sha256": "1sx6j952lj98629qfgr7ds5aipyw9d6lldcnnqs205wpj4pkcjb3",
"srcDir": "",
"url": "https://github.com/ehmry/hashlib/archive/f9455d4be988e14e3dc7933eb7cc7d7c4820b7ac.tar.gz"
},
{
"method": "fetchzip",
"packages": [
"nimcrypto"
],
"path": "/nix/store/zyr8zwh7vaiycn1s4r8cxwc71f2k5l0h-source",
"ref": "traditional-api",
"rev": "602c5d20c69c76137201b5d41f788f72afb95aa8",
"sha256": "1dmdmgb6b9m5f8dyxk781nnd61dsk3hdxqks7idk9ncnpj9fng65",
"srcDir": "",
"url": "https://github.com/cheatfate/nimcrypto/archive/602c5d20c69c76137201b5d41f788f72afb95aa8.tar.gz"
},
{
"method": "fetchzip",
"packages": [
"npeg"
],
"path": "/nix/store/ffkxmjmigfs7zhhiiqm0iw2c34smyciy-source",
"ref": "1.2.1",
"rev": "26d62fdc40feb84c6533956dc11d5ee9ea9b6c09",
"sha256": "0xpzifjkfp49w76qmaylan8q181bs45anmp46l4bwr3lkrr7bpwh",
"srcDir": "src",
"url": "https://github.com/zevv/npeg/archive/26d62fdc40feb84c6533956dc11d5ee9ea9b6c09.tar.gz"
},
{
"method": "fetchzip",
"packages": [
"preserves"
],
"path": "/nix/store/fpkhfxnfbdcri6k7mac21r3byg738bs4-source",
"ref": "20240108",
"rev": "a01ba8c96d65f670862ba074bf82b50cbda6ed99",
"sha256": "0n8pghy2qfywx0psr54yzjvhdhi5av204150jyyzfxhigczd8sr4",
"srcDir": "src",
"url": "https://git.syndicate-lang.org/ehmry/preserves-nim/archive/a01ba8c96d65f670862ba074bf82b50cbda6ed99.tar.gz"
}
]
}

View File

@ -1,5 +1,6 @@
let
syndicate = builtins.getFlake "syndicate";
pkgs =
import <nixpkgs> { overlays = builtins.attrValues syndicate.overlays; };
in pkgs.nimPackages.syndicate
let pkgs = import <nixpkgs> { };
in pkgs.buildNimPackage {
name = "noiseprotocol";
buildInputs = [ pkgs.noise-c ];
lockFile = ./lock.json;
}

View File

@ -6,42 +6,37 @@
import std/[asyncdispatch, macros, tables, typetraits]
import preserves
export fromPreserve, toPreserve
export fromPreserves, toPreserves
import ./syndicate/[actors, dataspaces, durings, patterns]
import ./syndicate/protocols/dataspace
when defined(posix):
from ./syndicate/relays import Tcp, Unix, connect, connectStdio
export Tcp, Unix, connect, connectStdio
export actors, dataspace, dataspaces, patterns
export patterns
export Actor, Assertion, Facet, Handle, Ref, Symbol, Turn, TurnAction,
`$`, addCallback, analyse, asyncCheck, bootDataspace,
facet, future, inFacet, message, newDataspace, onStop, publish,
retract, replace, run, stop, unembed
type Assertion* {.deprecated: "Assertion and Preserve[void] replaced by Value".} = Value
proc `!`*(typ: static typedesc): Pattern {.inline.} =
patterns.dropType(typ)
proc `?`*(typ: static typedesc): Pattern {.inline.} =
proc `?`*[T](val: T): Pattern {.inline.} =
patterns.grab[T](val)
proc `?:`*(typ: static typedesc): Pattern {.inline.} =
patterns.grabType(typ)
proc `?`*(typ: static typedesc; bindings: sink openArray[(int, Pattern)]): Pattern {.inline.} =
proc `?:`*(typ: static typedesc; bindings: sink openArray[(int, Pattern)]): Pattern {.inline.} =
patterns.grab(typ, bindings)
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)
proc `?`*[T](val: T): Pattern {.inline.} =
patterns.grab[T](val)
type
Observe* = dataspace.Observe[Ref]
PublishProc = proc (turn: var Turn; v: Assertion; h: Handle) {.closure, gcsafe.}
PublishProc = proc (turn: var Turn; v: Value; h: Handle) {.closure, gcsafe.}
RetractProc = proc (turn: var Turn; h: Handle) {.closure, gcsafe.}
MessageProc = proc (turn: var Turn; v: Assertion) {.closure, gcsafe.}
MessageProc = proc (turn: var Turn; v: Value) {.closure, gcsafe.}
ClosureEntity = ref object of Entity
publishImpl: PublishProc
retractImpl: RetractProc
@ -60,17 +55,20 @@ proc argumentCount(handler: NimNode): int =
handler.expectKind {nnkDo, nnkStmtList}
if handler.kind == nnkDo: result = pred handler[3].len
proc wrapPublishHandler(handler: NimNode): NimNode =
handler.expectKind {nnkDo, nnkStmtList}
var innerProc = newNimNode(nnkProcDef)
handler.copyChildrenTo innerProc
innerProc[0] = genSym(nskProc, "message")
var
valuesSym = genSym(nskVar, "values")
valuesTuple = newNimNode(nnkTupleTy, handler)
innerTuple = newNimNode(nnkVarTuple, handler)
varSectionInner = newNimNode(nnkVarSection, handler).add(innerTuple)
if handler.kind == nnkDo:
type HandlerNodes = tuple
valuesSym, varSection, body: NimNode
proc generateHandlerNodes(handler: NimNode): HandlerNodes =
handler.expectKind {nnkStmtList, nnkDo}
result.valuesSym = genSym(nskVar, "values")
let valuesTuple = newNimNode(nnkTupleTy, handler)
case handler.kind
of nnkStmtList:
result.body = handler
of nnkDo:
let
innerTuple = newNimNode(nnkVarTuple, handler)
varSectionInner = newNimNode(nnkVarSection, handler).add(innerTuple)
for i, arg in handler[3]:
if i > 0:
arg.expectKind nnkIdentDefs
@ -80,169 +78,125 @@ proc wrapPublishHandler(handler: NimNode): NimNode =
arg.copyChildrenTo def
valuesTuple.add(def)
innerTuple.add(arg[0])
innerTuple.add(newEmptyNode(), valuesSym)
innerTuple.add(newEmptyNode(), result.valuesSym)
result.body = newStmtList(varSectionInner, handler[6])
else:
discard # caught earlier by expectKind
result.varSection = newNimNode(nnkVarSection, handler).
add(newIdentDefs(result.valuesSym, valuesTuple))
proc wrapPublishHandler(turn, handler: NimNode): NimNode =
var
varSectionOuter = newNimNode(nnkVarSection, handler).add(
newIdentDefs(valuesSym, valuesTuple))
publishBody =
if handler.kind == nnkStmtList: handler
else: newStmtList(varSectionInner, handler[6])
turnSym = ident"turn"
(valuesSym, varSection, publishBody) =
generateHandlerNodes(handler)
handleSym = ident"handle"
handlerSym = genSym(nskProc, "publish")
bindingsSym = ident"bindings"
quote do:
proc `handlerSym`(`turnSym`: var Turn; bindings: Assertion; `handleSym`: Handle) =
`varSectionOuter`
if fromPreserve(`valuesSym`, bindings):
proc `handlerSym`(`turn`: var Turn; `bindingsSym`: Value; `handleSym`: Handle) =
`varSection`
if fromPreserves(`valuesSym`, bindings):
`publishBody`
proc wrapMessageHandler(handler: NimNode): NimNode =
handler.expectKind {nnkDo, nnkStmtList}
var innerProc = newNimNode(nnkProcDef)
handler.copyChildrenTo innerProc
innerProc[0] = genSym(nskProc, "message")
proc wrapMessageHandler(turn, handler: NimNode): NimNode =
var
valuesSym = genSym(nskVar, "values")
valuesTuple = newNimNode(nnkTupleTy, handler)
innerTuple = newNimNode(nnkVarTuple, handler)
varSectionInner = newNimNode(nnkVarSection, handler).add(innerTuple)
if handler.kind == nnkDo:
for i, arg in handler[3]:
if i > 0:
arg.expectKind nnkIdentDefs
if arg[1].kind == nnkEmpty:
error("type required for capture", arg)
var def = newNimNode(nnkIdentDefs, arg)
arg.copyChildrenTo def
valuesTuple.add(def)
innerTuple.add(arg[0])
innerTuple.add(newEmptyNode(), valuesSym)
var
varSectionOuter = newNimNode(nnkVarSection, handler).add(
newIdentDefs(valuesSym, valuesTuple))
body = newStmtList(varSectionInner, handler[6])
turnSym = ident"turn"
(valuesSym, varSection, body) =
generateHandlerNodes(handler)
handlerSym = genSym(nskProc, "message")
bindingsSym = ident"bindings"
quote do:
proc `handlerSym`(`turnSym`: var Turn; bindings: Assertion) =
`varSectionOuter`
if fromPreserve(`valuesSym`, bindings):
proc `handlerSym`(`turn`: var Turn; `bindingsSym`: Value) =
`varSection`
if fromPreserves(`valuesSym`, bindings):
`body`
macro onPublish*(turn: Turn; ds: Ref; pattern: Pattern; handler: untyped) =
## Call `handler` when an assertion matching `pattern` is published at `ds`.
let
argCount = argumentCount(handler)
handlerProc = wrapPublishHandler(handler)
handlerSym = handlerProc[0]
result = quote do:
if `pattern`.analyse.capturePaths.len != `argCount`:
raiseAssert($`pattern`.analyse.capturePaths.len & " values captured but handler has " & $`argCount` & " arguments")
`handlerProc`
discard observe(`turn`, `ds`, `pattern`, ClosureEntity(publishImpl: `handlerSym`))
macro onMessage*(turn: Turn; ds: Ref; pattern: Pattern; handler: untyped) =
## Call `handler` when an message matching `pattern` is broadcasted at `ds`.
let
argCount = argumentCount(handler)
handlerProc = wrapMessageHandler(handler)
handlerSym = handlerProc[0]
result = quote do:
if `pattern`.analyse.capturePaths.len != `argCount`:
raiseAssert($`pattern`.analyse.capturePaths.len & " values captured but handler has " & $`argCount` & " arguments")
`handlerProc`
discard observe(`turn`, `ds`, `pattern`, ClosureEntity(messageImpl: `handlerSym`))
proc wrapDuringHandler(entryBody, exitBody: NimNode): NimNode =
entryBody.expectKind {nnkDo, nnkStmtList}
var innerProc = newNimNode(nnkProcDef)
entryBody.copyChildrenTo innerProc
innerProc[0] = genSym(nskProc, "during")
proc wrapDuringHandler(turn, entryBody, exitBody: NimNode): NimNode =
var
valuesSym = ident("rawValues")
valuesTuple = newNimNode(nnkTupleTy, entryBody)
innerTuple = newNimNode(nnkVarTuple, entryBody)
varSectionInner = newNimNode(nnkVarSection, entryBody).add(innerTuple)
if entryBody.kind == nnkDo:
for i, arg in entryBody[3]:
if i > 0:
arg.expectKind nnkIdentDefs
if arg[1].kind == nnkEmpty:
error("type required for capture", arg)
var def = newNimNode(nnkIdentDefs, arg)
arg.copyChildrenTo def
valuesTuple.add(def)
innerTuple.add(arg[0])
innerTuple.add(newEmptyNode(), valuesSym)
var
varSectionOuter = newNimNode(nnkVarSection, entryBody).add(
newIdentDefs(valuesSym, valuesTuple))
publishBody =
if entryBody.kind == nnkStmtList: entryBody
else: newStmtList(varSectionInner, entryBody[6])
turnSym = ident"turn"
(valuesSym, varSection, publishBody) =
generateHandlerNodes(entryBody)
bindingsSym = ident"bindings"
handleSym = ident"duringHandle"
duringSym = genSym(nskProc, "during")
if exitBody.isNil:
quote do:
proc `duringSym`(`turnSym`: var Turn; `bindingsSym`: Assertion; `handleSym`: Handle): TurnAction =
`varSectionOuter`
if fromPreserve(`valuesSym`, `bindingsSym`):
proc `duringSym`(`turn`: var Turn; `bindingsSym`: Value; `handleSym`: Handle): TurnAction =
`varSection`
if fromPreserves(`valuesSym`, `bindingsSym`):
`publishBody`
else:
quote do:
proc `duringSym`(`turnSym`: var Turn; `bindingsSym`: Assertion; `handleSym`: Handle): TurnAction =
`varSectionOuter`
if fromPreserve(`valuesSym`, `bindingsSym`):
proc `duringSym`(`turn`: var Turn; `bindingsSym`: Value; `handleSym`: Handle): TurnAction =
`varSection`
if fromPreserves(`valuesSym`, `bindingsSym`):
`publishBody`
proc action(`turnSym`: var Turn) =
proc action(`turn`: var Turn) =
`exitBody`
result = action
macro during*(turn: var Turn; ds: Ref; pattern: Pattern; publishBody, retractBody: untyped) =
macro onPublish*(turn: untyped; ds: Cap; pattern: Pattern; handler: untyped) =
## Call `handler` when an assertion matching `pattern` is published at `ds`.
let
argCount = argumentCount(handler)
handlerProc = wrapPublishHandler(turn, handler)
handlerSym = handlerProc[0]
result = quote do:
if `argCount` != 0 and `pattern`.analyse.capturePaths.len != `argCount`:
raiseAssert($`pattern`.analyse.capturePaths.len & " values captured but handler has " & $`argCount` & " arguments - " & $`pattern`)
`handlerProc`
discard observe(`turn`, `ds`, `pattern`, ClosureEntity(publishImpl: `handlerSym`))
macro onMessage*(turn: untyped; ds: Cap; pattern: Pattern; handler: untyped) =
## Call `handler` when an message matching `pattern` is broadcasted at `ds`.
let
argCount = argumentCount(handler)
handlerProc = wrapMessageHandler(turn, handler)
handlerSym = handlerProc[0]
result = quote do:
if `argCount` != 0 and `pattern`.analyse.capturePaths.len != `argCount`:
raiseAssert($`pattern`.analyse.capturePaths.len & " values captured but handler has " & $`argCount` & " arguments - " & $`pattern`)
`handlerProc`
discard observe(`turn`, `ds`, `pattern`, ClosureEntity(messageImpl: `handlerSym`))
macro during*(turn: untyped; ds: Cap; pattern: Pattern; publishBody, retractBody: untyped) =
## Call `publishBody` when an assertion matching `pattern` is published to `ds` and
## call `retractBody` on retraction. Assertions that match `pattern` but are not
## convertable to the arguments of `publishBody` are silently discarded.
##
## The following symbols are injected into the scope of both bodies:
## - `turn` - active turn at entry of `publishBody` and `retractBody`
## - `bindings` - raw Preserves sequence that matched `pattern`
## - `duringHandle` - dataspace handle of the assertion that triggered `publishBody`
let
argCount = argumentCount(publishBody)
callbackProc = wrapDuringHandler(publishBody, retractBody)
callbackProc = wrapDuringHandler(turn, publishBody, retractBody)
callbackSym = callbackProc[0]
result = quote do:
if `pattern`.analyse.capturePaths.len != `argCount`:
raiseAssert($`pattern`.analyse.capturePaths.len & " values captured but handler has " & $`argCount` & " arguments")
if `argCount` != 0 and `pattern`.analyse.capturePaths.len != `argCount`:
raiseAssert($`pattern`.analyse.capturePaths.len & " values captured but handler has " & $`argCount` & " arguments - " & $`pattern`)
`callbackProc`
discard observe(`turn`, `ds`, `pattern`, during(`callbackSym`))
macro during*(turn: var Turn; ds: Ref; pattern: Pattern; publishBody: untyped) =
macro during*(turn: untyped; ds: Cap; pattern: Pattern; publishBody: untyped) =
## Variant of `during` without a retract body.
let
argCount = argumentCount(publishBody)
callbackProc = wrapDuringHandler(publishBody, nil)
`argCount` = argumentCount(publishBody)
callbackProc = wrapDuringHandler(turn, publishBody, nil)
callbackSym = callbackProc[0]
result = quote do:
if `pattern`.analyse.capturePaths.len != `argCount`:
raiseAssert($`pattern`.analyse.capturePaths.len & " values captured but handler has " & $`argCount` & " arguments")
if `argCount` != 0 and `pattern`.analyse.capturePaths.len != `argCount`:
raiseAssert($`pattern`.analyse.capturePaths.len & " values captured but handler has " & $`argCount` & " arguments - " & $`pattern`)
`callbackProc`
discard observe(`turn`, `ds`, `pattern`, during(`callbackSym`))
type BootProc = proc (ds: Ref; turn: var Turn) {.gcsafe.}
from std/os import getEnv
type BootProc = proc (turn: var Turn; ds: Cap) {.gcsafe.}
type DeprecatedBootProc = proc (ds: Cap; turn: var Turn) {.gcsafe.}
proc runActor*(name: string; bootProc: BootProc) =
## Run an `Actor` to completion.
let actor = bootDataspace(name, bootProc)
if getEnv"SYNDICATE_DEBUG" == "":
while not actor.future.finished:
poll()
else:
while not actor.future.finished:
stderr.writeLine("Polling ", name, " actor…")
poll()
read(actor.future)
while actor.running:
waitFor sleepAsync(500)
proc runActor*(name: string; bootProc: DeprecatedBootProc) {.deprecated.} =
## Run an `Actor` to completion.
runActor(name) do (turn: var Turn, ds: Cap):
bootProc(ds, turn)

View File

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

View File

@ -1,10 +1,17 @@
# SPDX-FileCopyrightText: ☭ Emery Hemingway
# SPDX-License-Identifier: Unlicense
import std/[asyncfutures, deques, hashes, monotimes, options, sets, tables, times]
import std/[asyncfutures, hashes, monotimes, options, sets, tables, times]
import preserves
import ../syndicate/protocols/[protocol, sturdy]
const tracing = defined(traceSyndicate)
when tracing:
import std/streams
from std/os import getEnv
import ./protocols/trace
export Handle
template generateIdType(typ: untyped) =
@ -20,66 +27,91 @@ generateIdType(TurnId)
type
Oid = sturdy.Oid
Assertion* = Preserve[Ref]
Caveat = sturdy.Caveat[Ref]
Caveat = sturdy.Caveat
Attenuation = seq[Caveat]
Rewrite = sturdy.Rewrite[Ref]
Rewrite = sturdy.Rewrite
AssertionRef* = ref object
value*: Value
# if the Enity methods take a Value object then the generated
# C code has "redefinition of struct" problems when orc is enabled
Entity* = ref object of RootObj
oid*: Oid # oid is how Entities are identified over the wire
Ref* {.unpreservable.} = ref object # TODO: rename
Cap* {.preservesEmbedded.} = ref object of EmbeddedObj
relay*: Facet
target*: Entity
attenuation*: Attenuation
Ref* {.deprecated: "Ref was renamed to Cap".} = Cap
OutboundAssertion = ref object
handle: Handle
peer: Ref
peer: Cap
established: bool
OutboundTable = Table[Handle, OutboundAssertion]
Actor* = ref object
future: Future[void]
name: string
id: ActorId
handleAllocator: Handle
handleAllocator: ref Handle
# a fresh actor gets a new ref Handle and
# all actors spawned from it get the same ref.
root: Facet
exitReason: ref Exception
exitHooks: seq[TurnAction]
exiting: bool
id: ActorId
exiting, exited: bool
when tracing:
turnIdAllocator: ref TurnId
traceStream: FileStream
TurnAction* = proc (t: var Turn) {.gcsafe.}
Queues = TableRef[Facet, seq[TurnAction]]
Turn* = object # an object that should remain on the stack
id: TurnId
facet: Facet
queues: Queues # a ref object that can outlive Turn
ParentFacet = Option[Facet]
when tracing:
desc: TurnDescription
Facet* = ref FacetObj
FacetObj = object
id: FacetId
actor*: Actor
parent: ParentFacet
parent: Facet
children: HashSet[Facet]
outbound: OutboundTable
shutdownActions: seq[TurnAction]
inertCheckPreventers: int
id: FacetId
isAlive: bool
type AssertionRef* = ref object
value*: Preserve[Ref]
# if the Enity methods take a Preserve[Ref] object then the generated
# C code has "redefinition of struct" problems when orc is enabled
when tracing:
proc nextTurnId(facet: Facet): TurnId =
result = succ(facet.actor.turnIdAllocator[])
facet.actor.turnIdAllocator[] = result
proc trace(actor: Actor; act: ActorActivation) =
if not actor.traceStream.isNil:
var entry = TraceEntry(
timestamp: getTime().toUnixFloat(),
actor: initRecord("named", actor.name.toPreserves),
item: act)
actor.traceStream.writeText entry.toPreserves
actor.traceStream.writeLine()
proc path(facet: Facet): seq[trace.FacetId] =
var f = facet
while not f.isNil:
result.add f.id.toPreserves
f = f.parent
method publish*(e: Entity; turn: var Turn; v: AssertionRef; h: Handle) {.base, gcsafe.} = discard
method retract*(e: Entity; turn: var Turn; h: Handle) {.base, gcsafe.} = discard
method message*(e: Entity; turn: var Turn; v: AssertionRef) {.base, gcsafe.} = discard
method sync*(e: Entity; turn: var Turn; peer: Ref) {.base, gcsafe.} = discard
method sync*(e: Entity; turn: var Turn; peer: Cap) {.base, gcsafe.} = discard
using
actor: Actor
@ -90,25 +122,26 @@ using
proc labels(f: Facet): string =
proc catLabels(f: Facet; labels: var string) =
labels.add ':'
if f.parent.isSome:
catLabels(f.parent.get, labels)
if not f.parent.isNil:
catLabels(f.parent, labels)
labels.add ':'
labels.add $f.id
when tracing:
labels.add $f.id
result.add f.actor.name
catLabels(f, result)
proc `$`*(f: Facet): string =
"<Facet:" & f.labels & ">"
proc `$`*(r: Ref): string =
proc `$`*(r: Cap): string =
"<Ref:" & r.relay.labels & ">"
proc `$`*(actor: Actor): string =
"<Actor:" & actor.name & ">" # TODO: ambigous
proc attenuate(r: Ref; a: Attenuation): Ref =
proc attenuate(r: Cap; a: Attenuation): Cap =
if a.len == 0: result = r
else: result = Ref(
else: result = Cap(
relay: r.relay,
target: r.target,
attenuation: a & r.attenuation)
@ -116,11 +149,11 @@ proc attenuate(r: Ref; a: Attenuation): Ref =
proc hash*(facet): Hash =
facet.id.hash
proc hash*(r: Ref): Hash = !$(r.relay.hash !& r.target.unsafeAddr.hash)
proc hash*(r: Cap): Hash = !$(r.relay.hash !& r.target.unsafeAddr.hash)
proc nextHandle(facet: Facet): Handle =
inc facet.actor.handleAllocator
facet.actor.handleAllocator
result = succ(facet.actor.handleAllocator[])
facet.actor.handleAllocator[] = result
proc facet*(turn: var Turn): Facet = turn.facet
@ -130,9 +163,9 @@ proc enqueue(turn: var Turn; target: Facet; action: TurnAction) =
else:
turn.queues[target] = @[action]
type Bindings = Table[Preserve[Ref], Preserve[Ref]]
type Bindings = Table[Value, Value]
proc match(bindings: var Bindings; p: Pattern; v: Assertion): bool =
proc match(bindings: var Bindings; p: Pattern; v: Value): bool =
case p.orKind
of PatternKind.Pdiscard: result = true
of PatternKind.Patom:
@ -148,7 +181,7 @@ proc match(bindings: var Bindings; p: Pattern; v: Assertion): bool =
result = v.isEmbedded
of PatternKind.Pbind:
if match(bindings, p.pbind.pattern, v):
bindings[toPreserve(p.pbind.pattern, Ref)] = v
bindings[p.pbind.pattern.toPreserves] = v
result = true
of PatternKind.Pand:
for pp in p.pand.patterns:
@ -186,21 +219,22 @@ proc match(bindings: var Bindings; p: Pattern; v: Assertion): bool =
result = true
break
proc match(p: Pattern; v: Assertion): Option[Bindings] =
proc match(p: Pattern; v: Value): Option[Bindings] =
var b: Bindings
if match(b, p, v):
result = some b
proc instantiate(t: Template; bindings: Bindings): Assertion =
proc instantiate(t: Template; bindings: Bindings): Value =
case t.orKind
of TemplateKind.Tattenuate:
let v = instantiate(t.tattenuate.template, bindings)
if not v.isEmbedded:
let cap = v.unembed(Cap)
if cap.isNone:
raise newException(ValueError, "Attempt to attenuate non-capability")
result = embed(attenuate(v.embed, t.tattenuate.attenuation))
result = attenuate(get cap, t.tattenuate.attenuation).embed
of TemplateKind.TRef:
let n = $t.tref.binding.int
try: result = bindings[toPreserve(n, Ref)]
try: result = bindings[n.toPreserves]
except KeyError:
raise newException(ValueError, "unbound reference: " & n)
of TemplateKind.Lit:
@ -212,20 +246,20 @@ proc instantiate(t: Template; bindings: Bindings): Assertion =
for i, tt in t.tcompound.rec.fields:
result[i] = instantiate(tt, bindings)
of TCompoundKind.arr:
result = initSequence(t.tcompound.arr.items.len, Ref)
result = initSequence(t.tcompound.arr.items.len)
for i, tt in t.tcompound.arr.items:
result[i] = instantiate(tt, bindings)
of TCompoundKind.dict:
result = initDictionary(Ref)
result = initDictionary()
for key, tt in t.tcompound.dict.entries:
result[key] = instantiate(tt, bindings)
proc rewrite(r: Rewrite; v: Assertion): Assertion =
proc rewrite(r: Rewrite; v: Value): Value =
let bindings = match(r.pattern, v)
if bindings.isSome:
result = instantiate(r.template, get bindings)
proc examineAlternatives(cav: Caveat; v: Assertion): Assertion =
proc examineAlternatives(cav: Caveat; v: Value): Value =
case cav.orKind
of CaveatKind.Rewrite:
result = rewrite(cav.rewrite, v)
@ -236,13 +270,13 @@ proc examineAlternatives(cav: Caveat; v: Assertion): Assertion =
of CaveatKind.Reject: discard
of CaveatKind.unknown: discard
proc runRewrites*(a: Attenuation; v: Assertion): Assertion =
proc runRewrites*(a: Attenuation; v: Value): Value =
result = v
for stage in a:
result = examineAlternatives(stage, result)
if result.isFalse: break
proc publish(turn: var Turn; r: Ref; v: Assertion; h: Handle) =
proc publish(turn: var Turn; r: Cap; v: Value; h: Handle) =
var a = runRewrites(r.attenuation, v)
if not a.isFalse:
let e = OutboundAssertion(
@ -251,13 +285,23 @@ proc publish(turn: var Turn; r: Ref; v: Assertion; h: Handle) =
enqueue(turn, r.relay) do (turn: var Turn):
e.established = true
publish(r.target, turn, AssertionRef(value: a), e.handle)
when tracing:
var act = ActionDescription(orKind: ActionDescriptionKind.enqueue)
act.enqueue.event.target.actor = turn.facet.actor.id.toPreserves
act.enqueue.event.target.facet = turn.facet.id.toPreserves
act.enqueue.event.target.oid = r.target.oid.toPreserves
act.enqueue.event.detail = trace.TurnEvent(orKind: TurnEventKind.assert)
act.enqueue.event.detail.assert.assertion.value.value =
mapEmbeds(v) do (cap: Value) -> Value: discard
act.enqueue.event.detail.assert.handle = h
turn.desc.actions.add act
proc publish*(turn: var Turn; r: Ref; a: Assertion): Handle =
proc publish*(turn: var Turn; r: Cap; a: Value): Handle {.discardable.} =
result = turn.facet.nextHandle()
publish(turn, r, a, result)
proc publish*[T](turn: var Turn; r: Ref; a: T): Handle =
publish(turn, r, toPreserve(a, Ref))
proc publish*[T](turn: var Turn; r: Cap; a: T): Handle {.discardable.} =
publish(turn, r, a.toPreserves)
proc retract(turn: var Turn; e: OutboundAssertion) =
enqueue(turn, e.peer.relay) do (turn: var Turn):
@ -270,31 +314,30 @@ proc retract*(turn: var Turn; h: Handle) =
if turn.facet.outbound.pop(h, e):
turn.retract(e)
proc message*(turn: var Turn; r: Ref; v: Assertion) =
proc message*(turn: var Turn; r: Cap; v: Value) =
var a = runRewrites(r.attenuation, v)
if not a.isFalse:
enqueue(turn, r.relay) do (turn: var Turn):
r.target.message(turn, AssertionRef(value: a))
proc message*[T](turn: var Turn; r: Ref; v: T) =
message(turn, r, toPreserve(v, Ref))
proc message*[T](turn: var Turn; r: Cap; v: T) =
message(turn, r, v.toPreserves)
proc sync(turn: var Turn; e: Entity; peer: Ref) =
proc sync(turn: var Turn; e: Entity; peer: Cap) =
e.sync(turn, peer)
# or turn.message(peer, true) ?
proc sync*(turn: var Turn; r, peer: Ref) =
proc sync*(turn: var Turn; r, peer: Cap) =
enqueue(turn, r.relay) do (turn: var Turn):
sync(turn, r.target, peer)
proc replace*[T](turn: var Turn; `ref`: Ref; h: Handle; v: T): Handle =
result = publish(turn, `ref`, v)
proc replace*[T](turn: var Turn; cap: Cap; h: Handle; v: T): Handle =
result = publish(turn, cap, v)
if h != default(Handle):
retract(turn, h)
proc replace*[T](turn: var Turn; `ref`: Ref; h: var Handle; v: T): Handle {.discardable.} =
proc replace*[T](turn: var Turn; cap: Cap; h: var Handle; v: T): Handle {.discardable.} =
var old = h
h = publish(turn, `ref`, v)
h = publish(turn, cap, v)
if old != default(Handle):
retract(turn, old)
h
@ -303,22 +346,22 @@ proc stop*(turn: var Turn) {.gcsafe.}
proc run*(facet; action: TurnAction; zombieTurn = false) {.gcsafe.}
proc newFacet(actor; parent: ParentFacet; initialAssertions: OutboundTable): Facet =
proc newFacet(actor; parent: Facet; initialAssertions: OutboundTable): Facet =
result = Facet(
id: getMonoTime().ticks.FacetId,
actor: actor,
parent: parent,
outbound: initialAssertions,
isAlive: true)
if parent.isSome: parent.get.children.incl result
if not parent.isNil: parent.children.incl result
proc newFacet(actor; parent: ParentFacet): Facet =
proc newFacet(actor; parent: Facet): Facet =
var initialAssertions: OutboundTable
newFacet(actor, parent, initialAssertions)
proc isInert(facet): bool =
result = facet.children.len == 0 and
(facet.outbound.len == 0 or facet.parent.isNone) and
(facet.outbound.len == 0 or facet.parent.isNil) and
facet.inertCheckPreventers == 0
proc preventInertCheck*(facet): (proc() {.gcsafe.}) {.discardable.} =
@ -342,8 +385,8 @@ proc terminate(facet; turn: var Turn; orderly: bool) {.gcsafe.} =
if facet.isAlive:
facet.isAlive = false
let parent = facet.parent
if parent.isSome:
parent.get.children.excl facet
if not parent.isNil:
parent.children.excl facet
block:
var turn = Turn(facet: facet, queues: turn.queues)
while facet.children.len > 0:
@ -353,55 +396,87 @@ proc terminate(facet; turn: var Turn; orderly: bool) {.gcsafe.} =
act(turn)
for a in facet.outbound.values: turn.retract(a)
if orderly:
if parent.isSome:
if parent.get.isInert:
parent.get.terminate(turn, true)
if not parent.isNil:
if parent.isInert:
parent.terminate(turn, true)
else:
terminate(facet.actor, turn, nil)
when tracing:
var act = ActionDescription(orKind: ActionDescriptionKind.facetStop)
act.facetstop.path = facet.path
turn.desc.actions.add act
proc stopIfInertAfter(action: TurnAction): TurnAction =
proc wrapper(turn: var Turn) =
action(turn)
enqueue(turn, turn.facet) do (turn: var Turn):
if (turn.facet.parent.isSome and
(not turn.facet.parent.get.isAlive)) or
if (not turn.facet.parent.isNil and
(not turn.facet.parent.isAlive)) or
turn.facet.isInert:
stop(turn)
wrapper
proc newFacet*(turn: var Turn): Facet = newFacet(turn.facet.actor, turn.facet)
proc inFacet*(turn: var Turn; bootProc: TurnAction): Facet =
result = newFacet(turn.facet.actor, some turn.facet)
result = newFacet(turn)
when tracing:
var act = ActionDescription(orKind: ActionDescriptionKind.facetstart)
act.facetstart.path.add result.path
turn.desc.actions.add act
inFacet(turn, result, stopIfInertAfter(bootProc))
proc facet*(turn: var Turn; bootProc: TurnAction): Facet {.deprecated.} = inFacet(turn, bootProc)
proc newActor(name: string; bootProc: TurnAction; initialAssertions: OutboundTable): Actor =
proc newActor(name: string; handleAlloc: ref Handle): Actor =
let
now = getTime()
seed = now.toUnix * 1_000_000_000 + now.nanosecond
result = Actor(
name: name,
id: ActorId(seed))
result.root = newFacet(result, none Facet)
result.future = newFuture[void]($result)
run(
newFacet(result, some result.root, initialAssertions),
stopIfInertAfter(bootProc))
id: ActorId(seed),
handleAllocator: handleAlloc,
)
result.root = newFacet(result, nil)
when tracing:
var act = ActorActivation(orKind: ActorActivationKind.start)
act.start.actorName = Name(orKind: NameKind.named)
act.start.actorName.named.name = name.toPreserves
trace(result, act)
proc run(actor; bootProc: TurnAction; initialAssertions: OutboundTable) =
run(newFacet(actor, actor.root, initialAssertions), stopIfInertAfter(bootProc))
proc bootActor*(name: string; bootProc: TurnAction): Actor =
var initialAssertions: OutboundTable
newActor(name, bootProc, initialAssertions)
result = newActor(name, new(ref Handle))
when tracing:
new result.turnIdAllocator
let path = getEnv("SYNDICATE_TRACE_FILE", "/tmp/" & name & ".trace.pr")
case path
of "": stderr.writeLine "$SYNDICATE_TRACE_FILE unset, not tracing actor ", name
of "-": result.traceStream = newFileStream(stderr)
else: result.traceStream = openFileStream(path, fmWrite)
run(result, bootProc, initialAssertions)
proc spawn*(name: string; turn: var Turn; bootProc: TurnAction; initialAssertions = initHashSet[Handle]()) =
proc spawn*(name: string; turn: var Turn; bootProc: TurnAction; initialAssertions = initHashSet[Handle]()): Actor {.discardable.} =
let actor = newActor(name, turn.facet.actor.handleAllocator)
enqueue(turn, turn.facet) do (turn: var Turn):
var newOutBound: Table[Handle, OutboundAssertion]
for key in initialAssertions:
discard turn.facet.outbound.pop(key, newOutbound[key])
discard newActor(name, bootProc, newOutBound)
when tracing:
actor.turnIdAllocator = turn.facet.actor.turnIdAllocator
actor.traceStream = turn.facet.actor.traceStream
var act = ActionDescription(orKind: ActionDescriptionKind.spawn)
act.spawn.id = actor.id.toPreserves
turn.desc.actions.add act
run(actor, bootProc, newOutBound)
actor
proc newInertRef*(): Ref =
proc newInertCap*(): Cap =
let a = bootActor("inert") do (turn: var Turn): turn.stop()
Ref(relay: a.root)
Cap(relay: a.root)
proc atExit*(actor; action) = actor.exitHooks.add action
@ -409,13 +484,16 @@ proc terminate(actor; turn; reason: ref Exception) =
if not actor.exiting:
actor.exiting = true
actor.exitReason = reason
when tracing:
var act = ActorActivation(orKind: ActorActivationKind.stop)
if not reason.isNil:
act.stop.status = ExitStatus(orKind: ExitStatusKind.Error)
act.stop.status.error.message = reason.msg
trace(actor, act)
for hook in actor.exitHooks: hook(turn)
proc finish(turn: var Turn) =
actor.root.terminate(turn, not reason.isNil)
if reason.isNil:
actor.future.complete()
else:
actor.future.fail reason
actor.root.terminate(turn, reason.isNil)
actor.exited = true
callSoon do ():
run(actor.root, finish, true)
@ -437,28 +515,36 @@ template tryFacet(facet; body: untyped) =
except CatchableError as err: terminate(facet, err)
proc run*(facet; action: TurnAction; zombieTurn = false) =
if not zombieTurn:
if not facet.actor.exitReason.isNil: return
if not facet.isAlive: return
# TODO: not Nim idiom
tryFacet(facet):
var queues = newTable[Facet, seq[TurnAction]]()
block:
var turn = Turn(facet: facet, queues: queues)
action(turn)
for facet, queue in queues:
for action in queue: run(facet, action)
if zombieTurn or (facet.actor.exitReason.isNil and facet.isAlive):
tryFacet(facet):
var queues = newTable[Facet, seq[TurnAction]]()
block:
var turn = Turn(facet: facet, queues: queues)
action(turn)
when tracing:
turn.desc.id = facet.nextTurnId.toPreserves
facet.actor.trace ActorActivation(
orKind: ActorActivationKind.turn, turn: turn.desc)
for facet, queue in queues:
for action in queue: run(facet, action)
proc run*(`ref`: Ref; action: TurnAction) =
## Convenience proc to run a `TurnAction` in the scope of a `Ref`.
run(`ref`.relay, action)
proc run*(cap: Cap; action: TurnAction) =
## Convenience proc to run a `TurnAction` in the scope of a `Cap`.
run(cap.relay, action)
proc addCallback*(fut: FutureBase; facet: Facet; act: TurnAction) =
## Add a callback to a `Future` that will be called at a later `Turn`
## within the context of `facet`.
addCallback(fut) do ():
if fut.failed: terminate(facet, fut.error)
else: run(facet, act)
else:
when tracing:
run(facet) do (turn: var Turn):
turn.desc.cause = TurnCause(orKind: TurnCauseKind.external)
turn.desc.cause.external.description = "Future".toPreserves
act(turn)
else:
run(facet, act)
proc addCallback*(fut: FutureBase; turn: var Turn; act: TurnAction) =
## Add a callback to a `Future` that will be called at a later `Turn`
@ -470,9 +556,21 @@ proc addCallback*(fut: FutureBase; turn: var Turn; act: TurnAction) =
else:
addCallback(fut, turn.facet, act)
proc addCallback*[T](fut: Future[T]; turn: var Turn; act: proc (t: var Turn, x: T) {.gcsafe.}) =
addCallback(fut, turn) do (turn: var Turn):
if fut.failed: terminate(turn.facet, fut.error)
else:
when tracing:
turn.desc.cause = TurnCause(orKind: TurnCauseKind.external)
turn.desc.cause.external.description = "Future".toPreserves
act(turn, read fut)
proc stop*(turn: var Turn, facet: Facet) =
enqueue(turn, facet.parent.get) do (turn: var Turn):
facet.terminate(turn, true)
if facet.parent.isNil:
facet.terminate(turn, true)
else:
enqueue(turn, facet.parent) do (turn: var Turn):
facet.terminate(turn, true)
proc stop*(turn: var Turn) =
stop(turn, turn.facet)
@ -483,20 +581,32 @@ proc onStop*(facet: Facet; act: TurnAction) =
proc stopActor*(turn: var Turn) =
let actor = turn.facet.actor
enqueue(turn, turn.facet.actor.root) do (turn: var Turn):
enqueue(turn, actor.root) do (turn: var Turn):
terminate(actor, turn, nil)
proc freshen*(turn: var Turn, act: TurnAction) =
assert(turn.queues.len == 0, "Attempt to freshen a non-stale Turn")
run(turn.facet, act)
proc newRef*(relay: Facet; e: Entity): Ref =
Ref(relay: relay, target: e)
proc newCap*(relay: Facet; e: Entity): Cap =
Cap(relay: relay, target: e)
proc newRef*(turn; e: Entity): Ref =
Ref(relay: turn.facet, target: e)
proc newCap*(turn; e: Entity): Cap =
Cap(relay: turn.facet, target: e)
proc sync*(turn, refer: Ref, cb: proc(t: Turn) {.gcsafe.}) =
discard # TODO
proc newCap*(e: Entity; turn): Cap =
Cap(relay: turn.facet, target: e)
proc future*(actor): Future[void] = actor.future
type SyncContinuation {.final.} = ref object of Entity
action: TurnAction
method message(entity: SyncContinuation; turn: var Turn; v: AssertionRef) =
entity.action(turn)
proc sync*(turn: var Turn; refer: Cap; act: TurnAction) =
sync(turn, refer, newCap(turn, SyncContinuation(action: act)))
proc running*(actor): bool =
result = not actor.exited
if not (result or actor.exitReason.isNil):
raise actor.exitReason

View File

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

View File

@ -0,0 +1,35 @@
# SPDX-FileCopyrightText: ☭ Emery Hemingway
# SPDX-License-Identifier: Unlicense
import std/[asyncdispatch, monotimes, times]
import preserves
import syndicate
import ../protocols/timer
from syndicate/protocols/dataspace import Observe
export timer
type Observe = dataspace.Observe
proc now: float64 = getTime().toUnixFloat()
proc spawnTimers*(turn: var Turn; ds: Cap): Actor {.discardable.} =
## Spawn a timer actor.
spawn("timer", turn) do (turn: var Turn):
during(turn, ds, inject(grab Observe(pattern: dropType LaterThan), {0: grabLit()})) do (seconds: float):
let period = seconds - now()
if period < 0.001:
discard publish(turn, ds, LaterThan(seconds: seconds))
else:
addCallback(sleepAsync(period * 1_000), turn) do (turn: var Turn):
discard publish(turn, ds, LaterThan(seconds: seconds))
proc after*(turn: var Turn; ds: Cap; dur: Duration; act: TurnAction) =
## Execute `act` after some duration of time.
let later = now() + dur.inMilliseconds.float64 * 1_000.0
onPublish(turn, ds, grab LaterThan(seconds: later)):
act(turn)
# TODO: periodic timer

View File

@ -37,5 +37,12 @@ proc change*[T](bag: var Bag[T]; key: T; delta: int; clamp = false): ChangeDescr
if result in {cdAbsentToAbsent, cdPresentToAbsent}:
bag.del(key)
iterator items*[T](bag: Bag[T]): (int, T) =
for k, v in bag: yield(v, k)
iterator items*[T](bag: Bag[T]): T =
for x in bag.keys: yield x
proc `$`*(bag: Bag): string =
result.add '{'
for x in bag.keys:
if result.len > 1: result.add ' '
result.add $x
result.add '}'

View File

@ -6,64 +6,37 @@ runnableExamples:
let sturdy = mint()
check $sturdy == """<ref {oid: "syndicate" sig: #x"69ca300c1dbfa08fba692102dd82311a"}>"""
import std/options
import std/[options, tables]
from std/sequtils import toSeq
import hashlib/misc/blake2
import preserves
import ./protocols/sturdy
from ./actors import Ref
export `$`
proc hmac(key, data: openarray[byte]): seq[byte] =
count[Hmac[BLAKE2S_256]](key, data).data[0..15].toSeq
proc mint*[T](key: openarray[byte]; oid: Preserve[T]): SturdyRef[T] =
SturdyRef[T](parameters: {
"oid": oid,
"sig": hmac(key, encode(oid)).toPreserve(T),
}.toDictionary,
)
proc mint*(key: openarray[byte]; oid: Value): SturdyRef =
result.parameters.oid = oid
result.parameters.sig = hmac(key, oid.encode)
proc mint*(): SturdyRef[Ref] =
proc mint*(): SturdyRef =
var key: array[16, byte]
mint(key, toPreserve("syndicate", Ref))
mint(key, "syndicate".toPreserves)
proc attenuate*[T](r: SturdyRef[T]; caveats: seq[Caveat]): SturdyRef[T] =
result = SturdyRef[T](
oid: r.oid,
caveatChain: r.caveatChain,
sig: hmac(r.sig, encode caveats))
result.caveatChain.add caveats
proc attenuate*(r: SturdyRef; caveats: seq[Caveat]): SturdyRef =
if r.parameters.caveats.isSome:
result.parameters.caveats = some(r.parameters.caveats.get & caveats.toPreserves)
result.parameters.oid = r.parameters.oid
result.parameters.sig = hmac(r.parameters.sig, caveats.toPreserves.encode)
proc validate*[T](key: openarray[byte]; sturdy: SturdyRef[T]): bool =
let oid = step(sturdy.parameters, Symbol"oid")
if oid.isSome:
let ctrl = step(sturdy.parameters, Symbol"sig")
if ctrl.isSome:
var sig = hmac(key, oid.get.encode)
let caveats = step(sturdy.parameters, Symbol"caveats")
if caveats.isSome and caveats.get.isSequence:
for cav in caveats.get.sequence:
sig = hmac(sig, encode cav)
result = (sig == ctrl.get.bytes)
proc validate*(key: openarray[byte]; sturdy: SturdyRef): bool =
var sig = hmac(key, sturdy.parameters.oid.encode)
if sturdy.parameters.caveats.isSome:
for cav in sturdy.parameters.caveats.get:
sig = hmac(sig, encode cav)
result = (sig == sturdy.parameters.sig)
when isMainModule:
from os import commandLineParams
var key: array[16, byte]
case readBytes(stdin, key, 0, 16)
of 16: discard
of 0: stderr.writeLine "using null key"
else: quit "expected sixteen bytes of key from stdin"
var oids: seq[Preserve[void]]
for p in commandLineParams():
add(oids, parsePreserves p)
if oids.len == 0: oids.add(toPreserve "syndicate")
for oid in oids:
let sturdy = mint(key, oid)
doAssert validate(key, sturdy)
stdout.writeLine(sturdy)
# mint utility moved to syndicate_utils/src/mintsturdyref.nim

View File

@ -1,15 +1,15 @@
# SPDX-FileCopyrightText: ☭ 2022 Emery Hemingway
# SPDX-License-Identifier: Unlicense
import std/[hashes, tables]
import std/[hashes, options, tables]
import preserves
import ./actors, ./protocols/dataspace, ./skeletons
from ./protocols/protocol import Handle
type
Assertion = Preserve[Ref]
Observe = dataspace.Observe[Ref]
Assertion = Value
Observe = dataspace.Observe
Turn = actors.Turn
Dataspace {.final.} = ref object of Entity
@ -18,30 +18,33 @@ type
method publish(ds: Dataspace; turn: var Turn; a: AssertionRef; h: Handle) {.gcsafe.} =
if add(ds.index, turn, a.value):
var obs: Observe
if obs.fromPreserve a.value:
ds.index.add(turn, obs.pattern, obs.observer)
var obs = a.value.preservesTo(Observe)
if obs.isSome and obs.get.observer of Cap:
ds.index.add(turn, obs.get.pattern, Cap(obs.get.observer))
ds.handleMap[h] = a.value
method retract(ds: Dataspace; turn: var Turn; h: Handle) {.gcsafe.} =
try:
let v = ds.handleMap[h]
if remove(ds.index, turn, v):
ds.handleMap.del h
var obs: Observe
if obs.fromPreserve v:
ds.index.remove(turn, obs.pattern, obs.observer)
except KeyError: discard
let v = ds.handleMap[h]
if remove(ds.index, turn, v):
ds.handleMap.del h
var obs = v.preservesTo(Observe)
if obs.isSome and obs.get.observer of Cap:
ds.index.remove(turn, obs.get.pattern, Cap(obs.get.observer))
method message(ds: Dataspace; turn: var Turn; a: AssertionRef) {.gcsafe.} =
ds.index.deliverMessage(turn, a.value)
proc newDataspace*(turn: var Turn): Ref =
newRef(turn, Dataspace(index: initIndex()))
proc newDataspace*(turn: var Turn): Cap =
newCap(turn, Dataspace(index: initIndex()))
type BootProc = proc (ds: Ref; turn: var Turn) {.gcsafe.}
type BootProc = proc (turn: var Turn; ds: Cap) {.gcsafe.}
type DeprecatedBootProc = proc (ds: Cap; turn: var Turn) {.gcsafe.}
proc bootDataspace*(name: string; bootProc: BootProc): Actor {.discardable.} =
proc bootDataspace*(name: string; bootProc: BootProc): Actor =
bootActor(name) do (turn: var Turn):
discard turn.facet.preventInertCheck()
bootProc(newDataspace(turn), turn)
bootProc(turn, newDataspace(turn))
proc bootDataspace*(name: string; bootProc: DeprecatedBootProc): Actor {.deprecated.} =
bootDataspace(name) do (turn: var Turn, ds: Cap):
bootProc(ds, turn)

View File

@ -1,23 +0,0 @@
# SPDX-FileCopyrightText: 2021 ☭ Emery Hemingway
# SPDX-License-Identifier: Unlicense
import std/[asyncdispatch, monotimes, times]
import preserves, preserves/records
import syndicate, syndicate/assertions
import ../../syndicate/protocols/schemas/timer
syndicate timerDriver:
spawn "timer":
during(observe(laterThan(?msecs))) do (msecs: float64):
let
now = getTime().toUnixFloat() * 1_000.0
period = msecs - now
if period > 0:
getCurrentFacet().beginExternalTask()
addTimer(period.int, oneshot = true) do (fd: AsyncFD) -> bool:
react: publish: laterThan(deadline)
getCurrentFacet().endExternalTask()
true
else:
react: publish: prsTimeLaterThan(deadline)

View File

@ -5,14 +5,8 @@ import std/[hashes, tables]
import preserves
import ./actors, ./patterns, ./protocols/dataspace
from ./protocols/protocol import Handle
type
Observe = dataspace.Observe[Ref]
Turn = actors.Turn
type
DuringProc* = proc (turn: var Turn; a: Assertion; h: Handle): TurnAction {.gcsafe.}
DuringProc* = proc (turn: var Turn; a: Value; h: Handle): TurnAction {.gcsafe.}
DuringActionKind = enum null, dead, act
DuringAction = object
case kind: DuringActionKind
@ -50,5 +44,5 @@ method retract(de: DuringEntity; turn: var Turn; h: Handle) =
proc during*(cb: DuringProc): DuringEntity = DuringEntity(cb: cb)
proc observe*(turn: var Turn; ds: Ref; pat: Pattern; e: Entity): Handle =
publish(turn, ds, Observe(pattern: pat, observer: newRef(turn, e)))
proc observe*(turn: var Turn; ds: Cap; pat: Pattern; e: Entity): Handle =
publish(turn, ds, Observe(pattern: pat, observer: newCap(turn, e)))

View File

@ -3,43 +3,43 @@
import std/[hashes, tables]
from ./actors import Ref, hash
from ./actors import Cap, hash
from ./protocols/sturdy import Oid
proc hash(r: Ref): Hash = !$(r.relay.hash !& r.target.unsafeAddr.hash)
proc hash(r: Cap): Hash = !$(r.relay.hash !& r.target.unsafeAddr.hash)
type
Membrane* = object
## Bidirectional mapping between `Oid` and `Ref` values.
## Bidirectional mapping between `Oid` and `Cap` values.
## https://synit.org/book/protocol.html#membranes
byOid: Table[Oid, WireSymbol]
byRef: Table[Ref, WireSymbol]
byCap: Table[Cap, WireSymbol]
WireSymbol* = ref object
oid: Oid
`ref`: Ref
cap: Cap
count: int
proc oid*(sym: WireSymbol): Oid = sym.oid
proc `ref`*(sym: WireSymbol): Ref = sym.ref
proc cap*(sym: WireSymbol): Cap = sym.cap
proc grab*(mem: Membrane; key: Oid): WireSymbol =
## Grab a `WireSymbol` from a `Membrane`.
mem.byOid.getOrDefault(key)
proc grab*(mem: Membrane; key: Ref): WireSymbol =
proc grab*(mem: Membrane; key: Cap): WireSymbol =
## Grab a `WireSymbol` from a `Membrane`.
mem.byRef.getOrDefault(key)
mem.byCap.getOrDefault(key)
proc drop*(mem: var Membrane; sym: WireSymbol) =
## Drop a `WireSymbol` from a `Membrane`.
dec sym.count
if sym.count < 1:
mem.byOid.del sym.oid
mem.byRef.del sym.`ref`
mem.byCap.del sym.cap
proc newWireSymbol*(mem: var Membrane; o: Oid; r: Ref): WireSymbol =
proc newWireSymbol*(mem: var Membrane; o: Oid; r: Cap): WireSymbol =
## Allocate a `WireSymbol` at a `Membrane`.
result = WireSymbol(oid: o, `ref`: r, count: 1)
result = WireSymbol(oid: o, cap: r, count: 1)
mem.byOid[result.oid] = result
mem.byRef[result.`ref`] = result
mem.byCap[result.cap] = result

View File

@ -1,23 +1,34 @@
# SPDX-FileCopyrightText: ☭ Emery Hemingway
# SPDX-License-Identifier: Unlicense
import std/[options, sequtils, tables, typetraits]
import std/[algorithm, options, sequtils, tables, typetraits]
import preserves
import ./protocols/dataspacePatterns
from ./actors import Ref
from ./actors import Cap
export dataspacePatterns.`$`, PatternKind, DCompoundKind, AnyAtomKind
type
AnyAtom = dataspacePatterns.AnyAtom[Ref]
DBind = dataspacePatterns.DBind[Ref]
DCompound = dataspacePatterns.DCompound[Ref]
DCompoundArr = dataspacePatterns.DCompoundArr[Ref]
DCompoundDict = dataspacePatterns.DCompoundDict[Ref]
DCompoundRec = dataspacePatterns.DCompoundRec[Ref]
DLit = dataspacePatterns.DLit[Ref]
Pattern* = dataspacePatterns.Pattern[Ref]
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(d: sink DBind): Pattern =
Pattern(orKind: PatternKind.DBind, dbind: d)
@ -46,7 +57,7 @@ proc drop*(): Pattern {.inline.} = Pattern(orKind: PatternKind.DDiscard)
proc grab*(): Pattern {.inline.} = DBind(pattern: drop()).toPattern
## Create a pattern to capture any value.
proc grab*[T](pr: Preserve[T]): Pattern =
proc grab*(pr: Value): Pattern =
## Convert a `Preserve` value to a `Pattern`.
runnableExamples:
from std/unittest import check
@ -55,7 +66,6 @@ proc grab*[T](pr: Preserve[T]): Pattern =
$(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>}> <_>]>"""
assert not pr.embedded
case pr.kind
of pkBoolean:
AnyAtom(orKind: AnyAtomKind.`bool`, bool: pr.bool).toPattern
@ -63,8 +73,8 @@ proc grab*[T](pr: Preserve[T]): Pattern =
AnyAtom(orKind: AnyAtomKind.`float`, float: pr.float).toPattern
of pkDouble:
AnyAtom(orKind: AnyAtomKind.`double`, double: pr.double).toPattern
of pkSignedInteger:
AnyAtom(orKind: AnyAtomKind.`int`, int: pr.int).toPattern
of pkRegister:
AnyAtom(orKind: AnyAtomKind.`int`, int: pr.register).toPattern
of pkString:
AnyAtom(orKind: AnyAtomKind.`string`, string: pr.string).toPattern
of pkByteString:
@ -76,21 +86,24 @@ proc grab*[T](pr: Preserve[T]): Pattern =
drop()
else:
DCompoundRec(
label: cast[Preserve[Ref]](pr.label), # TODO: don't cast like this
fields: map[Preserve[T], Pattern](pr.fields, grab)).toPattern
label: pr.label,
fields: map[Value, Pattern](pr.fields, grab)).toPattern
of pkSequence:
DCompoundArr(items: map(pr.sequence, grab)).toPattern
of pkSet: raise newException(
ValueError, "cannot construct a pattern over a set literal")
of pkSet:
raiseAssert "cannot construct a pattern over a set literal"
of pkDictionary:
var dict = DCompoundDict()
for key, val in pr.pairs: dict.entries[cast[Preserve[Ref]](key)] = grab val
for key, val in pr.pairs: dict.entries[key] = grab val
dict.toPattern
of pkEmbedded:
# TODO: can patterns be constructed over embedded literals?
drop()
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")
proc grab*[T](val: T): Pattern =
proc grab*[T](x: T): Pattern =
## Construct a `Pattern` from value of type `T`.
runnableExamples:
from std/unittest import check
@ -98,34 +111,7 @@ proc grab*[T](val: T): Pattern =
$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 (toPreserve(val, Ref))
proc patternOfType(typ: static typedesc; `bind`: static bool): Pattern =
when typ is ref:
patternOfType(pointerBase(typ), `bind`)
elif typ.hasPreservesRecordPragma:
var rec = DCompoundRec(label: typ.recordLabel.tosymbol(Ref))
for _, f in fieldPairs(default typ):
add(rec.fields, patternOfType(typeof f, `bind`))
result = rec.toPattern
elif typ.hasPreservesDictionaryPragma:
var dict = DCompoundDict()
for key, val in fieldPairs(default typ):
dict.entries[toSymbol(key, Ref)] = patternOfType(typeof val, `bind`)
dict.toPattern
elif typ is tuple:
var arr = DCompoundArr()
for _, f in fieldPairs(default typ):
add(arr.items, patternOfType(typeof f, `bind`))
arr.toPattern
elif typ is array:
var arr = DCompoundArr()
arr.items.setLen(len(typ))
for e in arr.items.mitems: e = grab()
arr.toPattern
else:
if `bind`: grab()
else: drop()
grab(x.toPreserves)
proc grabType*(typ: static typedesc): Pattern =
## Derive a `Pattern` from type `typ`.
@ -148,43 +134,76 @@ proc grabType*(typ: static typedesc): Pattern =
"<rec rect [<arr [<bind <_>> <bind <_>>]> <arr [<bind <_>> <bind <_>>]>]>"
$(grabType ColoredRect) ==
"<dict {color: <bind <_>> rect: <rec rect [<arr [<bind <_>> <bind <_>>]> <arr [<bind <_>> <bind <_>>]>]>}>"
patternOfType(typ, true)
proc dropType*(typ: static typedesc): Pattern =
## Derive a `Pattern` from type `typ` without any bindings.
patternOfType(typ, false)
when typ is ref:
grabType(pointerBase(typ))
elif typ.hasPreservesRecordPragma:
var rec = DCompoundRec(label: typ.recordLabel.toSymbol)
for _, f in fieldPairs(default typ):
add(rec.fields, grabType(typeof f))
result = rec.toPattern
elif typ.hasPreservesDictionaryPragma:
var dict = DCompoundDict()
for key, val in fieldPairs(default typ):
dict.entries[key.toSymbol] = grabType(typeof val)
dict.toPattern
elif typ is tuple:
var arr = DCompoundArr()
for _, f in fieldPairs(default typ):
add(arr.items, grabType(typeof f))
arr.toPattern
elif typ is array:
var arr = DCompoundArr()
arr.items.setLen(len(typ))
for e in arr.items.mitems: e = grab()
arr.toPattern
else:
grab()
proc fieldCount(T: typedesc): int =
for _, _ in fieldPairs(default T):
inc result
proc match(bindings: sink openArray[(int, Pattern)]; i: int; pat: var Pattern): bool =
proc dropType*(typ: static typedesc): Pattern =
## Derive a `Pattern` from type `typ` without any bindings.
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
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
else:
drop()
proc lookup(bindings: openArray[(int, Pattern)]; i: int): Pattern =
for (j, b) in bindings:
if i == j:
pat = b
return true
if i == j: return b
return drop()
proc grab*(typ: static typedesc; bindings: sink openArray[(int, Pattern)]): Pattern =
## Construct a `Pattern` from type `typ` that selectively captures fields.
runnableExamples:
import preserves
from std/unittest import check
type
Point = tuple[x: int; y: int]
Rect {.preservesRecord: "rect".} = tuple[a: Point; B: Point]
ColoredRect {.preservesDictionary.} = tuple[color: string; rect: Rect]
check:
$grab(Point, { 1: grab() }) == "<arr [<_> <bind <_>>]>"
$grab(Rect, { 0: grab() }) == "<rec rect [<bind <_>> <arr [<_> <_>]>]>"
when typ is ref:
## 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(Ref))
var rec = DCompoundRec(label: typ.recordLabel.toSymbol)
rec.fields.setLen(fieldCount typ)
var i: int
for _, f in fieldPairs(default typ):
if not match(bindings, i, rec.fields[i]):
rec.fields[i] = dropType(typeof f)
rec.fields[i] = lookup(bindings, i)
inc i
result = rec.toPattern
elif typ is tuple:
@ -192,27 +211,33 @@ proc grab*(typ: static typedesc; bindings: sink openArray[(int, Pattern)]): Patt
arr.items.setLen(fieldCount typ)
var i: int
for _, f in fieldPairs(default typ):
if not match(bindings, i, arr.items[i]):
arr.items[i] = dropType(typeof f)
arr.items[i] = lookup(bindings, i)
inc i
result = arr.toPattern
else:
{.error: "grab with 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 =
## Construct a `Pattern` from type `typ` with dictionary field `bindings`.
when typ.hasPreservesDictionaryPragma:
DCompoundDict(entries: bindings.toTable).toPattern
else:
{.error: "grab with dictionary bindings not implemented for " & $typ.}
proc grabLit*(): Pattern =
runnableExamples:
from std/unittest import check
check:
$grabLit() == """<rec lit [<bind <_>>]>"""
grabType(dataspacePatterns.DLit[void])
grabType(dataspacePatterns.DLit)
proc grabDict*(): Pattern =
grabType(dataspacePatterns.DCompoundDict[void])
grabType(dataspacePatterns.DCompoundDict)
proc unpackLiterals*[E](pr: Preserve[E]): Preserve[E] =
proc unpackLiterals*(pr: Value): Value =
result = pr
apply(result) do (pr: var Preserve[E]):
if pr.isRecord("lit", 1):
apply(result) do (pr: var 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 =
@ -257,27 +282,98 @@ proc inject*(pat: Pattern; bindings: openArray[(int, Pattern)]): Pattern =
var offset = 0
inject(pat, bindings, offset)
proc recordPattern*(label: Preserve[Ref], fields: varargs[Pattern]): Pattern =
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 =
runnableExamples:
from std/unittest import check
import syndicate/actors, preserves
check:
$recordPattern("Says".toSymbol(Ref), grab(), grab()) ==
$grabRecord("Says".toSymbol, grab(), grab()) ==
"""<rec Says [<bind <_>> <bind <_>>]>"""
DCompoundRec(label: label, fields: fields.toSeq).toPattern
proc grabRecord*(label: string, fields: varargs[Pattern]): Pattern =
## Sugar for creating record patterns.
## `label` is converted to a symbol value.
grabRecord(label.toSymbol, fields)
proc grabDictionary*(bindings: sink openArray[(Value, Pattern)]): Pattern =
## Construct a pattern that grabs some dictionary pairs.
DCompoundDict(entries: bindings.toTable).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
proc depattern(comp: DCompound; values: var seq[Value]; index: var int): Value {.gcsafe.}
proc depattern(pat: Pattern; values: var seq[Value]; index: var int): Value =
case pat.orKind
of PatternKind.DDiscard:
discard
of PatternKind.DBind:
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)
proc depattern(comp: DCompound; values: var seq[Value]; index: var int): Value {.gcsafe.} =
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:
result = initDictionary(Cap)
for key, val in comp.dict.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`.
var index: int
depattern(pat, values, index)
type Literal*[T] = object
## A wrapper type to deserialize patterns to native values.
value*: T
proc fromPreservesHook*[T](lit: var Literal[T]; pr: Value): bool =
var pat: Pattern
pat.fromPreserves(pr) and lit.value.fromPreserves(depattern(pat, @[]))
proc toPreservesHook*[T](lit: Literal[T]): Value =
lit.value.grab.toPreserves
type
Value = Preserve[Ref]
Path = seq[Value]
Path* = seq[Value]
Paths* = seq[Path]
Captures* = seq[Value]
Analysis* = tuple
constPaths: seq[Path]
constPaths: Paths
constValues: seq[Value]
capturePaths: seq[Path]
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.toPreserve(Ref))
path.add(key.toPreserves)
walk(result, path, pat)
discard path.pop
@ -290,51 +386,47 @@ func walk(result: var Analysis; path: var Path; p: Pattern) =
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.entries: walk(result, path, k, e)
for k, e in p.dcompound.dict.orderedEntries: walk(result, path, k, e)
of PatternKind.DBind:
result.capturePaths.add(path)
walk(result, path, p.dbind.pattern)
of PatternKind.DDiscard: discard
of PatternKind.DLit:
result.constPaths.add(path)
result.constValues.add(p.dlit.value.toPreserve(Ref))
result.constValues.add(p.dlit.value.toPreserves)
func analyse*(p: Pattern): Analysis =
var path: Path
walk(result, path, p)
func projectPath*(v: Value; path: Path): Option[Value] =
result = some(v)
for index in path:
result = preserves.step(result.get, index)
if result.isNone: break
func projectPaths*(v: Value; paths: seq[Path]): seq[Value] =
result = newSeq[Value](paths.len)
func projectPaths*(v: Value; paths: Paths): Option[Captures] =
var res = newSeq[Value](paths.len)
for i, path in paths:
var vv = projectPath(v, path)
if vv.isSome: result[i] = get(vv)
var vv = step(v, path)
if vv.isSome: res[i] = get(vv)
else: return
some res
func matches*(pat: Pattern; pr: Value): bool =
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 = projectPath(pr, path)
if v.isNone : return false
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 projectPath(pr, path): return false
if isNone step(pr, path): return false
true
func 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 = projectPath(pr, path)
let v = step(pr, path)
if v.isNone : return @[]
if analysis.constValues[i] != v.get: return @[]
for path in analysis.capturePaths:
let v = projectPath(pr, path)
let v = step(pr, path)
if v.isNone: return @[]
result.add(get v)

View File

@ -3,12 +3,12 @@ import
preserves, dataspacePatterns
type
Observe*[Cap] {.preservesRecord: "Observe".} = ref object
`pattern`*: dataspacePatterns.Pattern[Cap]
`observer`*: Cap
Observe* {.preservesRecord: "Observe".} = object
`pattern`*: dataspacePatterns.Pattern
`observer`* {.preservesEmbedded.}: EmbeddedRef
proc `$`*[Cap](x: Observe[Cap]): string =
`$`(toPreserve(x, Cap))
proc `$`*(x: Observe): string =
`$`(toPreserves(x))
proc encode*[Cap](x: Observe[Cap]): seq[byte] =
encode(toPreserve(x, Cap))
proc encode*(x: Observe): seq[byte] =
encode(toPreserves(x))

View File

@ -5,100 +5,85 @@ import
type
AnyAtomKind* {.pure.} = enum
`bool`, `float`, `double`, `int`, `string`, `bytes`, `symbol`, `embedded`
AnyAtomBool* = bool
AnyAtomFloat* = float32
AnyAtomDouble* = float64
AnyAtomInt* = BiggestInt
AnyAtomString* = string
AnyAtomBytes* = seq[byte]
AnyAtomSymbol* = Symbol
AnyAtomEmbedded*[Cap] = Cap
`AnyAtom`*[Cap] {.preservesOr.} = object
`AnyAtom`* {.preservesOr.} = object
case orKind*: AnyAtomKind
of AnyAtomKind.`bool`:
`bool`*: AnyAtomBool
`bool`*: bool
of AnyAtomKind.`float`:
`float`*: AnyAtomFloat
`float`*: float32
of AnyAtomKind.`double`:
`double`*: AnyAtomDouble
`double`*: float64
of AnyAtomKind.`int`:
`int`*: AnyAtomInt
`int`*: BiggestInt
of AnyAtomKind.`string`:
`string`*: AnyAtomString
`string`*: string
of AnyAtomKind.`bytes`:
`bytes`*: AnyAtomBytes
`bytes`*: seq[byte]
of AnyAtomKind.`symbol`:
`symbol`*: AnyAtomSymbol
`symbol`*: Symbol
of AnyAtomKind.`embedded`:
`embedded`*: AnyAtomEmbedded[Cap]
`embedded`* {.preservesEmbedded.}: EmbeddedRef
DLit*[Cap] {.preservesRecord: "lit".} = object
`value`*: AnyAtom[Cap]
DLit* {.preservesRecord: "lit".} = object
`value`*: AnyAtom
DBind*[Cap] {.preservesRecord: "bind".} = ref object
`pattern`*: Pattern[Cap]
DBind* {.preservesRecord: "bind".} = object
`pattern`*: Pattern
DDiscard* {.preservesRecord: "_".} = object
DCompoundKind* {.pure.} = enum
`rec`, `arr`, `dict`
DCompoundRec*[Cap] {.preservesRecord: "rec".} = ref object
`label`*: Preserve[Cap]
`fields`*: seq[Pattern[Cap]]
DCompoundRec* {.preservesRecord: "rec".} = object
`label`*: Value
`fields`*: seq[Pattern]
DCompoundArr*[Cap] {.preservesRecord: "arr".} = ref object
`items`*: seq[Pattern[Cap]]
DCompoundArr* {.preservesRecord: "arr".} = object
`items`*: seq[Pattern]
DCompoundDict*[Cap] {.preservesRecord: "dict".} = ref object
`entries`*: Table[Preserve[Cap], Pattern[Cap]]
DCompoundDict* {.preservesRecord: "dict".} = object
`entries`*: Table[Value, Pattern]
`DCompound`*[Cap] {.preservesOr.} = ref object
`DCompound`* {.preservesOr.} = object
case orKind*: DCompoundKind
of DCompoundKind.`rec`:
`rec`*: DCompoundRec[Cap]
`rec`* {.preservesEmbedded.}: DCompoundRec
of DCompoundKind.`arr`:
`arr`*: DCompoundArr[Cap]
`arr`* {.preservesEmbedded.}: DCompoundArr
of DCompoundKind.`dict`:
`dict`*: DCompoundDict[Cap]
`dict`* {.preservesEmbedded.}: DCompoundDict
PatternKind* {.pure.} = enum
`DDiscard`, `DBind`, `DLit`, `DCompound`
`Pattern`*[Cap] {.preservesOr.} = ref object
`Pattern`* {.acyclic, preservesOr.} = ref object
case orKind*: PatternKind
of PatternKind.`DDiscard`:
`ddiscard`*: DDiscard
of PatternKind.`DBind`:
`dbind`*: DBind[Cap]
`dbind`* {.preservesEmbedded.}: DBind
of PatternKind.`DLit`:
`dlit`*: DLit[Cap]
`dlit`* {.preservesEmbedded.}: DLit
of PatternKind.`DCompound`:
`dcompound`*: DCompound[Cap]
`dcompound`* {.preservesEmbedded.}: DCompound
proc `$`*[Cap](x: AnyAtom[Cap] | DLit[Cap] | DBind[Cap] | DCompound[Cap] |
Pattern[Cap]): string =
`$`(toPreserve(x, Cap))
proc `$`*(x: AnyAtom | DLit | DBind | DDiscard | DCompound | Pattern): string =
`$`(toPreserves(x))
proc encode*[Cap](x: AnyAtom[Cap] | DLit[Cap] | DBind[Cap] | DCompound[Cap] |
Pattern[Cap]): seq[byte] =
encode(toPreserve(x, Cap))
proc `$`*(x: DDiscard): string =
`$`(toPreserve(x))
proc encode*(x: DDiscard): seq[byte] =
encode(toPreserve(x))
proc encode*(x: AnyAtom | DLit | DBind | DDiscard | DCompound | Pattern): seq[
byte] =
encode(toPreserves(x))

View File

@ -3,106 +3,101 @@ import
preserves
type
Bind*[Cap] {.preservesRecord: "bind".} = object
`description`*: Description[Cap]
`target`*: Cap
`observer`*: BindObserver[Cap]
Bind* {.preservesRecord: "bind".} = object
`description`*: Description
`target`* {.preservesEmbedded.}: EmbeddedRef
`observer`*: BindObserver
Route*[Cap] {.preservesRecord: "route".} = object
`transports`*: seq[Preserve[Cap]]
`pathSteps`* {.preservesTupleTail.}: seq[PathStep[Cap]]
Route* {.preservesRecord: "route".} = object
`transports`*: seq[Value]
`pathSteps`* {.preservesTupleTail.}: seq[PathStep]
BindObserverKind* {.pure.} = enum
`present`, `absent`
BindObserverPresent*[Cap] = Cap
`BindObserver`*[Cap] {.preservesOr.} = object
`BindObserver`* {.preservesOr.} = object
case orKind*: BindObserverKind
of BindObserverKind.`present`:
`present`*: BindObserverPresent[Cap]
`present`* {.preservesEmbedded.}: EmbeddedRef
of BindObserverKind.`absent`:
`absent`* {.preservesLiteral: "#f".}: bool
TransportConnection*[Cap] {.preservesRecord: "connect-transport".} = object
`addr`*: Preserve[Cap]
`control`*: Cap
`resolved`*: Resolved[Cap]
TransportConnection* {.preservesRecord: "connect-transport".} = object
`addr`*: Value
`control`* {.preservesEmbedded.}: EmbeddedRef
`resolved`*: Resolved
Step*[Cap] = Preserve[Cap]
ResolvedPathStep*[Cap] {.preservesRecord: "path-step".} = object
`origin`*: Cap
`pathStep`*: PathStep[Cap]
`resolved`*: Resolved[Cap]
Step* = Value
ResolvedPathStep* {.preservesRecord: "path-step".} = object
`origin`* {.preservesEmbedded.}: EmbeddedRef
`pathStep`*: PathStep
`resolved`*: Resolved
BoundKind* {.pure.} = enum
`bound`, `Rejected`
BoundBound*[Cap] {.preservesRecord: "bound".} = object
`pathStep`*: PathStep[Cap]
BoundBound* {.preservesRecord: "bound".} = object
`pathStep`*: PathStep
`Bound`*[Cap] {.preservesOr.} = object
`Bound`* {.preservesOr.} = object
case orKind*: BoundKind
of BoundKind.`bound`:
`bound`*: BoundBound[Cap]
`bound`*: BoundBound
of BoundKind.`Rejected`:
`rejected`*: Rejected[Cap]
`rejected`*: Rejected
ForceDisconnect* {.preservesRecord: "force-disconnect".} = object
Description*[Cap] = Preserve[Cap]
Rejected*[Cap] {.preservesRecord: "rejected".} = object
`detail`*: Preserve[Cap]
Description* = Value
Rejected* {.preservesRecord: "rejected".} = object
`detail`*: Value
Resolve*[Cap] {.preservesRecord: "resolve".} = object
`step`*: Step[Cap]
`observer`*: Cap
Resolve* {.preservesRecord: "resolve".} = object
`step`*: Step
`observer`* {.preservesEmbedded.}: EmbeddedRef
ResolvedKind* {.pure.} = enum
`accepted`, `Rejected`
ResolvedAccepted*[Cap] {.preservesRecord: "accepted".} = object
`responderSession`*: Cap
ResolvedAccepted* {.preservesRecord: "accepted".} = object
`responderSession`* {.preservesEmbedded.}: EmbeddedRef
`Resolved`*[Cap] {.preservesOr.} = object
`Resolved`* {.preservesOr.} = object
case orKind*: ResolvedKind
of ResolvedKind.`accepted`:
`accepted`*: ResolvedAccepted[Cap]
`accepted`* {.preservesEmbedded.}: ResolvedAccepted
of ResolvedKind.`Rejected`:
`rejected`*: Rejected[Cap]
`rejected`*: Rejected
TransportControl* = ForceDisconnect
ResolvePath*[Cap] {.preservesRecord: "resolve-path".} = object
`route`*: Route[Cap]
`addr`*: Preserve[Cap]
`control`*: Cap
`resolved`*: Resolved[Cap]
ResolvePath* {.preservesRecord: "resolve-path".} = object
`route`*: Route
`addr`*: Value
`control`* {.preservesEmbedded.}: EmbeddedRef
`resolved`*: Resolved
PathStep*[Cap] = Preserve[Cap]
proc `$`*[Cap](x: Bind[Cap] | Route[Cap] | BindObserver[Cap] |
TransportConnection[Cap] |
ResolvedPathStep[Cap] |
Bound[Cap] |
Rejected[Cap] |
Resolve[Cap] |
Resolved[Cap] |
ResolvePath[Cap]): string =
`$`(toPreserve(x, Cap))
PathStep* = Value
proc `$`*(x: Bind | Route | BindObserver | TransportConnection |
ResolvedPathStep |
Bound |
ForceDisconnect |
Rejected |
Resolve |
Resolved |
TransportControl |
ResolvePath): string =
`$`(toPreserves(x))
proc encode*[Cap](x: Bind[Cap] | Route[Cap] | BindObserver[Cap] |
TransportConnection[Cap] |
ResolvedPathStep[Cap] |
Bound[Cap] |
Rejected[Cap] |
Resolve[Cap] |
Resolved[Cap] |
ResolvePath[Cap]): seq[byte] =
encode(toPreserve(x, Cap))
proc `$`*(x: ForceDisconnect | TransportControl): string =
`$`(toPreserve(x))
proc encode*(x: ForceDisconnect | TransportControl): seq[byte] =
encode(toPreserve(x))
proc encode*(x: Bind | Route | BindObserver | TransportConnection |
ResolvedPathStep |
Bound |
ForceDisconnect |
Rejected |
Resolve |
Resolved |
TransportControl |
ResolvePath): seq[byte] =
encode(toPreserves(x))

View File

@ -0,0 +1,169 @@
import
preserves, std/tables
type
HostPatternKind* {.pure.} = enum
`host`, `any`
`HostPattern`* {.preservesOr.} = object
case orKind*: HostPatternKind
of HostPatternKind.`host`:
`host`*: string
of HostPatternKind.`any`:
`any`* {.preservesLiteral: "#f".}: bool
HttpListener* {.preservesRecord: "http-listener".} = object
`port`*: BiggestInt
MethodPatternKind* {.pure.} = enum
`any`, `specific`
`MethodPattern`* {.preservesOr.} = object
case orKind*: MethodPatternKind
of MethodPatternKind.`any`:
`any`* {.preservesLiteral: "#f".}: bool
of MethodPatternKind.`specific`:
`specific`*: Symbol
MimeType* = Symbol
QueryValueKind* {.pure.} = enum
`string`, `file`
QueryValueFile* {.preservesRecord: "file".} = object
`filename`*: string
`headers`*: Headers
`body`*: seq[byte]
`QueryValue`* {.preservesOr.} = object
case orKind*: QueryValueKind
of QueryValueKind.`string`:
`string`*: string
of QueryValueKind.`file`:
`file`*: QueryValueFile
HttpRequest* {.preservesRecord: "http-request".} = object
`sequenceNumber`*: BiggestInt
`host`*: string
`port`*: BiggestInt
`method`*: Symbol
`path`*: seq[string]
`headers`*: Headers
`query`*: Table[Symbol, seq[QueryValue]]
`body`*: RequestBody
RequestBodyKind* {.pure.} = enum
`present`, `absent`
`RequestBody`* {.preservesOr.} = object
case orKind*: RequestBodyKind
of RequestBodyKind.`present`:
`present`*: seq[byte]
of RequestBodyKind.`absent`:
`absent`* {.preservesLiteral: "#f".}: bool
Headers* = Table[Symbol, string]
HttpResponseKind* {.pure.} = enum
`status`, `header`, `chunk`, `done`
HttpResponseStatus* {.preservesRecord: "status".} = object
`code`*: BiggestInt
`message`*: string
HttpResponseHeader* {.preservesRecord: "header".} = object
`name`*: Symbol
`value`*: string
HttpResponseChunk* {.preservesRecord: "chunk".} = object
`chunk`*: Chunk
HttpResponseDone* {.preservesRecord: "done".} = object
`chunk`*: Chunk
`HttpResponse`* {.preservesOr.} = object
case orKind*: HttpResponseKind
of HttpResponseKind.`status`:
`status`*: HttpResponseStatus
of HttpResponseKind.`header`:
`header`*: HttpResponseHeader
of HttpResponseKind.`chunk`:
`chunk`*: HttpResponseChunk
of HttpResponseKind.`done`:
`done`*: HttpResponseDone
HttpService* {.preservesRecord: "http-service".} = object
`host`*: HostPattern
`port`*: BiggestInt
`method`*: MethodPattern
`path`*: PathPattern
HttpBinding* {.preservesRecord: "http-bind".} = object
`host`*: HostPattern
`port`*: BiggestInt
`method`*: MethodPattern
`path`*: PathPattern
`handler`* {.preservesEmbedded.}: Value
HttpContext* {.preservesRecord: "request".} = object
`req`*: HttpRequest
`res`* {.preservesEmbedded.}: Value
PathPatternElementKind* {.pure.} = enum
`label`, `wildcard`, `rest`
`PathPatternElement`* {.preservesOr.} = object
case orKind*: PathPatternElementKind
of PathPatternElementKind.`label`:
`label`*: string
of PathPatternElementKind.`wildcard`:
`wildcard`* {.preservesLiteral: "_".}: bool
of PathPatternElementKind.`rest`:
`rest`* {.preservesLiteral: "|...|".}: bool
ChunkKind* {.pure.} = enum
`string`, `bytes`
`Chunk`* {.preservesOr.} = object
case orKind*: ChunkKind
of ChunkKind.`string`:
`string`*: string
of ChunkKind.`bytes`:
`bytes`*: seq[byte]
PathPattern* = seq[PathPatternElement]
proc `$`*(x: HostPattern | HttpListener | MethodPattern | MimeType | QueryValue |
HttpRequest |
RequestBody |
Headers |
HttpResponse |
HttpService |
HttpBinding |
HttpContext |
PathPatternElement |
Chunk |
PathPattern): string =
`$`(toPreserves(x))
proc encode*(x: HostPattern | HttpListener | MethodPattern | MimeType |
QueryValue |
HttpRequest |
RequestBody |
Headers |
HttpResponse |
HttpService |
HttpBinding |
HttpContext |
PathPatternElement |
Chunk |
PathPattern): seq[byte] =
encode(toPreserves(x))

View File

@ -0,0 +1,121 @@
import
preserves, std/options
type
NoiseDescriptionDetail* = NoiseServiceSpec
NoisePreSharedKeysKind* {.pure.} = enum
`present`, `invalid`, `absent`
NoisePreSharedKeysPresent* {.preservesDictionary.} = object
`preSharedKeys`*: seq[seq[byte]]
NoisePreSharedKeysInvalid* {.preservesDictionary.} = object
`preSharedKeys`*: Value
NoisePreSharedKeysAbsent* {.preservesDictionary.} = object
`NoisePreSharedKeys`* {.preservesOr.} = object
case orKind*: NoisePreSharedKeysKind
of NoisePreSharedKeysKind.`present`:
`present`*: NoisePreSharedKeysPresent
of NoisePreSharedKeysKind.`invalid`:
`invalid`*: NoisePreSharedKeysInvalid
of NoisePreSharedKeysKind.`absent`:
`absent`*: NoisePreSharedKeysAbsent
SecretKeyFieldKind* {.pure.} = enum
`present`, `invalid`, `absent`
SecretKeyFieldPresent* {.preservesDictionary.} = object
`secretKey`*: seq[byte]
SecretKeyFieldInvalid* {.preservesDictionary.} = object
`secretKey`*: Value
SecretKeyFieldAbsent* {.preservesDictionary.} = object
`SecretKeyField`* {.preservesOr.} = object
case orKind*: SecretKeyFieldKind
of SecretKeyFieldKind.`present`:
`present`*: SecretKeyFieldPresent
of SecretKeyFieldKind.`invalid`:
`invalid`*: SecretKeyFieldInvalid
of SecretKeyFieldKind.`absent`:
`absent`*: SecretKeyFieldAbsent
NoiseProtocolKind* {.pure.} = enum
`present`, `invalid`, `absent`
NoiseProtocolPresent* {.preservesDictionary.} = object
`protocol`*: string
NoiseProtocolInvalid* {.preservesDictionary.} = object
`protocol`*: Value
NoiseProtocolAbsent* {.preservesDictionary.} = object
`NoiseProtocol`* {.preservesOr.} = object
case orKind*: NoiseProtocolKind
of NoiseProtocolKind.`present`:
`present`*: NoiseProtocolPresent
of NoiseProtocolKind.`invalid`:
`invalid`*: NoiseProtocolInvalid
of NoiseProtocolKind.`absent`:
`absent`*: NoiseProtocolAbsent
NoisePathStepDetail* = NoiseSpec
NoiseServiceSpecKey* = seq[byte]
NoiseServiceSpecPreSharedKeys* = Option[Value]
NoiseServiceSpecProtocol* = Option[Value]
NoiseServiceSpecSecretKey* = Option[Value]
`NoiseServiceSpec`* {.preservesDictionary.} = object
`key`*: seq[byte]
`preSharedKeys`*: Option[Value]
`protocol`*: Option[Value]
`secretKey`*: Option[Value]
`service`*: ServiceSelector
ServiceSelector* = Value
NoiseStepDetail* = ServiceSelector
NoiseSpecKey* = seq[byte]
NoiseSpecPreSharedKeys* = Option[Value]
NoiseSpecProtocol* = Option[Value]
`NoiseSpec`* {.preservesDictionary.} = object
`key`*: seq[byte]
`preSharedKeys`*: Option[Value]
`protocol`*: Option[Value]
`service`*: ServiceSelector
PacketKind* {.pure.} = enum
`complete`, `fragmented`
`Packet`* {.preservesOr.} = object
case orKind*: PacketKind
of PacketKind.`complete`:
`complete`*: seq[byte]
of PacketKind.`fragmented`:
`fragmented`*: seq[seq[byte]]
proc `$`*(x: NoiseDescriptionDetail | NoisePreSharedKeys | SecretKeyField |
NoiseProtocol |
NoisePathStepDetail |
NoiseServiceSpec |
NoiseSpec |
Packet): string =
`$`(toPreserves(x))
proc encode*(x: NoiseDescriptionDetail | NoisePreSharedKeys | SecretKeyField |
NoiseProtocol |
NoisePathStepDetail |
NoiseServiceSpec |
NoiseSpec |
Packet): seq[byte] =
encode(toPreserves(x))

View File

@ -5,7 +5,7 @@ import
type
Error* {.preservesRecord: "error".} = object
`message`*: string
`detail`*: Preserve[void]
`detail`*: Value
Turn* = seq[TurnEvent]
Message* {.preservesRecord: "message".} = object
@ -18,23 +18,23 @@ type
`assertion`*: Assertion
`handle`*: Handle
Extension* = Preserve[void]
Extension* = Value
Sync* {.preservesRecord: "sync".} = object
`peer`* {.preservesLiteral: "#!<lit #t>".}: tuple[]
`peer`* {.preservesEmbedded.}: Value
TurnEvent* {.preservesTuple.} = object
`oid`*: Oid
`event`*: Event
Oid* = BiggestInt
Assertion* = Preserve[void]
Assertion* = Value
Handle* = BiggestInt
PacketKind* {.pure.} = enum
`Turn`, `Error`, `Extension`
`Packet`* {.preservesOr.} = object
case orKind*: PacketKind
of PacketKind.`Turn`:
`turn`*: Turn
`turn`* {.preservesEmbedded.}: Turn
of PacketKind.`Error`:
`error`*: Error
@ -57,18 +57,18 @@ type
`message`*: Message
of EventKind.`Sync`:
`sync`*: Sync
`sync`* {.preservesEmbedded.}: Sync
proc `$`*(x: Error | Turn | Message | Retract | Assert | Sync | TurnEvent | Oid |
Handle |
Packet |
Event): string =
`$`(toPreserve(x))
`$`(toPreserves(x))
proc encode*(x: Error | Turn | Message | Retract | Assert | Sync | TurnEvent |
Oid |
Handle |
Packet |
Event): seq[byte] =
encode(toPreserve(x))
encode(toPreserves(x))

View File

@ -5,8 +5,7 @@ import
type
StateKind* {.pure.} = enum
`started`, `ready`, `failed`, `complete`, `userDefined`
StateUserDefined*[Cap] = Preserve[Cap]
`State`*[Cap] {.preservesOr.} = object
`State`* {.preservesOr.} = object
case orKind*: StateKind
of StateKind.`started`:
`started`* {.preservesLiteral: "started".}: bool
@ -21,40 +20,38 @@ type
`complete`* {.preservesLiteral: "complete".}: bool
of StateKind.`userDefined`:
`userdefined`*: StateUserDefined[Cap]
`userdefined`*: Value
ServiceObject*[Cap] {.preservesRecord: "service-object".} = object
`serviceName`*: Preserve[Cap]
`object`*: Preserve[Cap]
ServiceObject* {.preservesRecord: "service-object".} = object
`serviceName`*: Value
`object`*: Value
RequireService*[Cap] {.preservesRecord: "require-service".} = object
`serviceName`*: Preserve[Cap]
RequireService* {.preservesRecord: "require-service".} = object
`serviceName`*: Value
RestartService*[Cap] {.preservesRecord: "restart-service".} = object
`serviceName`*: Preserve[Cap]
RestartService* {.preservesRecord: "restart-service".} = object
`serviceName`*: Value
RunService*[Cap] {.preservesRecord: "run-service".} = object
`serviceName`*: Preserve[Cap]
RunService* {.preservesRecord: "run-service".} = object
`serviceName`*: Value
ServiceState*[Cap] {.preservesRecord: "service-state".} = object
`serviceName`*: Preserve[Cap]
`state`*: State[Cap]
ServiceState* {.preservesRecord: "service-state".} = object
`serviceName`*: Value
`state`*: State
ServiceDependency*[Cap] {.preservesRecord: "depends-on".} = object
`depender`*: Preserve[Cap]
`dependee`*: ServiceState[Cap]
ServiceDependency* {.preservesRecord: "depends-on".} = object
`depender`*: Value
`dependee`*: ServiceState
proc `$`*[Cap](x: State[Cap] | ServiceObject[Cap] | RequireService[Cap] |
RestartService[Cap] |
RunService[Cap] |
ServiceState[Cap] |
ServiceDependency[Cap]): string =
`$`(toPreserve(x, Cap))
proc `$`*(x: State | ServiceObject | RequireService | RestartService |
RunService |
ServiceState |
ServiceDependency): string =
`$`(toPreserves(x))
proc encode*[Cap](x: State[Cap] | ServiceObject[Cap] | RequireService[Cap] |
RestartService[Cap] |
RunService[Cap] |
ServiceState[Cap] |
ServiceDependency[Cap]): seq[byte] =
encode(toPreserve(x, Cap))
proc encode*(x: State | ServiceObject | RequireService | RestartService |
RunService |
ServiceState |
ServiceDependency): seq[byte] =
encode(toPreserves(x))

View File

@ -5,11 +5,10 @@ import
type
CreditAmountKind* {.pure.} = enum
`count`, `unbounded`
CreditAmountCount* = BiggestInt
`CreditAmount`* {.preservesOr.} = object
case orKind*: CreditAmountKind
of CreditAmountKind.`count`:
`count`*: CreditAmountCount
`count`*: BiggestInt
of CreditAmountKind.`unbounded`:
`unbounded`* {.preservesLiteral: "unbounded".}: bool
@ -18,76 +17,76 @@ type
StreamError* {.preservesRecord: "error".} = object
`message`*: string
StreamListenerError*[Cap] {.preservesRecord: "stream-listener-error".} = object
`spec`*: Preserve[Cap]
StreamListenerError* {.preservesRecord: "stream-listener-error".} = object
`spec`*: Value
`message`*: string
StreamConnection*[Cap] {.preservesRecord: "stream-connection".} = object
`source`*: Cap
`sink`*: Cap
`spec`*: Preserve[Cap]
StreamConnection* {.preservesRecord: "stream-connection".} = object
`source`* {.preservesEmbedded.}: EmbeddedRef
`sink`* {.preservesEmbedded.}: EmbeddedRef
`spec`*: Value
`LineMode`* {.preservesOr, pure.} = enum
`lf`, `crlf`
SourceKind* {.pure.} = enum
`sink`, `StreamError`, `credit`
SourceSink*[Cap] {.preservesRecord: "sink".} = object
`controller`*: Cap
SourceSink* {.preservesRecord: "sink".} = object
`controller`* {.preservesEmbedded.}: EmbeddedRef
SourceCredit*[Cap] {.preservesRecord: "credit".} = object
SourceCredit* {.preservesRecord: "credit".} = object
`amount`*: CreditAmount
`mode`*: Mode[Cap]
`mode`*: Mode
`Source`*[Cap] {.preservesOr.} = object
`Source`* {.acyclic, preservesOr.} = ref object
case orKind*: SourceKind
of SourceKind.`sink`:
`sink`*: SourceSink[Cap]
`sink`* {.preservesEmbedded.}: SourceSink
of SourceKind.`StreamError`:
`streamerror`*: StreamError
of SourceKind.`credit`:
`credit`*: SourceCredit[Cap]
`credit`*: SourceCredit
SinkKind* {.pure.} = enum
`source`, `StreamError`, `data`, `eof`
SinkSource*[Cap] {.preservesRecord: "source".} = object
`controller`*: Cap
SinkSource* {.preservesRecord: "source".} = object
`controller`* {.preservesEmbedded.}: EmbeddedRef
SinkData*[Cap] {.preservesRecord: "data".} = object
`payload`*: Preserve[Cap]
`mode`*: Mode[Cap]
SinkData* {.preservesRecord: "data".} = object
`payload`*: Value
`mode`*: Mode
SinkEof* {.preservesRecord: "eof".} = object
`Sink`*[Cap] {.preservesOr.} = object
`Sink`* {.acyclic, preservesOr.} = ref object
case orKind*: SinkKind
of SinkKind.`source`:
`source`*: SinkSource[Cap]
`source`* {.preservesEmbedded.}: SinkSource
of SinkKind.`StreamError`:
`streamerror`*: StreamError
of SinkKind.`data`:
`data`*: SinkData[Cap]
`data`*: SinkData
of SinkKind.`eof`:
`eof`*: SinkEof
StreamListenerReady*[Cap] {.preservesRecord: "stream-listener-ready".} = object
`spec`*: Preserve[Cap]
StreamListenerReady* {.preservesRecord: "stream-listener-ready".} = object
`spec`*: Value
ModeKind* {.pure.} = enum
`bytes`, `lines`, `packet`, `object`
ModePacket* {.preservesRecord: "packet".} = object
`size`*: BiggestInt
ModeObject*[Cap] {.preservesRecord: "object".} = object
`description`*: Preserve[Cap]
ModeObject* {.preservesRecord: "object".} = object
`description`*: Value
`Mode`*[Cap] {.preservesOr.} = object
`Mode`* {.preservesOr.} = object
case orKind*: ModeKind
of ModeKind.`bytes`:
`bytes`* {.preservesLiteral: "bytes".}: bool
@ -99,24 +98,20 @@ type
`packet`*: ModePacket
of ModeKind.`object`:
`object`*: ModeObject[Cap]
`object`*: ModeObject
proc `$`*[Cap](x: StreamListenerError[Cap] | StreamConnection[Cap] | Source[Cap] |
Sink[Cap] |
StreamListenerReady[Cap] |
Mode[Cap]): string =
`$`(toPreserve(x, Cap))
proc `$`*(x: CreditAmount | StreamError | StreamListenerError | StreamConnection |
Source |
Sink |
StreamListenerReady |
Mode): string =
`$`(toPreserves(x))
proc encode*[Cap](x: StreamListenerError[Cap] | StreamConnection[Cap] |
Source[Cap] |
Sink[Cap] |
StreamListenerReady[Cap] |
Mode[Cap]): seq[byte] =
encode(toPreserve(x, Cap))
proc `$`*(x: CreditAmount | StreamError): string =
`$`(toPreserve(x))
proc encode*(x: CreditAmount | StreamError): seq[byte] =
encode(toPreserve(x))
proc encode*(x: CreditAmount | StreamError | StreamListenerError |
StreamConnection |
Source |
Sink |
StreamListenerReady |
Mode): seq[byte] =
encode(toPreserves(x))

View File

@ -1,104 +1,111 @@
import
preserves, std/tables
preserves, std/tables, std/options
type
PCompoundKind* {.pure.} = enum
`rec`, `arr`, `dict`
PCompoundRec*[Cap] {.preservesRecord: "rec".} = ref object
`label`*: Preserve[Cap]
`fields`*: seq[Pattern[Cap]]
PCompoundRec* {.preservesRecord: "rec".} = object
`label`*: Value
`fields`*: seq[Pattern]
PCompoundArr*[Cap] {.preservesRecord: "arr".} = ref object
`items`*: seq[Pattern[Cap]]
PCompoundArr* {.preservesRecord: "arr".} = object
`items`*: seq[Pattern]
PCompoundDict*[Cap] {.preservesRecord: "dict".} = ref object
`entries`*: Table[Preserve[Cap], Pattern[Cap]]
PCompoundDict* {.preservesRecord: "dict".} = object
`entries`*: Table[Value, Pattern]
`PCompound`*[Cap] {.preservesOr.} = ref object
`PCompound`* {.preservesOr.} = object
case orKind*: PCompoundKind
of PCompoundKind.`rec`:
`rec`*: PCompoundRec[Cap]
`rec`*: PCompoundRec
of PCompoundKind.`arr`:
`arr`*: PCompoundArr[Cap]
`arr`*: PCompoundArr
of PCompoundKind.`dict`:
`dict`*: PCompoundDict[Cap]
`dict`*: PCompoundDict
Reject*[Cap] {.preservesRecord: "reject".} = ref object
`pattern`*: Pattern[Cap]
Reject* {.preservesRecord: "reject".} = object
`pattern`*: Pattern
CaveatsFieldKind* {.pure.} = enum
`present`, `invalid`, `absent`
CaveatsFieldPresent*[Cap] {.preservesDictionary.} = ref object
`caveats`*: seq[Caveat[Cap]]
CaveatsFieldPresent* {.preservesDictionary.} = object
`caveats`*: seq[Caveat]
CaveatsFieldInvalid*[Cap] {.preservesDictionary.} = object
`caveats`*: Preserve[Cap]
CaveatsFieldInvalid* {.preservesDictionary.} = object
`caveats`*: Value
CaveatsFieldAbsent* {.preservesDictionary.} = object
`CaveatsField`*[Cap] {.preservesOr.} = ref object
`CaveatsField`* {.preservesOr.} = object
case orKind*: CaveatsFieldKind
of CaveatsFieldKind.`present`:
`present`*: CaveatsFieldPresent[Cap]
`present`*: CaveatsFieldPresent
of CaveatsFieldKind.`invalid`:
`invalid`*: CaveatsFieldInvalid[Cap]
`invalid`*: CaveatsFieldInvalid
of CaveatsFieldKind.`absent`:
`absent`*: CaveatsFieldAbsent
SturdyDescriptionDetail*[Cap] {.preservesDictionary.} = object
`oid`*: Preserve[Cap]
SturdyDescriptionDetail* {.preservesDictionary.} = object
`key`*: seq[byte]
`oid`*: Value
PAnd*[Cap] {.preservesRecord: "and".} = ref object
`patterns`*: seq[Pattern[Cap]]
PAnd* {.preservesRecord: "and".} = object
`patterns`*: seq[Pattern]
SturdyStepDetail*[Cap] = Parameters[Cap]
Rewrite*[Cap] {.preservesRecord: "rewrite".} = ref object
`pattern`*: Pattern[Cap]
`template`*: Template[Cap]
SturdyStepDetail* = Parameters
Rewrite* {.preservesRecord: "rewrite".} = object
`pattern`*: Pattern
`template`*: Template
ParametersCaveats* = Option[Value]
ParametersOid* = Value
ParametersSig* = seq[byte]
`Parameters`* {.preservesDictionary.} = object
`caveats`*: Option[Value]
`oid`*: Value
`sig`*: seq[byte]
Parameters*[Cap] = Preserve[Cap]
TRef* {.preservesRecord: "ref".} = object
`binding`*: BiggestInt
PBind*[Cap] {.preservesRecord: "bind".} = ref object
`pattern`*: Pattern[Cap]
PBind* {.preservesRecord: "bind".} = object
`pattern`*: Pattern
Lit*[Cap] {.preservesRecord: "lit".} = object
`value`*: Preserve[Cap]
Lit* {.preservesRecord: "lit".} = object
`value`*: Value
TCompoundKind* {.pure.} = enum
`rec`, `arr`, `dict`
TCompoundRec*[Cap] {.preservesRecord: "rec".} = ref object
`label`*: Preserve[Cap]
`fields`*: seq[Template[Cap]]
TCompoundRec* {.preservesRecord: "rec".} = object
`label`*: Value
`fields`*: seq[Template]
TCompoundArr*[Cap] {.preservesRecord: "arr".} = ref object
`items`*: seq[Template[Cap]]
TCompoundArr* {.preservesRecord: "arr".} = object
`items`*: seq[Template]
TCompoundDict*[Cap] {.preservesRecord: "dict".} = ref object
`entries`*: Table[Preserve[Cap], Template[Cap]]
TCompoundDict* {.preservesRecord: "dict".} = object
`entries`*: Table[Value, Template]
`TCompound`*[Cap] {.preservesOr.} = ref object
`TCompound`* {.preservesOr.} = object
case orKind*: TCompoundKind
of TCompoundKind.`rec`:
`rec`*: TCompoundRec[Cap]
`rec`*: TCompoundRec
of TCompoundKind.`arr`:
`arr`*: TCompoundArr[Cap]
`arr`*: TCompoundArr
of TCompoundKind.`dict`:
`dict`*: TCompoundDict[Cap]
`dict`*: TCompoundDict
SturdyPathStepDetail*[Cap] = Parameters[Cap]
SturdyPathStepDetail* = Parameters
`PAtom`* {.preservesOr, pure.} = enum
`Boolean`, `Float`, `Double`, `SignedInteger`, `String`, `ByteString`,
`Symbol`
@ -106,44 +113,43 @@ type
TemplateKind* {.pure.} = enum
`TAttenuate`, `TRef`, `Lit`, `TCompound`
`Template`*[Cap] {.preservesOr.} = ref object
`Template`* {.acyclic, preservesOr.} = ref object
case orKind*: TemplateKind
of TemplateKind.`TAttenuate`:
`tattenuate`*: TAttenuate[Cap]
`tattenuate`*: TAttenuate
of TemplateKind.`TRef`:
`tref`*: TRef
of TemplateKind.`Lit`:
`lit`*: Lit[Cap]
`lit`*: Lit
of TemplateKind.`TCompound`:
`tcompound`*: TCompound[Cap]
`tcompound`*: TCompound
CaveatKind* {.pure.} = enum
`Rewrite`, `Alts`, `Reject`, `unknown`
CaveatUnknown*[Cap] = Preserve[Cap]
`Caveat`*[Cap] {.preservesOr.} = ref object
`Caveat`* {.preservesOr.} = object
case orKind*: CaveatKind
of CaveatKind.`Rewrite`:
`rewrite`*: Rewrite[Cap]
`rewrite`*: Rewrite
of CaveatKind.`Alts`:
`alts`*: Alts[Cap]
`alts`*: Alts
of CaveatKind.`Reject`:
`reject`*: Reject[Cap]
`reject`*: Reject
of CaveatKind.`unknown`:
`unknown`*: CaveatUnknown[Cap]
`unknown`*: Value
PNot*[Cap] {.preservesRecord: "not".} = ref object
`pattern`*: Pattern[Cap]
PNot* {.preservesRecord: "not".} = object
`pattern`*: Pattern
SturdyRef*[Cap] {.preservesRecord: "ref".} = ref object
`parameters`*: Parameters[Cap]
SturdyRef* {.preservesRecord: "ref".} = object
`parameters`*: Parameters
WireRefKind* {.pure.} = enum
`mine`, `yours`
@ -151,32 +157,32 @@ type
`field0`* {.preservesLiteral: "0".}: tuple[]
`oid`*: Oid
WireRefYours*[Cap] {.preservesTuple.} = ref object
WireRefYours* {.preservesTuple.} = object
`field0`* {.preservesLiteral: "1".}: tuple[]
`oid`*: Oid
`attenuation`* {.preservesTupleTail.}: seq[Caveat[Cap]]
`attenuation`* {.preservesTupleTail.}: seq[Caveat]
`WireRef`*[Cap] {.preservesOr.} = ref object
`WireRef`* {.preservesOr.} = object
case orKind*: WireRefKind
of WireRefKind.`mine`:
`mine`*: WireRefMine
of WireRefKind.`yours`:
`yours`*: WireRefYours[Cap]
`yours`*: WireRefYours
TAttenuate*[Cap] {.preservesRecord: "attenuate".} = ref object
`template`*: Template[Cap]
`attenuation`*: seq[Caveat[Cap]]
TAttenuate* {.preservesRecord: "attenuate".} = object
`template`*: Template
`attenuation`*: seq[Caveat]
Oid* = BiggestInt
Alts*[Cap] {.preservesRecord: "or".} = ref object
`alternatives`*: seq[Rewrite[Cap]]
Alts* {.preservesRecord: "or".} = object
`alternatives`*: seq[Rewrite]
PatternKind* {.pure.} = enum
`PDiscard`, `PAtom`, `PEmbedded`, `PBind`, `PAnd`, `PNot`, `Lit`,
`PCompound`
`Pattern`*[Cap] {.preservesOr.} = ref object
`Pattern`* {.acyclic, preservesOr.} = ref object
case orKind*: PatternKind
of PatternKind.`PDiscard`:
`pdiscard`*: PDiscard
@ -188,57 +194,60 @@ type
`pembedded`* {.preservesLiteral: "Embedded".}: bool
of PatternKind.`PBind`:
`pbind`*: PBind[Cap]
`pbind`*: PBind
of PatternKind.`PAnd`:
`pand`*: PAnd[Cap]
`pand`*: PAnd
of PatternKind.`PNot`:
`pnot`*: PNot[Cap]
`pnot`*: PNot
of PatternKind.`Lit`:
`lit`*: Lit[Cap]
`lit`*: Lit
of PatternKind.`PCompound`:
`pcompound`*: PCompound[Cap]
`pcompound`*: PCompound
proc `$`*[Cap](x: PCompound[Cap] | Reject[Cap] | CaveatsField[Cap] |
SturdyDescriptionDetail[Cap] |
PAnd[Cap] |
Rewrite[Cap] |
PBind[Cap] |
Lit[Cap] |
TCompound[Cap] |
Template[Cap] |
Caveat[Cap] |
PNot[Cap] |
SturdyRef[Cap] |
WireRef[Cap] |
TAttenuate[Cap] |
Alts[Cap] |
Pattern[Cap]): string =
`$`(toPreserve(x, Cap))
proc `$`*(x: PCompound | Reject | CaveatsField | SturdyDescriptionDetail | PAnd |
SturdyStepDetail |
Rewrite |
Parameters |
TRef |
PBind |
Lit |
TCompound |
SturdyPathStepDetail |
PDiscard |
Template |
Caveat |
PNot |
SturdyRef |
WireRef |
TAttenuate |
Oid |
Alts |
Pattern): string =
`$`(toPreserves(x))
proc encode*[Cap](x: PCompound[Cap] | Reject[Cap] | CaveatsField[Cap] |
SturdyDescriptionDetail[Cap] |
PAnd[Cap] |
Rewrite[Cap] |
PBind[Cap] |
Lit[Cap] |
TCompound[Cap] |
Template[Cap] |
Caveat[Cap] |
PNot[Cap] |
SturdyRef[Cap] |
WireRef[Cap] |
TAttenuate[Cap] |
Alts[Cap] |
Pattern[Cap]): seq[byte] =
encode(toPreserve(x, Cap))
proc `$`*(x: TRef | PDiscard | Oid): string =
`$`(toPreserve(x))
proc encode*(x: TRef | PDiscard | Oid): seq[byte] =
encode(toPreserve(x))
proc encode*(x: PCompound | Reject | CaveatsField | SturdyDescriptionDetail |
PAnd |
SturdyStepDetail |
Rewrite |
Parameters |
TRef |
PBind |
Lit |
TCompound |
SturdyPathStepDetail |
PDiscard |
Template |
Caveat |
PNot |
SturdyRef |
WireRef |
TAttenuate |
Oid |
Alts |
Pattern): seq[byte] =
encode(toPreserves(x))

View File

@ -7,8 +7,8 @@ type
`host`*: string
`port`*: BiggestInt
TcpPeerInfo*[Cap] {.preservesRecord: "tcp-peer".} = object
`handle`*: Cap
TcpPeerInfo* {.preservesRecord: "tcp-peer".} = object
`handle`* {.preservesEmbedded.}: EmbeddedRef
`local`*: TcpLocal
`remote`*: TcpRemote
@ -16,14 +16,8 @@ type
`host`*: string
`port`*: BiggestInt
proc `$`*[Cap](x: TcpPeerInfo[Cap]): string =
`$`(toPreserve(x, Cap))
proc `$`*(x: TcpLocal | TcpPeerInfo | TcpRemote): string =
`$`(toPreserves(x))
proc encode*[Cap](x: TcpPeerInfo[Cap]): seq[byte] =
encode(toPreserve(x, Cap))
proc `$`*(x: TcpLocal | TcpRemote): string =
`$`(toPreserve(x))
proc encode*(x: TcpLocal | TcpRemote): seq[byte] =
encode(toPreserve(x))
proc encode*(x: TcpLocal | TcpPeerInfo | TcpRemote): seq[byte] =
encode(toPreserves(x))

View File

@ -4,11 +4,11 @@ import
type
TimerExpired* {.preservesRecord: "timer-expired".} = object
`label`*: Preserve[void]
`label`*: Value
`seconds`*: float64
SetTimer* {.preservesRecord: "set-timer".} = object
`label`*: Preserve[void]
`label`*: Value
`seconds`*: float64
`kind`*: TimerKind
@ -18,7 +18,7 @@ type
`seconds`*: float64
proc `$`*(x: TimerExpired | SetTimer | LaterThan): string =
`$`(toPreserve(x))
`$`(toPreserves(x))
proc encode*(x: TimerExpired | SetTimer | LaterThan): seq[byte] =
encode(toPreserve(x))
encode(toPreserves(x))

View File

@ -3,160 +3,160 @@ import
preserves, protocol
type
TargetedTurnEvent*[Cap] {.preservesRecord: "event".} = object
`target`*: Target[Cap]
`detail`*: TurnEvent[Cap]
TargetedTurnEvent* {.preservesRecord: "event".} = object
`target`*: Target
`detail`*: TurnEvent
`LinkedTaskReleaseReason`* {.preservesOr, pure.} = enum
`cancelled`, `normal`
TurnId*[Cap] = Preserve[Cap]
TurnId* = Value
AssertionDescriptionKind* {.pure.} = enum
`value`, `opaque`
AssertionDescriptionValue*[Cap] {.preservesRecord: "value".} = object
`value`*: Preserve[Cap]
AssertionDescriptionValue* {.preservesRecord: "value".} = object
`value`*: Value
AssertionDescriptionOpaque*[Cap] {.preservesRecord: "opaque".} = object
`description`*: Preserve[Cap]
AssertionDescriptionOpaque* {.preservesRecord: "opaque".} = object
`description`*: Value
`AssertionDescription`*[Cap] {.preservesOr.} = object
`AssertionDescription`* {.preservesOr.} = object
case orKind*: AssertionDescriptionKind
of AssertionDescriptionKind.`value`:
`value`*: AssertionDescriptionValue[Cap]
`value`*: AssertionDescriptionValue
of AssertionDescriptionKind.`opaque`:
`opaque`*: AssertionDescriptionOpaque[Cap]
`opaque`*: AssertionDescriptionOpaque
NameKind* {.pure.} = enum
`anonymous`, `named`
NameAnonymous* {.preservesRecord: "anonymous".} = object
NameNamed*[Cap] {.preservesRecord: "named".} = object
`name`*: Preserve[Cap]
NameNamed* {.preservesRecord: "named".} = object
`name`*: Value
`Name`*[Cap] {.preservesOr.} = object
`Name`* {.preservesOr.} = object
case orKind*: NameKind
of NameKind.`anonymous`:
`anonymous`*: NameAnonymous
of NameKind.`named`:
`named`*: NameNamed[Cap]
`named`*: NameNamed
ActorId*[Cap] = Preserve[Cap]
FacetId*[Cap] = Preserve[Cap]
ActorId* = Value
FacetId* = Value
`FacetStopReason`* {.preservesOr, pure.} = enum
`explicitAction`, `inert`, `parentStopping`, `actorStopping`
TaskId*[Cap] = Preserve[Cap]
TaskId* = Value
ActorActivationKind* {.pure.} = enum
`start`, `turn`, `stop`
ActorActivationStart*[Cap] {.preservesRecord: "start".} = object
`actorName`*: Name[Cap]
ActorActivationStart* {.preservesRecord: "start".} = object
`actorName`*: Name
ActorActivationStop* {.preservesRecord: "stop".} = object
`status`*: ExitStatus
`ActorActivation`*[Cap] {.preservesOr.} = object
`ActorActivation`* {.preservesOr.} = object
case orKind*: ActorActivationKind
of ActorActivationKind.`start`:
`start`*: ActorActivationStart[Cap]
`start`*: ActorActivationStart
of ActorActivationKind.`turn`:
`turn`*: TurnDescription[Cap]
`turn`*: TurnDescription
of ActorActivationKind.`stop`:
`stop`*: ActorActivationStop
Target*[Cap] {.preservesRecord: "entity".} = object
`actor`*: ActorId[Cap]
`facet`*: FacetId[Cap]
`oid`*: Oid[Cap]
Target* {.preservesRecord: "entity".} = object
`actor`*: ActorId
`facet`*: FacetId
`oid`*: Oid
TurnCauseKind* {.pure.} = enum
`turn`, `cleanup`, `linkedTaskRelease`, `periodicActivation`, `delay`,
`external`
TurnCauseTurn*[Cap] {.preservesRecord: "caused-by".} = object
`id`*: TurnId[Cap]
TurnCauseTurn* {.preservesRecord: "caused-by".} = object
`id`*: TurnId
TurnCauseCleanup* {.preservesRecord: "cleanup".} = object
TurnCauseLinkedTaskRelease*[Cap] {.preservesRecord: "linked-task-release".} = object
`id`*: TaskId[Cap]
TurnCauseLinkedTaskRelease* {.preservesRecord: "linked-task-release".} = object
`id`*: TaskId
`reason`*: LinkedTaskReleaseReason
TurnCausePeriodicActivation* {.preservesRecord: "periodic-activation".} = object
`period`*: float64
TurnCauseDelay*[Cap] {.preservesRecord: "delay".} = object
`causingTurn`*: TurnId[Cap]
TurnCauseDelay* {.preservesRecord: "delay".} = object
`causingTurn`*: TurnId
`amount`*: float64
TurnCauseExternal*[Cap] {.preservesRecord: "external".} = object
`description`*: Preserve[Cap]
TurnCauseExternal* {.preservesRecord: "external".} = object
`description`*: Value
`TurnCause`*[Cap] {.preservesOr.} = object
`TurnCause`* {.preservesOr.} = object
case orKind*: TurnCauseKind
of TurnCauseKind.`turn`:
`turn`*: TurnCauseTurn[Cap]
`turn`*: TurnCauseTurn
of TurnCauseKind.`cleanup`:
`cleanup`*: TurnCauseCleanup
of TurnCauseKind.`linkedTaskRelease`:
`linkedtaskrelease`*: TurnCauseLinkedTaskRelease[Cap]
`linkedtaskrelease`*: TurnCauseLinkedTaskRelease
of TurnCauseKind.`periodicActivation`:
`periodicactivation`*: TurnCausePeriodicActivation
of TurnCauseKind.`delay`:
`delay`*: TurnCauseDelay[Cap]
`delay`*: TurnCauseDelay
of TurnCauseKind.`external`:
`external`*: TurnCauseExternal[Cap]
`external`*: TurnCauseExternal
TurnEventKind* {.pure.} = enum
`assert`, `retract`, `message`, `sync`, `breakLink`
TurnEventAssert*[Cap] {.preservesRecord: "assert".} = object
`assertion`*: AssertionDescription[Cap]
TurnEventAssert* {.preservesRecord: "assert".} = object
`assertion`*: AssertionDescription
`handle`*: protocol.Handle
TurnEventRetract* {.preservesRecord: "retract".} = object
`handle`*: protocol.Handle
TurnEventMessage*[Cap] {.preservesRecord: "message".} = object
`body`*: AssertionDescription[Cap]
TurnEventMessage* {.preservesRecord: "message".} = object
`body`*: AssertionDescription
TurnEventSync*[Cap] {.preservesRecord: "sync".} = object
`peer`*: Target[Cap]
TurnEventSync* {.preservesRecord: "sync".} = object
`peer`*: Target
TurnEventBreakLink*[Cap] {.preservesRecord: "break-link".} = object
`source`*: ActorId[Cap]
TurnEventBreakLink* {.preservesRecord: "break-link".} = object
`source`*: ActorId
`handle`*: protocol.Handle
`TurnEvent`*[Cap] {.preservesOr.} = object
`TurnEvent`* {.preservesOr.} = object
case orKind*: TurnEventKind
of TurnEventKind.`assert`:
`assert`*: TurnEventAssert[Cap]
`assert`*: TurnEventAssert
of TurnEventKind.`retract`:
`retract`*: TurnEventRetract
of TurnEventKind.`message`:
`message`*: TurnEventMessage[Cap]
`message`*: TurnEventMessage
of TurnEventKind.`sync`:
`sync`*: TurnEventSync[Cap]
`sync`*: TurnEventSync
of TurnEventKind.`breakLink`:
`breaklink`*: TurnEventBreakLink[Cap]
`breaklink`*: TurnEventBreakLink
TurnDescription*[Cap] {.preservesRecord: "turn".} = object
`id`*: TurnId[Cap]
`cause`*: TurnCause[Cap]
`actions`*: seq[ActionDescription[Cap]]
TurnDescription* {.preservesRecord: "turn".} = object
`id`*: TurnId
`cause`*: TurnCause
`actions`*: seq[ActionDescription]
ExitStatusKind* {.pure.} = enum
`ok`, `Error`
@ -169,101 +169,95 @@ type
`error`*: protocol.Error
TraceEntry*[Cap] {.preservesRecord: "trace".} = object
TraceEntry* {.preservesRecord: "trace".} = object
`timestamp`*: float64
`actor`*: ActorId[Cap]
`item`*: ActorActivation[Cap]
`actor`*: ActorId
`item`*: ActorActivation
Oid*[Cap] = Preserve[Cap]
Oid* = Value
ActionDescriptionKind* {.pure.} = enum
`dequeue`, `enqueue`, `dequeueInternal`, `enqueueInternal`, `spawn`, `link`,
`facetStart`, `facetStop`, `linkedTaskStart`
ActionDescriptionDequeue*[Cap] {.preservesRecord: "dequeue".} = object
`event`*: TargetedTurnEvent[Cap]
ActionDescriptionDequeue* {.preservesRecord: "dequeue".} = object
`event`*: TargetedTurnEvent
ActionDescriptionEnqueue*[Cap] {.preservesRecord: "enqueue".} = object
`event`*: TargetedTurnEvent[Cap]
ActionDescriptionEnqueue* {.preservesRecord: "enqueue".} = object
`event`*: TargetedTurnEvent
ActionDescriptionDequeueInternal*[Cap] {.preservesRecord: "dequeue-internal".} = object
`event`*: TargetedTurnEvent[Cap]
ActionDescriptionDequeueInternal* {.preservesRecord: "dequeue-internal".} = object
`event`*: TargetedTurnEvent
ActionDescriptionEnqueueInternal*[Cap] {.preservesRecord: "enqueue-internal".} = object
`event`*: TargetedTurnEvent[Cap]
ActionDescriptionEnqueueInternal* {.preservesRecord: "enqueue-internal".} = object
`event`*: TargetedTurnEvent
ActionDescriptionSpawn*[Cap] {.preservesRecord: "spawn".} = object
ActionDescriptionSpawn* {.preservesRecord: "spawn".} = object
`link`*: bool
`id`*: ActorId[Cap]
`id`*: ActorId
ActionDescriptionLink*[Cap] {.preservesRecord: "link".} = object
`parentActor`*: ActorId[Cap]
ActionDescriptionLink* {.preservesRecord: "link".} = object
`parentActor`*: ActorId
`childToParent`*: protocol.Handle
`childActor`*: ActorId[Cap]
`childActor`*: ActorId
`parentToChild`*: protocol.Handle
ActionDescriptionFacetStart*[Cap] {.preservesRecord: "facet-start".} = object
`path`*: seq[FacetId[Cap]]
ActionDescriptionFacetStart* {.preservesRecord: "facet-start".} = object
`path`*: seq[FacetId]
ActionDescriptionFacetStop*[Cap] {.preservesRecord: "facet-stop".} = object
`path`*: seq[FacetId[Cap]]
ActionDescriptionFacetStop* {.preservesRecord: "facet-stop".} = object
`path`*: seq[FacetId]
`reason`*: FacetStopReason
ActionDescriptionLinkedTaskStart*[Cap] {.preservesRecord: "linked-task-start".} = object
`taskName`*: Name[Cap]
`id`*: TaskId[Cap]
ActionDescriptionLinkedTaskStart* {.preservesRecord: "linked-task-start".} = object
`taskName`*: Name
`id`*: TaskId
`ActionDescription`*[Cap] {.preservesOr.} = object
`ActionDescription`* {.preservesOr.} = object
case orKind*: ActionDescriptionKind
of ActionDescriptionKind.`dequeue`:
`dequeue`*: ActionDescriptionDequeue[Cap]
`dequeue`*: ActionDescriptionDequeue
of ActionDescriptionKind.`enqueue`:
`enqueue`*: ActionDescriptionEnqueue[Cap]
`enqueue`*: ActionDescriptionEnqueue
of ActionDescriptionKind.`dequeueInternal`:
`dequeueinternal`*: ActionDescriptionDequeueInternal[Cap]
`dequeueinternal`*: ActionDescriptionDequeueInternal
of ActionDescriptionKind.`enqueueInternal`:
`enqueueinternal`*: ActionDescriptionEnqueueInternal[Cap]
`enqueueinternal`*: ActionDescriptionEnqueueInternal
of ActionDescriptionKind.`spawn`:
`spawn`*: ActionDescriptionSpawn[Cap]
`spawn`*: ActionDescriptionSpawn
of ActionDescriptionKind.`link`:
`link`*: ActionDescriptionLink[Cap]
`link`*: ActionDescriptionLink
of ActionDescriptionKind.`facetStart`:
`facetstart`*: ActionDescriptionFacetStart[Cap]
`facetstart`*: ActionDescriptionFacetStart
of ActionDescriptionKind.`facetStop`:
`facetstop`*: ActionDescriptionFacetStop[Cap]
`facetstop`*: ActionDescriptionFacetStop
of ActionDescriptionKind.`linkedTaskStart`:
`linkedtaskstart`*: ActionDescriptionLinkedTaskStart[Cap]
`linkedtaskstart`*: ActionDescriptionLinkedTaskStart
proc `$`*[Cap](x: TargetedTurnEvent[Cap] | AssertionDescription[Cap] | Name[Cap] |
ActorActivation[Cap] |
Target[Cap] |
TurnCause[Cap] |
TurnEvent[Cap] |
TurnDescription[Cap] |
TraceEntry[Cap] |
ActionDescription[Cap]): string =
`$`(toPreserve(x, Cap))
proc `$`*(x: TargetedTurnEvent | AssertionDescription | Name | ActorActivation |
Target |
TurnCause |
TurnEvent |
TurnDescription |
ExitStatus |
TraceEntry |
ActionDescription): string =
`$`(toPreserves(x))
proc encode*[Cap](x: TargetedTurnEvent[Cap] | AssertionDescription[Cap] |
Name[Cap] |
ActorActivation[Cap] |
Target[Cap] |
TurnCause[Cap] |
TurnEvent[Cap] |
TurnDescription[Cap] |
TraceEntry[Cap] |
ActionDescription[Cap]): seq[byte] =
encode(toPreserve(x, Cap))
proc `$`*(x: ExitStatus): string =
`$`(toPreserve(x))
proc encode*(x: ExitStatus): seq[byte] =
encode(toPreserve(x))
proc encode*(x: TargetedTurnEvent | AssertionDescription | Name |
ActorActivation |
Target |
TurnCause |
TurnEvent |
TurnDescription |
ExitStatus |
TraceEntry |
ActionDescription): seq[byte] =
encode(toPreserves(x))

View File

@ -16,7 +16,7 @@ type
`port`*: BiggestInt
proc `$`*(x: WebSocket | Stdio | Unix | Tcp): string =
`$`(toPreserve(x))
`$`(toPreserves(x))
proc encode*(x: WebSocket | Stdio | Unix | Tcp): seq[byte] =
encode(toPreserve(x))
encode(toPreserves(x))

View File

@ -3,12 +3,12 @@ import
preserves
type
Instance*[Cap] {.preservesRecord: "Instance".} = object
Instance* {.preservesRecord: "Instance".} = object
`name`*: string
`argument`*: Preserve[Cap]
`argument`*: Value
proc `$`*[Cap](x: Instance[Cap]): string =
`$`(toPreserve(x, Cap))
proc `$`*(x: Instance): string =
`$`(toPreserves(x))
proc encode*[Cap](x: Instance[Cap]): seq[byte] =
encode(toPreserve(x, Cap))
proc encode*(x: Instance): seq[byte] =
encode(toPreserves(x))

View File

@ -1,9 +1,10 @@
# SPDX-FileCopyrightText: ☭ Emery Hemingway
# SPDX-License-Identifier: Unlicense
import std/[asyncdispatch, options, streams, tables]
import std/[asyncdispatch, options, tables]
from std/os import getEnv, `/`
import preserves
import ./actors, ./durings, ./membranes, ./protocols/[protocol, sturdy, transportAddress]
import ../syndicate, /capabilities, ./durings, ./membranes, ./protocols/[gatekeeper, protocol, sturdy, transportAddress]
when defined(traceSyndicate):
when defined(posix):
@ -15,20 +16,21 @@ else:
export `$`
type Oid = sturdy.Oid
type
Oid = sturdy.Oid
export Stdio, Tcp, WebSocket, Unix
type
Value = Preserve[void]
Assertion = Preserve[Ref]
WireRef = sturdy.WireRef[void]
Assertion = Value
WireRef = sturdy.WireRef
Turn = syndicate.Turn
Handle = actors.Handle
Turn = actors.Turn
PacketWriter = proc (turn: var Turn; buf: seq[byte]) {.closure, gcsafe.}
RelaySetup = proc (turn: var Turn; relay: Relay) {.closure, gcsafe.}
type
PacketWriter = proc (pkt: sink Packet): Future[void] {.gcsafe.}
RelaySetup = proc (turn: var Turn; relay: Relay) {.gcsafe.}
Relay* = ref object of RootObj
Relay* = ref object
facet: Facet
inboundAssertions: Table[Handle,
tuple[localHandle: Handle, imported: seq[WireSymbol]]]
@ -37,12 +39,13 @@ type
imported: Membrane
nextLocalOid: Oid
pendingTurn: protocol.Turn
wireBuf: BufferedDecoder
packetWriter: PacketWriter
untrusted: bool
peer: Cap
SyncPeerEntity = ref object of Entity
relay: Relay
peer: Ref
peer: Cap
handleMap: Table[Handle, Handle]
e: WireSymbol
@ -51,7 +54,7 @@ type
label: string
relay: Relay
proc releaseRefOut(r: Relay; e: WireSymbol) =
proc releaseCapOut(r: Relay; e: WireSymbol) =
r.exported.drop e
method publish(spe: SyncPeerEntity; t: var Turn; a: AssertionRef; h: Handle) =
@ -64,33 +67,35 @@ method retract(se: SyncPeerEntity; t: var Turn; h: Handle) =
method message(se: SyncPeerEntity; t: var Turn; a: AssertionRef) =
if not se.e.isNil:
se.relay.releaseRefOut(se.e)
se.relay.releaseCapOut(se.e)
message(t, se.peer, a.value)
method sync(se: SyncPeerEntity; t: var Turn; peer: Ref) =
method sync(se: SyncPeerEntity; t: var Turn; peer: Cap) =
sync(t, se.peer, peer)
proc newSyncPeerEntity(r: Relay; p: Ref): SyncPeerEntity =
proc newSyncPeerEntity(r: Relay; p: Cap): SyncPeerEntity =
SyncPeerEntity(relay: r, peer: p)
proc rewriteRefOut(relay: Relay; `ref`: Ref; exported: var seq[WireSymbol]): WireRef =
if `ref`.target of RelayEntity and `ref`.target.RelayEntity.relay == relay and `ref`.attenuation.len == 0:
WireRef(orKind: WireRefKind.yours, yours: WireRefYours[void](oid: `ref`.target.oid))
proc rewriteCapOut(relay: Relay; cap: Cap; exported: var seq[WireSymbol]): WireRef =
if cap.target of RelayEntity and cap.target.RelayEntity.relay == relay and cap.attenuation.len == 0:
result = WireRef(orKind: WireRefKind.yours, yours: WireRefYours(oid: cap.target.oid))
else:
var ws = grab(relay.exported, `ref`)
var ws = grab(relay.exported, cap)
if ws.isNil:
ws = newWireSymbol(relay.exported, relay.nextLocalOid, `ref`)
ws = newWireSymbol(relay.exported, relay.nextLocalOid, cap)
inc relay.nextLocalOid
exported.add ws
WireRef(
result = WireRef(
orKind: WireRefKind.mine,
mine: WireRefMine(oid: ws.oid))
proc rewriteOut(relay: Relay; v: Assertion):
tuple[rewritten: Value, exported: seq[WireSymbol]] {.gcsafe.} =
var exported: seq[WireSymbol]
result.rewritten = contract(v) do (r: Ref) -> Value:
rewriteRefOut(relay, r, exported).toPreserve
result.rewritten = mapEmbeds(v) do (pr: Value) -> Value:
let o = pr.unembed(Cap); if o.isSome:
rewriteCapOut(relay, o.get, exported).toPreserves
else: pr
result.exported = exported
proc register(relay: Relay; v: Assertion; h: Handle): tuple[rewritten: Value, exported: seq[WireSymbol]] =
@ -100,55 +105,52 @@ proc register(relay: Relay; v: Assertion; h: Handle): tuple[rewritten: Value, ex
proc deregister(relay: Relay; h: Handle) =
var outbound: seq[WireSymbol]
if relay.outboundAssertions.pop(h, outbound):
for e in outbound: releaseRefOut(relay, e)
for e in outbound: releaseCapOut(relay, e)
proc send(r: Relay; pkt: sink Packet): Future[void] =
assert(not r.packetWriter.isNil, "missing packetWriter proc")
r.packetWriter(pkt)
proc send(r: Relay; rOid: protocol.Oid; m: Event) =
if r.pendingTurn.len == 0:
# do not send a packet immediately,
# wait until the pending I/O is processed
proc send(relay: Relay; turn: var Turn; rOid: protocol.Oid; m: Event) =
if relay.pendingTurn.len == 0:
# If the pending queue is empty then schedule a packet
# to be sent after pending I/O is processed.
callSoon do ():
r.facet.run do (turn: var Turn):
relay.facet.run do (turn: var Turn):
var pkt = Packet(
orKind: PacketKind.Turn,
turn: move r.pendingTurn)
turn: move relay.pendingTurn)
trace "C: ", pkt
asyncCheck(turn, r.send(pkt))
r.pendingTurn.add TurnEvent(oid: rOid, event: m)
relay.packetWriter(turn, encode pkt)
relay.pendingTurn.add TurnEvent(oid: rOid, event: m)
proc send(re: RelayEntity; ev: Event) =
send(re.relay, protocol.Oid re.oid, ev)
proc send(re: RelayEntity; turn: var Turn; ev: Event) =
send(re.relay, turn, protocol.Oid re.oid, ev)
method publish(re: RelayEntity; t: var Turn; a: AssertionRef; h: Handle) {.gcsafe.} =
re.send Event(
re.send(t, Event(
orKind: EventKind.Assert,
`assert`: protocol.Assert(
assertion: re.relay.register(a.value, h).rewritten,
handle: h))
handle: h)))
method retract(re: RelayEntity; t: var Turn; h: Handle) {.gcsafe.} =
re.relay.deregister h
re.send Event(
re.send(t, Event(
orKind: EventKind.Retract,
retract: Retract(handle: h))
retract: Retract(handle: h)))
method message(re: RelayEntity; turn: var Turn; msg: AssertionRef) {.gcsafe.} =
var (value, exported) = rewriteOut(re.relay, msg.value)
assert(len(exported) == 0, "cannot send a reference in a message")
if len(exported) == 0:
re.send Event(orKind: EventKind.Message, message: Message(body: value))
re.send(turn, Event(orKind: EventKind.Message, message: Message(body: value)))
method sync(re: RelayEntity; turn: var Turn; peer: Ref) {.gcsafe.} =
method sync(re: RelayEntity; turn: var Turn; peer: Cap) {.gcsafe.} =
var
peerEntity = newSyncPeerEntity(re.relay, peer)
exported: seq[WireSymbol]
discard rewriteRefOut(re.relay, turn.newRef(peerEntity), exported)
# TODO: discard?
wr = rewriteCapOut(re.relay, turn.newCap(peerEntity), exported)
peerEntity.e = exported[0]
re.send Event(orKind: EventKind.Sync)
var ev = Event(orKind: EventKind.Sync)
ev.sync.peer = wr.toPreserves.embed
re.send(turn, ev)
proc newRelayEntity(label: string; r: Relay; o: Oid): RelayEntity =
RelayEntity(label: label, relay: r, oid: o)
@ -157,24 +159,26 @@ using
relay: Relay
facet: Facet
proc lookupLocal(relay; oid: Oid): Ref =
proc lookupLocal(relay; oid: Oid): Cap =
let sym = relay.exported.grab oid
if sym.isNil: newInertRef()
else: sym.`ref`
if sym.isNil: newInertCap()
else: sym.cap
proc isInert(r: Ref): bool =
proc isInert(r: Cap): bool =
r.target.isNil
proc rewriteRefIn(relay; facet; n: WireRef, imported: var seq[WireSymbol]): Ref =
proc rewriteCapIn(relay; facet; n: WireRef, imported: var seq[WireSymbol]): Cap =
case n.orKind
of WireRefKind.mine:
var e = relay.imported.grab(n.mine.oid)
if e.isNil: e = newWireSymbol(
relay.imported,
n.mine.oid,
newRef(facet, newRelayEntity("rewriteRefIn", relay, n.mine.oid)))
if e.isNil:
e = newWireSymbol(
relay.imported,
n.mine.oid,
newCap(facet, newRelayEntity("rewriteCapIn", relay, n.mine.oid)),
)
imported.add e
result = e.`ref`
result = e.cap
of WireRefKind.yours:
let r = relay.lookupLocal(n.yours.oid)
if n.yours.attenuation.len == 0 or r.isInert: result = r
@ -183,20 +187,20 @@ proc rewriteRefIn(relay; facet; n: WireRef, imported: var seq[WireSymbol]): Ref
proc rewriteIn(relay; facet; v: Value):
tuple[rewritten: Assertion; imported: seq[WireSymbol]] {.gcsafe.} =
var imported: seq[WireSymbol]
result.rewritten = expand(v) do (pr: Value) -> Assertion:
var wr: WireRef
if not fromPreserve(wr, pr):
raiseAssert "expansion of embedded value failed"
rewriteRefIn(relay, facet, wr, imported).toPreserve(Ref)
result.rewritten = mapEmbeds(v) do (pr: Value) -> Value:
let wr = pr.preservesTo WireRef; if wr.isSome:
result = rewriteCapIn(relay, facet, wr.get, imported).embed
else:
result = pr
result.imported = imported
proc close(r: Relay) = discard
proc dispatch*(relay: Relay; turn: var Turn; `ref`: Ref; event: Event) {.gcsafe.} =
proc dispatch(relay: Relay; turn: var Turn; cap: Cap; event: Event) {.gcsafe.} =
case event.orKind
of EventKind.Assert:
let (a, imported) = rewriteIn(relay, turn.facet, event.assert.assertion)
relay.inboundAssertions[event.assert.handle] = (publish(turn, `ref`, a), imported,)
relay.inboundAssertions[event.assert.handle] = (publish(turn, cap, a), imported,)
of EventKind.Retract:
let remoteHandle = event.retract.handle
@ -208,23 +212,23 @@ proc dispatch*(relay: Relay; turn: var Turn; `ref`: Ref; event: Event) {.gcsafe.
of EventKind.Message:
let (a, imported) = rewriteIn(relay, turn.facet, event.message.body)
assert imported.len == 0, "Cannot receive transient reference"
turn.message(`ref`, a)
turn.message(cap, a)
of EventKind.Sync:
discard # TODO
#[
var imported: seq[WireSymbol]
let k = relay.rewriteRefIn(turn, evenr.sync.peer, imported)
turn.sync(`ref`) do (turn: var Turn):
let k = relay.rewriteCapIn(turn, evenr.sync.peer, imported)
turn.sync(cap) do (turn: var Turn):
turn.message(k, true)
for e in imported: relay.imported.del e
]#
proc dispatch*(relay: Relay; v: Value) {.gcsafe.} =
proc dispatch(relay: Relay; v: Value) {.gcsafe.} =
trace "S: ", v
run(relay.facet) do (t: var Turn):
var pkt: Packet
if fromPreserve(pkt, v):
if pkt.fromPreserves(v):
case pkt.orKind
of PacketKind.Turn:
# https://synit.org/book/protocol.html#turn-packets
@ -233,7 +237,7 @@ proc dispatch*(relay: Relay; v: Value) {.gcsafe.} =
if not r.isInert:
dispatch(relay, t, r, te.event)
else:
stderr.writeLine("discarding event for unknown Ref; ", te.event)
stderr.writeLine("discarding event for unknown Cap; ", te.event)
of PacketKind.Error:
# https://synit.org/book/protocol.html#error-packets
when defined(posix):
@ -246,172 +250,451 @@ proc dispatch*(relay: Relay; v: Value) {.gcsafe.} =
when defined(posix):
stderr.writeLine("discarding undecoded packet ", v)
proc recv(relay: Relay; buf: seq[byte]) =
feed(relay.wireBuf, buf)
var pr = decode(relay.wireBuf)
if pr.isSome: dispatch(relay, pr.get)
type
RelayOptions* = object of RootObj
packetWriter*: PacketWriter
untrusted*: bool
RelayActorOptions* = object of RelayOptions
initialOid*: Option[Oid]
initialRef*: Ref
initialCap*: Cap
nextLocalOid*: Option[Oid]
proc newRelay(turn: var Turn; opts: RelayOptions; setup: RelaySetup): Relay =
result = Relay(
facet: turn.facet,
packetWriter: opts.packetWriter,
untrusted: opts.untrusted)
discard result.facet.preventInertCheck()
setup(turn, result)
proc spawnRelay*(name: string; turn: var Turn; opts: RelayActorOptions; setup: RelaySetup): Future[Ref] =
var fut = newFuture[Ref]"spawnRelay"
proc spawnRelay(name: string; turn: var Turn; opts: RelayActorOptions; setup: RelaySetup) =
spawn(name, turn) do (turn: var Turn):
let relay = newRelay(turn, opts, setup)
if not opts.initialRef.isNil:
let relay = Relay(
facet: turn.facet,
packetWriter: opts.packetWriter,
wireBuf: newBufferedDecoder(0),
)
discard relay.facet.preventInertCheck()
if not opts.initialCap.isNil:
var exported: seq[WireSymbol]
discard rewriteRefOut(relay, opts.initialRef, exported)
if opts.initialOid.isSome:
var imported: seq[WireSymbol]
var wr = WireRef(
orKind: WireRefKind.mine,
mine: WireRefMine(oid: opts.initialOid.get))
fut.complete rewriteRefIn(relay, turn.facet, wr, imported)
else:
fut.complete(nil)
discard rewriteCapOut(relay, opts.initialCap, exported)
opts.nextLocalOid.map do (oid: Oid):
relay.nextLocalOid =
if oid == 0.Oid: 1.Oid
else: oid
fut
assert opts.initialOid.isSome
if opts.initialOid.isSome:
var
imported: seq[WireSymbol]
wr = WireRef(
orKind: WireRefKind.mine,
mine: WireRefMine(oid: opts.initialOid.get))
relay.peer = rewriteCapIn(relay, turn.facet, wr, imported)
assert not relay.peer.isNil
setup(turn, relay)
proc rejected(detail: Value): Resolved =
result = Resolved(orKind: ResolvedKind.Rejected)
result.rejected.detail = detail
proc accepted(cap: Cap): Resolved =
result = Resolved(orKind: ResolvedKind.accepted)
result.accepted.responderSession = cap
import syndicate/protocols/noise
import noiseprotocol
type
NoiseTunnelEntity = ref object of Entity
handshake: HandshakeState
sendCipher, recvCipher: CipherState
peer: Cap
relay: Relay
action: ACTION
method message(tunnel: NoiseTunnelEntity; turn: var Turn; v: AssertionRef) =
doAssert(v.value.isByteString, $v.value)
stderr.writeLine "NoiseTunnelEntity: received message of ", v.value.bytes.len, " bytes"
proc protocolName(spec: NoiseSpec): string =
if spec.protocol.isNone: "Noise_NK_25519_ChaChaPoly_BLAKE2s"
else: spec.protocol.get.string
proc handshake(turn: var Turn; tunnel: NoiseTunnelEntity; spec: NoiseSpec; cont: TurnAction) =
stderr.writeLine "handshaking"
let handshake = spec.protocolName.newByName(INITIATOR)
stderr.writeLine "got handshake"
handshake.set_prologue(spec.service.encode)
stderr.writeLine "prologue set"
if spec.preSharedKeys.isSome:
stderr.writeLine "spec.preSharedKeys.isSome:"
var keys: seq[seq[byte]]
stderr.writeLine("get preSharedKeys")
if not keys.fromPreserves(get spec.preSharedKeys):
raise newException(ValueError, "invalid preSharedKeys in NoiseSpec")
for key in keys:
handshake.setPreSharedKey(key)
stderr.writeLine("get_local_keypair_dh")
let dh = handshake.get_remote_public_key_dh
if not dh.isNil:
dh.set_public_key(spec.key)
stderr.writeLine "start handshake"
start(handshake)
while true:
tunnel.action = get_action(handshake)
stderr.writeLine("tunnel.action is ", $tunnel.action)
case tunnel.action
of SPLIT:
(tunnel.sendCipher, tunnel.recvCipher) = split(handshake)
destroy(handshake)
cont(turn)
of WRITE_MESSAGE:
var buf = newSeqOfCap[byte](256)
write_message(handshake, buf, @[])
tunnel.peer.target.message(turn, AssertionRef(value: buf.toPreserves))
of READ_MESSAGE:
break
else:
raiseAssert("action is " & $tunnel.action)
#[
type
NoiseTunnel = ref object
buffer: seq[byte]
handshake: HandshakeState
sendCipher, recvCipher: CipherState
proc noiseSend(state: NoiseTunnel; buf: seq[byte]) =
stderr.writeLine("noise send ", buf.len, " bytes")
var state = NoiseTunnel(tun)
if not state.handshake.isNil:
let action = get_action(state.handshake)
case action
of SPLIT:
(state.sendCipher, state.recvCipher) = split(state.handshake)
destroy(state.handshake)
send(state, buf)
of WRITE_MESSAGE:
doAssert(buf.len < MAX_PAYLOAD_LEN)
state.buffer.setLen(buf.len)
write_message(state.handshake, state.buffer, buf)
send(state.inner, state.buffer)
else:
raiseAssert("bad noise handshake state " & $action)
else:
doAssert(buf.len < MAX_PAYLOAD_LEN)
state.buffer.setLen(buf.len)
copyMem(state.buffer[0].addr, buf[0].addr, state.buffer.len)
encrypt(state.sendCipher, state.buffer)
send(state.inner, state.buffer)
proc noiseRecv(tun: Tunnel; buf: seq[byte]) =
stderr.writeLine("noise recv ", buf.len, " bytes")
var state = NoiseTunnel(tun)
if not state.handshake.isNil:
let action = get_action(state.handshake)
case action
of SPLIT:
(state.sendCipher, state.recvCipher) = split(state.handshake)
destroy(state.handshake)
send(state, buf)
of READ_MESSAGE:
doAssert(buf.len < MAX_PAYLOAD_LEN)
state.buffer.setLen(buf.len)
copyMem(state.buffer[0].addr, buf[0].addr, state.buffer.len)
read_message(state.handshake, buf, state.buffer)
recv(state.outer, state.buffer)
else:
raiseAssert("bad noise handshake state " & $action)
else:
doAssert(buf.len < MAX_PAYLOAD_LEN)
state.buffer.setLen(buf.len)
copyMem(state.buffer[0].addr, buf[0].addr, state.buffer.len)
decrypt(state.sendCipher, state.buffer)
recv(state.outer, state.buffer)
proc newNoiseTunnel(facet: Facet; spec: NoiseSpec): NoiseTunnel =
noiseprotocol.init()
let state = NoiseTunnel(
send: noiseSend,
recv: noiseRecv,
handshake: spec.protocolName.newByName(INITIATOR)
)
state.handshake.set_prologue(spec.service.encode)
if spec.preSharedKeys.isSome:
var keys: seq[seq[byte]]
if not keys.fromPreserves(get spec.preSharedKeys):
raise newException(ValueError, "invalid preSharedKeys in NoiseSpec")
for key in keys:
state.handshake.setPreSharedKey(key)
if spec.key.len > 0: # responder public key
state.handshake.get_local_keypair_dh.set_public_key(spec.key)
start(state.handshake)
]#
proc spawnNoiseRelay(turn: var Turn; ds, origin: Cap; spec: NoiseSpec) =
let tunnel = NoiseTunnelEntity(peer: origin)
proc noiseWriter(turn: var Turn; buf: seq[byte]) =
stderr.writeLine "NoiseTunnelEntity: need to send ", buf.len, " bytes"
var opts = RelayActorOptions(
packetWriter: noiseWriter,
initialCap: ds,
initialOid: 0.Oid.some,
)
spawnRelay("noise-relay", turn, opts) do (turn: var Turn; relay: Relay):
tunnel.relay = relay
#[
handshake(turn, tunnel, spec) do (turn: var Turn):
publish(turn, ds, ResolvedPathStep(
origin: origin,
pathStep: toRecord(toSymbol"noise", spec),
resolved: tunnel.relay.peer.accepted,
))
]#
when defined(posix):
import std/asyncnet
from std/nativesockets import AF_INET, AF_UNIX, IPPROTO_TCP, SOCK_STREAM, Protocol
import protocols/[gatekeeper, sturdy]
type ShutdownEntity* = ref object of Entity
method retract(e: ShutdownEntity; turn: var Turn; h: Handle) =
stopActor(turn)
type ConnectProc* = proc (turn: var Turn; ds: Ref) {.gcsafe.}
export Tcp
when defined(posix):
export Unix
proc connect*(turn: var Turn; socket: AsyncSocket; step: Preserve[Ref]; bootProc: ConnectProc) =
proc socketWriter(packet: sink Packet): Future[void] =
socket.send(cast[string](encode(packet)))
const recvSize = 0x2000
var shutdownRef: Ref
let
reenable = turn.facet.preventInertCheck()
connectionClosedRef = newRef(turn, ShutdownEntity())
fut = newFuture[void]"connect"
discard bootActor("socket") do (turn: var Turn):
var ops = RelayActorOptions(
packetWriter: socketWriter,
initialOid: 0.Oid.some)
let refFut = spawnRelay("socket", turn, ops) do (turn: var Turn; relay: Relay):
let facet = turn.facet
var wireBuf = newBufferedDecoder(0)
proc recvCb(pktFut: Future[string]) {.gcsafe.} =
if pktFut.failed:
run(facet) do (turn: var Turn): stopActor(turn)
else:
var buf = pktFut.read
if buf.len == 0:
run(facet) do (turn: var Turn): stopActor(turn)
else:
feed(wireBuf, buf)
var (success, pr) = decode(wireBuf)
if success:
dispatch(relay, pr)
socket.recv(recvSize).addCallback(recvCb)
socket.recv(recvSize).addCallback(recvCb)
turn.facet.actor.atExit do (turn: var Turn): close(socket)
discard publish(turn, connectionClosedRef, true)
shutdownRef = newRef(turn, ShutdownEntity())
addCallback(refFut) do ():
let gatekeeper = read refFut
run(gatekeeper.relay) do (turn: var Turn):
reenable()
discard publish(turn, shutdownRef, true)
proc duringCallback(turn: var Turn; a: Assertion; h: Handle): TurnAction =
let facet = inFacet(turn) do (turn: var Turn):
var
accepted: ResolvedAccepted[Ref]
rejected: Rejected[Ref]
if fromPreserve(accepted, a):
bootProc(turn, accepted.responderSession)
elif fromPreserve(rejected, a):
fail(fut, newException(CatchableError, $rejected.detail))
else:
fail(fut, newException(CatchableError, $a))
proc action(turn: var Turn) =
stop(turn, facet)
result = action
discard publish(turn, gatekeeper, Resolve[Ref](
step: step,
observer: newRef(turn, during(duringCallback)),
))
fut.complete()
asyncCheck(turn, fut)
proc connect*(turn: var Turn; transport: Tcp; step: Preserve[Ref]; bootProc: ConnectProc) =
let socket = newAsyncSocket(
domain = AF_INET,
sockType = SOCK_STREAM,
protocol = IPPROTO_TCP,
buffered = false)
let fut = connect(socket, transport.host, Port transport.port)
addCallback(fut, turn) do (turn: var Turn):
connect(turn, socket, step, bootProc)
proc connect*(turn: var Turn; transport: Unix; step: Preserve[Ref]; bootProc: ConnectProc) =
let socket = newAsyncSocket(
domain = AF_UNIX,
sockType = SOCK_STREAM,
protocol = cast[Protocol](0),
buffered = false)
let fut = connectUnix(socket, transport.path)
addCallback(fut, turn) do (turn: var Turn):
connect(turn, socket, step, bootProc)
import std/asyncfile
export Unix
const stdinReadSize = 128
type StdioControlEntity = ref object of Entity
stdin: AsyncFile
proc connectStdio*(ds: Ref; turn: var Turn) =
## Connect to an external dataspace over stdin and stdout.
proc stdoutWriter(packet: sink Packet): Future[void] {.async.} =
var buf = encode(packet)
doAssert writeBytes(stdout, buf, 0, buf.len) == buf.len
method message(entity: StdioControlEntity; turn: var Turn; ass: AssertionRef) =
if ass.value.preservesTo(ForceDisconnect).isSome:
close(entity.stdin)
close(stdout)
proc connectTransport(turn: var Turn; ds: Cap; ta: transportAddress.Stdio) =
## Connect to an external dataspace over stdio.
proc stdoutWriter(turn: var Turn; buf: seq[byte]) =
## Blocking write to stdout.
let n = writeBytes(stdout, buf, 0, buf.len)
flushFile(stdout)
if n != buf.len:
stopActor(turn)
var opts = RelayActorOptions(
packetWriter: stdoutWriter,
initialRef: ds,
initialOid: 0.Oid.some)
asyncCheck spawnRelay("stdio", turn, opts) do (turn: var Turn; relay: Relay):
packetWriter: stdoutWriter,
initialCap: ds,
initialOid: 0.Oid.some,
)
spawnRelay("stdio", turn, opts) do (turn: var Turn; relay: Relay):
let
facet = turn.facet
asyncStdin = openAsync("/dev/stdin") # this is universal now?
close(stdin)
facet.actor.atExit do (turn: var Turn):
close(asyncStdin)
var wireBuf = newBufferedDecoder()
publish(turn, ds, TransportConnection(
`addr`: ta.toPreserves,
control: StdioControlEntity(stdin: asyncStdin).newCap(turn),
resolved: relay.peer.accepted,
))
const stdinReadSize = 0x2000
proc readCb(pktFut: Future[string]) {.gcsafe.} =
if not pktFut.failed:
var buf = pktFut.read
if buf.len == 0:
run(facet) do (turn: var Turn): stopActor(turn)
else:
feed(wireBuf, buf)
var (success, pr) = decode(wireBuf)
if success:
dispatch(relay, pr)
relay.recv(cast[seq[byte]](buf))
asyncStdin.read(stdinReadSize).addCallback(readCb)
asyncStdin.read(stdinReadSize).addCallback(readCb)
proc connectStdio*(turn: var Turn; ds: Cap) =
## Connect to an external dataspace over stdin and stdout.
connectTransport(turn, ds, transportAddress.Stdio())
import std/asyncnet
from std/nativesockets import AF_INET, AF_UNIX, IPPROTO_TCP, SOCK_STREAM, Protocol
type SocketControlEntity = ref object of Entity
socket: AsyncSocket
method message(entity: SocketControlEntity; turn: var Turn; ass: AssertionRef) =
if ass.value.preservesTo(ForceDisconnect).isSome:
close(entity.socket)
type ShutdownEntity* = ref object of Entity
method retract(e: ShutdownEntity; turn: var Turn; h: Handle) =
stopActor(turn)
proc connect(turn: var Turn; ds: Cap; transAddr: Value; socket: AsyncSocket) =
proc socketWriter(turn: var Turn; buf: seq[byte]) =
asyncCheck(turn, socket.send(cast[string](buf)))
var ops = RelayActorOptions(
packetWriter: socketWriter,
initialOid: 0.Oid.some,
)
spawnRelay("socket", turn, ops) do (turn: var Turn; relay: Relay):
let facet = turn.facet
facet.actor.atExit do (turn: var Turn): close(socket)
publish(turn, ds, TransportConnection(
`addr`: transAddr,
control: SocketControlEntity(socket: socket).newCap(turn),
resolved: relay.peer.accepted,
))
const recvSize = 0x4000
proc recvCb(pktFut: Future[string]) {.gcsafe.} =
if pktFut.failed or pktFut.read.len == 0:
run(facet) do (turn: var Turn): stopActor(turn)
else:
relay.recv(cast[seq[byte]](pktFut.read))
if not socket.isClosed:
socket.recv(recvSize).addCallback(recvCb)
socket.recv(recvSize).addCallback(recvCb)
proc connect(turn: var Turn; ds: Cap; ta: Value; socket: AsyncSocket; fut: Future[void]) =
let facet = turn.facet
fut.addCallback do ():
run(facet) do (turn: var Turn):
if fut.failed:
var ass = TransportConnection(
`addr`: ta,
resolved: Resolved(orKind: ResolvedKind.Rejected),
)
ass.resolved.rejected.detail = embed fut.error
publish(turn, ds, ass)
else:
connect(turn, ds, ta, socket)
proc connectTransport(turn: var Turn; ds: Cap; ta: transportAddress.Tcp) =
let
facet = turn.facet
socket = newAsyncSocket(
domain = AF_INET,
sockType = SOCK_STREAM,
protocol = IPPROTO_TCP,
buffered = false,
)
connect(turn, ds, ta.toPreserves, socket, connect(socket, ta.host, Port ta.port))
proc connectTransport(turn: var Turn; ds: Cap; ta: transportAddress.Unix) =
## Relay a dataspace over a UNIX socket.
let socket = newAsyncSocket(
domain = AF_UNIX,
sockType = SOCK_STREAM,
protocol = cast[Protocol](0),
buffered = false)
connect(turn, ds, ta.toPreserves, socket, connectUnix(socket, ta.path))
proc walk(turn: var Turn; ds, origin: Cap; route: Route; transOff, stepOff: int) {.gcsafe.} =
if stepOff < route.pathSteps.len:
let
step = route.pathSteps[stepOff]
rejectPat = ResolvedPathStep?:{
0: ?(origin.embed), 1: ?step, 2: ?:Rejected}
acceptPat = ResolvedPathStep?:{
0: ?(origin.embed), 1: ?step, 2: ?:ResolvedAccepted}
onPublish(turn, ds, rejectPat) do (detail: Value):
publish(turn, ds, ResolvePath(
route: route,
`addr`: route.transports[transOff],
resolved: detail.rejected,
))
during(turn, ds, acceptPat) do (next: Cap):
walk(turn, ds, next, route, transOff, stepOff.succ)
else:
publish(turn, ds, ResolvePath(
route: route,
`addr`: route.transports[transOff],
resolved: origin.accepted,
))
proc connectRoute(turn: var Turn; ds: Cap; route: Route; transOff: int) =
let rejectPat = TransportConnection ?: {
0: ?route.transports[transOff],
2: ?:Rejected,
}
during(turn, ds, rejectPat) do (detail: Value):
publish(turn, ds, ResolvePath(
route: route,
`addr`: route.transports[transOff],
resolved: detail.rejected,
))
let acceptPat = TransportConnection?:{
0: ?route.transports[transOff],
2: ?:ResolvedAccepted,
}
onPublish(turn, ds, acceptPat) do (origin: Cap):
walk(turn, ds, origin, route, transOff, 0)
proc spawnRelays*(turn: var Turn; ds: Cap) =
## Spawn actors that manage routes and appeasing gatekeepers.
spawn("transport-connector", turn) do (turn: var Turn):
let pat = ?Observe(pattern: !TransportConnection) ?? { 0: grab() }
# Use a generic pattern and type matching
# in the during handler because it is easy.
let stdioPat = ?Observe(pattern: TransportConnection?:{0: ?:Stdio})
during(turn, ds, stdioPat) do:
connectTransport(turn, ds, Stdio())
# TODO: tcp pattern
during(turn, ds, pat) do (ta: Literal[transportAddress.Tcp]):
connectTransport(turn, ds, ta.value)
# TODO: unix pattern
during(turn, ds, pat) do (ta: Literal[transportAddress.Unix]):
connectTransport(turn, ds, ta.value)
spawn("path-resolver", turn) do (turn: var Turn):
let pat = ?Observe(pattern: !ResolvePath) ?? {0: grab()}
during(turn, ds, pat) do (route: Literal[Route]):
for i, transAddr in route.value.transports:
connectRoute(turn, ds, route.value, i)
spawn("sturdyref-step", turn) do (turn: var Turn):
let pat = ?Observe(pattern: ResolvedPathStep?:{1: !SturdyRef}) ?? {0: grab(), 1: grab()}
during(turn, ds, pat) do (origin: Literal[Cap]; detail: Literal[sturdy.Parameters]):
let step = SturdyRef(parameters: detail.value).toPreserves
proc duringCallback(turn: var Turn; ass: Assertion; h: Handle): TurnAction =
let facet = inFacet(turn) do (turn: var Turn):
var res = ass.preservesTo Resolved
if res.isSome:
publish(turn, ds, ResolvedPathStep(
origin: origin.value,
pathStep: step,
resolved: res.get,
))
proc action(turn: var Turn) =
stop(turn, facet)
result = action
publish(turn, origin.value, Resolve(
step: step,
observer: newCap(turn, during(duringCallback)),
))
spawn("noise-step", turn) do (turn: var Turn):
let
stepPat = grabRecord(toSymbol"noise", grab())
pat = ?Observe(pattern: ResolvedPathStep?:{1: stepPat}) ?? {0: grab(), 1: grab()}
during(turn, ds, pat) do (origin: Literal[Cap]; detail: Literal[NoiseSpec]):
let step = toRecord(Symbol"noise", detail.value)
proc duringCallback(turn: var Turn; ass: Assertion; h: Handle): TurnAction =
# TODO: better during thing
let facet = inFacet(turn) do (turn: var Turn):
var res = ass.preservesTo Resolved
if res.isSome and res.get.orKind == ResolvedKind.accepted:
if res.get.accepted.responderSession of Cap:
spawnNoiseRelay(turn, ds, res.get.accepted.responderSession.Cap, detail.value)
proc action(turn: var Turn) =
stop(turn, facet)
result = action
publish(turn, origin.value, Resolve(
step: step,
observer: newCap(turn, during(duringCallback)),
))
type BootProc* = proc (turn: var Turn; ds: Cap) {.gcsafe.}
proc envRoute*: Route =
var text = getEnv("SYNDICATE_ROUTE")
if text == "":
var tx = (getEnv("XDG_RUNTIME_DIR", "/run/user/1000") / "dataspace").toPreserves
result.transports = @[initRecord("unix", tx)]
result.pathSteps = @[capabilities.mint().toPreserves]
else:
var pr = parsePreserves(text)
if not result.fromPreserves(pr):
raise newException(ValueError, "failed to parse $SYNDICATE_ROUTE " & $pr)
proc resolve*(turn: var Turn; ds: Cap; route: Route; bootProc: BootProc) =
during(turn, ds, ResolvePath ?: {0: ?route, 3: ?:ResolvedAccepted}) do (dst: Cap):
bootProc(turn, dst)
# TODO: define a runActor that comes preloaded with relaying

View File

@ -1,15 +1,16 @@
# SPDX-FileCopyrightText: ☭ 2021 Emery Hemingway
# SPDX-FileCopyrightText: ☭ Emery Hemingway
# SPDX-License-Identifier: Unlicense
import std/[hashes, lists, options, sets, tables]
## https://git.syndicate-lang.org/syndicate-lang/syndicate-rkt/src/commit/90c4c60699069b496491b81ee63b5a45ffd638cb/syndicate/HOWITWORKS.md
import std/[hashes, options, sets, tables]
import preserves
import ./actors, ./bags, ./patterns
import ./protocols/dataspacePatterns
type
DCompound = dataspacePatterns.DCompound[Ref]
Pattern = dataspacePatterns.Pattern[Ref]
Value = Preserve[Ref]
DCompound = dataspacePatterns.DCompound
Pattern = dataspacePatterns.Pattern
Path = seq[Value]
ClassKind = enum classNone, classRecord, classSequence, classDictionary
Class = object
@ -43,71 +44,106 @@ type
AssertionCache = HashSet[Value]
Paths = seq[Path]
ObserverGroup = ref object # Endpoints
cachedCaptures: Bag[seq[Value]]
observers: Table[Ref, TableRef[seq[Value], Handle]]
cachedCaptures: Bag[Captures]
observers: Table[Cap, TableRef[Captures, Handle]]
Leaf = ref object
cachedAssertions: AssertionCache
cache: AssertionCache
observerGroups: Table[Paths, ObserverGroup]
LeafMap = TableRef[seq[Value], Leaf]
Continuation = ref object
cachedAssertions: AssertionCache
leafMap: Table[Paths, TableRef[seq[Value], Leaf]]
Selector = tuple[popCount: int; index: Value]
Node = ref object
edges: Table[Selector, TableRef[Class, Node]]
continuation: Continuation
cache: AssertionCache
leafMap: Table[Paths, LeafMap]
func isEmpty(leaf: Leaf): bool =
leaf.cachedAssertions.len == 0 and leaf.observerGroups.len == 0
leaf.cache.len == 0 and leaf.observerGroups.len == 0
func isEmpty(cont: Continuation): bool =
cont.cache.len == 0 and cont.leafMap.len == 0
type
ContinuationProc = proc (c: Continuation; v: Value) {.gcsafe.}
LeafProc = proc (l: Leaf; v: Value) {.gcsafe.}
ObserverProc = proc (turn: var Turn; group: ObserverGroup; vs: seq[Value]) {.gcsafe.}
type TermStack = SinglyLinkedList[Value]
proc getLeaves(cont: Continuation; 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:
let key = projectPaths(ass, constPaths)
if key.isSome:
var leaf = result.getOrDefault(get key)
if leaf.isNil:
new leaf
result[get key] = leaf
leaf.cache.incl(ass)
proc getLeaf(leafMap: LeafMap; constVals: seq[Value]): Leaf =
result = leafMap.getOrDefault(constVals)
if result.isNil:
new result
leafMap[constVals] = result
type
Selector = tuple[popCount: int; index: Value]
Node = ref object
continuation: Continuation
edges: Table[Selector, TableRef[Class, Node]]
func isEmpty(node: Node): bool =
node.continuation.isEmpty and node.edges.len == 0
type TermStack = seq[Value]
proc push(stack: TermStack; val: Value): Termstack =
result = stack
prepend(result, val)
add(result, val)
proc pop(stack: TermStack; n: int): TermStack =
result = stack
var n = n
while n > 0:
result.remove(result.head)
assert not stack.head.isNil, "popped too far"
dec n
assert n <= stack.len
stack[stack.low..(stack.high-n)]
proc top(stack: TermStack): Value =
assert not stack.head.isNil, "stack is empty"
stack.head.value
assert stack.len > 0
stack[stack.high]
proc modify(node: Node; turn: var Turn; outerValue: Value; event: EventKind;
modCont: ContinuationProc; modLeaf: LeafProc; modObs: ObserverProc) =
proc walk(turn: var Turn; cont: Continuation; outerValue: Value; event: EventKind) =
proc walk(cont: Continuation; turn: var Turn) =
modCont(cont, outerValue)
for constPaths, constValMap in cont.leafMap.pairs:
let constVals = projectPaths(outerValue, constPaths)
var leaf = constValMap.getOrDefault(constVals)
if leaf.isNil and event == addedEvent:
new leaf
constValMap[constVals] = leaf
if not leaf.isNil:
modLeaf(leaf, outerValue)
for capturePaths, observerGroup in leaf.observerGroups.pairs:
modObs(turn, observerGroup, projectPaths(outerValue, capturePaths))
# TODO: cleanup dead leaves
if constVals.isSome:
case event
of addedEvent, messageEvent:
let leaf = constValMap.getLeaf(get constVals)
modLeaf(leaf, outerValue)
for capturePaths, observerGroup in leaf.observerGroups.pairs:
let captures = projectPaths(outerValue, capturePaths)
if captures.isSome:
modObs(turn, observerGroup, get captures)
of removedEvent:
let leaf = constValMap.getOrDefault(get constVals)
if not leaf.isNil:
modLeaf(leaf, outerValue)
for capturePaths, observerGroup in leaf.observerGroups.pairs:
let captures = projectPaths(outerValue, capturePaths)
if captures.isSome:
modObs(turn, observerGroup, get captures)
if leaf.isEmpty:
constValMap.del(get constVals)
proc walk(node: Node; turn: var Turn; outerValue: Value; event: EventKind; termStack: TermStack) =
walk(turn, node.continuation, outerValue, event)
proc walk(node: Node; turn: var Turn; termStack: TermStack) =
walk(node.continuation, turn)
for selector, table in node.edges:
let
nextStack = pop(termStack, selector.popCount)
@ -117,10 +153,11 @@ proc modify(node: Node; turn: var Turn; outerValue: Value; event: EventKind;
if nextClass.kind != classNone:
let nextNode = table.getOrDefault(nextClass)
if not nextNode.isNil:
walk(nextNode, turn, outerValue, event, push(nextStack, get nextValue))
walk(nextNode, turn, push(nextStack, get nextValue))
if event == removedEvent and nextNode.isEmpty:
table.del(nextClass)
var stack: TermStack
walk(node, turn, outerValue, event, push(stack, @[outerValue].toPreserve(Ref)))
walk(node, turn, @[@[outerValue].toPreserves])
proc getOrNew[A, B, C](t: var Table[A, TableRef[B, C]], k: A): TableRef[B, C] =
result = t.getOrDefault(k)
@ -132,10 +169,10 @@ iterator pairs(dc: DCompound): (Value, Pattern) =
case dc.orKind
of DCompoundKind.rec:
for i, p in dc.rec.fields:
yield (toPreserve(i, Ref), p,)
yield (i.toPreserves, p,)
of DCompoundKind.arr:
for i, p in dc.arr.items:
yield (toPreserve(i, Ref), p,)
yield (i.toPreserves, p,)
of DCompoundKind.dict:
for pair in dc.dict.entries.pairs:
yield pair
@ -148,123 +185,117 @@ proc extendWalk(node: Node; popCount: Natural; stepIndex: Value; pat: Pattern; p
result = extendWalk(node, popCount, stepIndex, pat.dbind.pattern, path)
of PatternKind.DCompound:
let
class = classOf pat
selector: Selector = (popCount, stepIndex,)
table = node.edges.getOrNew(selector)
class = classOf pat
result.nextNode = table.getOrDefault(class)
if result.nextNode.isNil:
new result.nextNode
table[class] = result.nextNode
new result.nextNode.continuation
for a in node.continuation.cachedAssertions:
var v = projectPath(a, path)
for a in node.continuation.cache:
var v = step(a, path)
if v.isSome and class == classOf(get v):
result.nextNode.continuation.cachedAssertions.incl a
for i, p in pat.dcompound.pairs:
add(path, i)
result = extendWalk(result.nextNode, result.popCount, i, p, path)
result.nextNode.continuation.cache.incl a
result.popCount = 0
for step, p in pat.dcompound.pairs:
add(path, step)
result = extendWalk(result.nextNode, result.popCount, step, p, path)
discard pop(path)
inc(result.popCount)
proc extend(node: var Node; pat: Pattern): Continuation =
var path: Path
extendWalk(node, 0, toPreserve(0, Ref), pat, path).nextNode.continuation
extendWalk(node, 0, 0.toPreserves, pat, path).nextNode.continuation
type
Index* = object
allAssertions: Bag[Value]
root: Node
observerCount: int
proc initIndex*(): Index =
Index(root: Node(continuation: Continuation()))
proc add*(index: var Index; turn: var Turn; pattern: Pattern; observer: Ref) =
let
analysis = analyse pattern
continuation = index.root.extend pattern
var constValMap = continuation.leafMap.getOrDefault(analysis.constPaths)
if constValMap.isNil:
new constValMap
for a in continuation.cachedAssertions:
let key = projectPaths(a, analysis.constPaths)
var leaf = constValMap.getOrDefault(key)
if leaf.isNil:
new leaf
constValMap[key] = leaf
leaf.cachedAssertions.incl(a)
continuation.leafMap[analysis.constPaths] = constValMap
var leaf = constValMap.getOrDefault(analysis.constValues)
if leaf.isNil:
new leaf
constValMap[analysis.constValues] = leaf
var observerGroup = leaf.observerGroups.getOrDefault(analysis.capturePaths)
if observerGroup.isNil:
new observerGroup
for a in leaf.cachedAssertions:
discard observerGroup.cachedCaptures.change(projectPaths(a, analysis.capturePaths), +1)
leaf.observerGroups[analysis.capturePaths] = observerGroup
var captureMap = newTable[seq[Value], Handle]()
for (count, captures) in observerGroup.cachedCaptures:
captureMap[captures] = publish(turn, observer, captures)
observerGroup.observers[observer] = captureMap
proc getEndpoints(leaf: Leaf; capturePaths: Paths): ObserverGroup =
result = leaf.observerGroups.getOrDefault(capturePaths)
if result.isNil:
new result
leaf.observerGroups[capturePaths] = result
for term in leaf.cache:
# leaf.cache would be empty if observers come before assertions
let captures = projectPaths(term, capturePaths)
if captures.isSome:
discard result.cachedCaptures.change(get captures, +1)
proc remove*(index: var Index; turn: var Turn; pattern: Pattern; observer: Ref) =
var
proc add*(index: var Index; turn: var Turn; pattern: Pattern; observer: Cap) =
let
cont = index.root.extend(pattern)
analysis = analyse pattern
continuation = index.root.extend pattern
let constValMap = continuation.leafMap.getOrDefault(analysis.constPaths)
constValMap = cont.getLeaves(analysis.constPaths)
leaf = constValMap.getLeaf(analysis.constValues)
endpoints = leaf.getEndpoints(analysis.capturePaths)
# TODO if endpoints.cachedCaptures.len > 0:
var captureMap = newTable[seq[Value], Handle]()
for capture in endpoints.cachedCaptures.items:
captureMap[capture] = publish(turn, observer, capture)
endpoints.observers[observer] = captureMap
proc remove*(index: var Index; turn: var Turn; pattern: Pattern; observer: Cap) =
let
cont = index.root.extend(pattern)
analysis = analyse pattern
constValMap = cont.leafMap.getOrDefault(analysis.constPaths)
if not constValMap.isNil:
let leaf = constValMap.getOrDefault(analysis.constValues)
if not leaf.isNil:
let observerGroup = leaf.observerGroups.getOrDefault(analysis.capturePaths)
if not observerGroup.isNil:
let captureMap = observerGroup.observers.getOrDefault(observer)
if not captureMap.isNil:
for handle in captureMap.values: retract(observer.target, turn, handle)
observerGroup.observers.del(observer)
if observerGroup.observers.len == 0:
let endpoints = leaf.observerGroups.getOrDefault(analysis.capturePaths)
if not endpoints.isNil:
var captureMap: TableRef[seq[Value], Handle]
if endpoints.observers.pop(observer, captureMap):
for handle in captureMap.values: retract(turn, handle)
if endpoints.observers.len == 0:
leaf.observerGroups.del(analysis.capturePaths)
if leaf.isEmpty:
constValMap.del(analysis.constValues)
if constValMap.len == 0:
continuation.leafMap.del(analysis.constPaths)
if leaf.observerGroups.len == 0:
constValMap.del(analysis.constValues)
if constValMap.len == 0:
cont.leafMap.del(analysis.constPaths)
proc adjustAssertion*(index: var Index; turn: var Turn; outerValue: Value; delta: int): bool =
proc adjustAssertion(index: var Index; turn: var Turn; outerValue: Value; delta: int): bool =
case index.allAssertions.change(outerValue, delta)
of cdAbsentToPresent:
result = true
proc modContinuation(c: Continuation; v: Value) =
c.cachedAssertions.incl(v)
c.cache.incl(v)
proc modLeaf(l: Leaf; v: Value) =
l.cachedAssertions.incl(v)
l.cache.incl(v)
proc modObserver(turn: var Turn; group: ObserverGroup; vs: seq[Value]) =
if group.cachedCaptures.change(vs, +1) == cdAbsentToPresent:
let change = group.cachedCaptures.change(vs, +1)
if change == cdAbsentToPresent:
for (observer, captureMap) in group.observers.pairs:
let a = vs.toPreserve(Ref)
captureMap[vs] = publish(turn, observer, a)
captureMap[vs] = publish(turn, observer, vs.toPreserves)
# TODO: this handle is coming from the facet?
modify(index.root, turn, outerValue, addedEvent, modContinuation, modLeaf, modObserver)
of cdPresentToAbsent:
result = true
proc modContinuation(c: Continuation; v: Value) =
c.cachedAssertions.excl(v)
c.cache.excl(v)
proc modLeaf(l: Leaf; v: Value) =
l.cachedAssertions.excl(v)
l.cache.excl(v)
proc modObserver(turn: var Turn; group: ObserverGroup; vs: seq[Value]) =
if group.cachedCaptures.change(vs, -1) == cdPresentToAbsent:
for (observer, captureMap) in group.observers.pairs:
retract(observer.target, turn, captureMap[vs])
captureMap.del(vs)
var h: Handle
if captureMap.take(vs, h):
retract(observer.target, turn, h)
modify(index.root, turn, outerValue, removedEvent, modContinuation, modLeaf, modObserver)
else: discard
proc continuationNoop(c: Continuation; v: Value) = discard
proc leafNoop(l: Leaf; v: Value) = discard
proc add*(index: var Index; turn: var Turn; v: Assertion): bool =
proc add*(index: var Index; turn: var Turn; v: Value): bool =
adjustAssertion(index, turn, v, +1)
proc remove*(index: var Index; turn: var Turn; v: Assertion): bool =
proc remove*(index: var Index; turn: var Turn; v: Value): bool =
adjustAssertion(index, turn, v, -1)
proc deliverMessage*(index: var Index; turn: var Turn; v: Value) =

View File

@ -1,6 +1,6 @@
# Package
version = "20230608"
version = "20240120"
author = "Emery Hemingway"
description = "Syndicated actors for conversational concurrency"
license = "Unlicense"
@ -9,4 +9,4 @@ srcDir = "src"
# Dependencies
requires "hashlib", "nim >= 1.4.8", "preserves >= 20230530", "taps >= 20221119"
requires "https://github.com/ehmry/hashlib.git#f9455d4be988e14e3dc7933eb7cc7d7c4820b7ac", "nim >= 2.0.0", "https://git.syndicate-lang.org/ehmry/preserves-nim.git >= 20240116"

View File

@ -1,2 +1,3 @@
include_rules
: foreach test*.nim | $(SYNDICATE_PROTOCOL) |> !nim_run |>
: foreach *.prs |> !preserves_schema_nim |> | {schema}
: foreach t*.nim | ../../preserves-nim/<tests> {schema} $(SYNDICATE_PROTOCOL) |> !nim_run |> | ../<test>

View File

@ -1,8 +1,8 @@
# SPDX-FileCopyrightText: ☭ Emery Hemingway
# SPDX-License-Identifier: Unlicense
import std/[asyncdispatch, asyncfile, os, parseopt]
import preserves, syndicate, syndicate/protocols/transportAddress
import std/[asyncdispatch, asyncfile, parseopt]
import preserves, syndicate, syndicate/relays
type
Present {.preservesRecord: "Present".} = object
@ -10,7 +10,7 @@ type
Says {.preservesRecord: "Says".} = object
who, what: string
proc readStdin(facet: Facet; ds: Ref; username: string) =
proc readStdin(facet: Facet; ds: Cap; username: string) =
let file = openAsync("/dev/stdin")
onStop(facet) do (turn: var Turn): close(file)
close(stdin)
@ -18,49 +18,39 @@ proc readStdin(facet: Facet; ds: Ref; username: string) =
let future = readLine(file)
addCallback(future, facet) do (turn: var Turn):
var msg = read(future)
if msg == "": quit()
message(turn, ds, Says(who: username, what: msg))
readLine()
readLine()
proc chat(turn: var Turn; ds: Ref; username: string) =
during(turn, ds, ?Present) do (who: string):
proc chat(turn: var Turn; ds: Cap; username: string) =
during(turn, ds, ?:Present) do (who: string):
echo who, " joined"
do:
echo who, " left"
onMessage(turn, ds, ?Says) do (who: string, what: string):
onMessage(turn, ds, ?:Says) do (who: string, what: string):
echo who, ": ", what
discard publish(turn, ds, Present(username: username))
readStdin(turn.facet, ds, username)
proc main =
var
transport: Preserve[void]
cap: Preserve[Ref]
username = getEnv("USER")
calledWithArguments = false
let route = envRoute()
var username = ""
for kind, key, val in getopt():
calledWithArguments = true
if kind == cmdLongOption:
case key
of "address", "transport":
transport = parsePreserves(val)
of "cap", "sturdy":
cap = parsePreserves(val, Ref)
of "user", "username":
username = val
if calledWithArguments:
runActor("chat") do (root: Ref; turn: var Turn):
var
unixAddr: transportAddress.Unix
tcpAddr: transportAddress.Tcp
if fromPreserve(unixAddr, transport):
connect(turn, unixAddr, cap) do (turn: var Turn; ds: Ref):
chat(turn, ds, username)
elif fromPreserve(tcpAddr, transport):
connect(turn, tcpAddr, cap) do (turn: var Turn; ds: Ref):
chat(turn, ds, username)
if username == "":
stderr.writeLine "--user: unspecified"
else:
runActor("chat") do (turn: var Turn; root: Cap):
spawnRelays(turn, root)
resolve(turn, root, route) do (turn: var Turn; ds: Cap):
chat(turn, ds, username)
main()

103
tests/test_patterns.nim Normal file
View File

@ -0,0 +1,103 @@
# SPDX-FileCopyrightText: ☭ Emery Hemingway
# SPDX-License-Identifier: Unlicense
import std/[options, tables, unittest]
import preserves, syndicate, syndicate/protocols/gatekeeper
import ./test_schema
test "patterns":
let
pat = ?Observe(pattern: !Foo) ?? {0: grab()}
text = """<rec Observe [<rec rec [<lit foo> <arr [<bind <_>> <_> <_>]>]> <_>]>"""
check($pat == text)
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)
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":
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
patC = grab obsA
test $patC:
check patC.matches obsA
test $patB:
checkpoint $obsA
check patB.matches obsA
test "TransportConnection":
let
pat = TransportConnection ?: { 2: ?:Rejected}
text = """<rec connect-transport [<_> <_> <rec rejected [<bind <_>>]>]>"""
check $pat == text

View File

@ -2,9 +2,9 @@ import std/[streams, strutils, unittest]
import preserves
import syndicate/relays
import syndicate/protocols/[protocol, sturdy]
import syndicate/protocols/sturdy
type WireRef = sturdy.WireRef[void]
type WireRef = sturdy.WireRef
suite "protocols":
test "PDiscard":
@ -20,9 +20,9 @@ suite "protocols":
var pos = str.getPosition
echo "decode position: ", pos
try:
var a = decodePreserves(str, WireRef)
var a = decodePreserves(str)
echo a
except:
except CatchableError:
str.setPosition pos
echo str.readAll.toHex
break

15
tests/test_schema.nim Normal file
View File

@ -0,0 +1,15 @@
import
preserves
type
Foo* {.preservesRecord: "foo".} = object
`x`*: seq[string]
`y`*: BiggestInt
`z`*: BiggestInt
proc `$`*(x: Foo): string =
`$`(toPreserves(x))
proc encode*(x: Foo): seq[byte] =
encode(toPreserves(x))

2
tests/test_schema.prs Normal file
View File

@ -0,0 +1,2 @@
version 1 .
Foo = <foo @x [string ...] @y int @z int> .

17
tests/test_timers.nim Normal file
View File

@ -0,0 +1,17 @@
# SPDX-FileCopyrightText: ☭ Emery Hemingway
# SPDX-License-Identifier: Unlicense
import std/times
import syndicate, syndicate/actors/timers
proc now: float64 = getTime().toUnixFloat()
runActor("test_timers") do (ds: Cap; turn: var Turn):
onPublish(turn, ds, grab(LaterThan(seconds: now()+1.0))) do:
stderr.writeLine "slept one second once"
onPublish(turn, ds, grab(LaterThan(seconds: now()+1.0))) do:
stderr.writeLine "slept one second twice"
onPublish(turn, ds, grab(LaterThan(seconds: now()+1.0))) do:
stderr.writeLine "slept one second thrice"
quit()
spawnTimers(turn, ds)