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
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 ### Publish
``` nim ``` 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")) let presenceHandle = publish(turn, dataspace, Present(username: "Judy"))
# publish <Present "Judy"> to the dataspace # publish <Present "Judy"> to the dataspace
# the assertion can be later retracted by handle # 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. 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 ``` nim
bootDataspace("main") do (dataspace: Ref; turn: var Turn): runActor("main") do (dataspace: Ref; turn: var Turn):
during(turn, dataspace, ?Present) do (who: string): during(turn, dataspace, ?Present) do (who: string):
# This body is active when the ?Present pattern is matched. # This body is active when the ?Present pattern is matched.
# The Present type contains two atomic values that can be 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) ### [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). Simple chat demo that is compatible with [chat.py](https://git.syndicate-lang.org/syndicate-lang/syndicate-py/src/branch/main/chat.py).
```sh ```sh
nim c -r tests/test_chat.nim \ SYNDICATE_ROUTE='<route [<unix "/run/user/1000/dataspace">] [<ref {oid: "syndicate" sig: #x"69ca300c1dbfa08fba692102dd82311a"}>]>' nim c -r tests/test_chat.nim --user:fnord
--cap:'<ref {oid: "syndicate" sig: #x"69ca300c1dbfa08fba692102dd82311a"}>' \
--transport:'<tcp "127.0.0.1" 666>' \
--user:fnord
``` ```
### [xdg_open_ng](https://git.syndicate-lang.org/ehmry/xdg_open_ng) ### [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 include depends.tup
NIM = $(DIRENV) $(NIM)
NIM_GROUPS += $(TUP_CWD)/<lock>

View File

@ -1,7 +1,4 @@
include ../preserves-nim/depends.tup 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)/../preserves-nim/src
NIM_FLAGS += --path:$(TUP_CWD)/../taps/src NIM_FLAGS += --path:$(TUP_CWD)/../noiseprotocol/src
NIM_FLAGS += --path:$(TUP_CWD)/../hashlib
NIM_GROUPS += $(TUP_CWD)/<protocol> 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 let pkgs = import <nixpkgs> { };
syndicate = builtins.getFlake "syndicate"; in pkgs.buildNimPackage {
pkgs = name = "noiseprotocol";
import <nixpkgs> { overlays = builtins.attrValues syndicate.overlays; }; buildInputs = [ pkgs.noise-c ];
in pkgs.nimPackages.syndicate lockFile = ./lock.json;
}

View File

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

View File

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

View File

@ -1,10 +1,17 @@
# SPDX-FileCopyrightText: ☭ Emery Hemingway # SPDX-FileCopyrightText: ☭ Emery Hemingway
# SPDX-License-Identifier: Unlicense # 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 preserves
import ../syndicate/protocols/[protocol, sturdy] import ../syndicate/protocols/[protocol, sturdy]
const tracing = defined(traceSyndicate)
when tracing:
import std/streams
from std/os import getEnv
import ./protocols/trace
export Handle export Handle
template generateIdType(typ: untyped) = template generateIdType(typ: untyped) =
@ -20,66 +27,91 @@ generateIdType(TurnId)
type type
Oid = sturdy.Oid Oid = sturdy.Oid
Assertion* = Preserve[Ref] Caveat = sturdy.Caveat
Caveat = sturdy.Caveat[Ref]
Attenuation = seq[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 Entity* = ref object of RootObj
oid*: Oid # oid is how Entities are identified over the wire 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 relay*: Facet
target*: Entity target*: Entity
attenuation*: Attenuation attenuation*: Attenuation
Ref* {.deprecated: "Ref was renamed to Cap".} = Cap
OutboundAssertion = ref object OutboundAssertion = ref object
handle: Handle handle: Handle
peer: Ref peer: Cap
established: bool established: bool
OutboundTable = Table[Handle, OutboundAssertion] OutboundTable = Table[Handle, OutboundAssertion]
Actor* = ref object Actor* = ref object
future: Future[void]
name: string name: string
id: ActorId handleAllocator: ref Handle
handleAllocator: Handle # a fresh actor gets a new ref Handle and
# all actors spawned from it get the same ref.
root: Facet root: Facet
exitReason: ref Exception exitReason: ref Exception
exitHooks: seq[TurnAction] exitHooks: seq[TurnAction]
exiting: bool id: ActorId
exiting, exited: bool
when tracing:
turnIdAllocator: ref TurnId
traceStream: FileStream
TurnAction* = proc (t: var Turn) {.gcsafe.} TurnAction* = proc (t: var Turn) {.gcsafe.}
Queues = TableRef[Facet, seq[TurnAction]] Queues = TableRef[Facet, seq[TurnAction]]
Turn* = object # an object that should remain on the stack Turn* = object # an object that should remain on the stack
id: TurnId
facet: Facet facet: Facet
queues: Queues # a ref object that can outlive Turn queues: Queues # a ref object that can outlive Turn
when tracing:
ParentFacet = Option[Facet] desc: TurnDescription
Facet* = ref FacetObj Facet* = ref FacetObj
FacetObj = object FacetObj = object
id: FacetId
actor*: Actor actor*: Actor
parent: ParentFacet parent: Facet
children: HashSet[Facet] children: HashSet[Facet]
outbound: OutboundTable outbound: OutboundTable
shutdownActions: seq[TurnAction] shutdownActions: seq[TurnAction]
inertCheckPreventers: int inertCheckPreventers: int
id: FacetId
isAlive: bool isAlive: bool
type AssertionRef* = ref object when tracing:
value*: Preserve[Ref]
# if the Enity methods take a Preserve[Ref] object then the generated proc nextTurnId(facet: Facet): TurnId =
# C code has "redefinition of struct" problems when orc is enabled 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 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 retract*(e: Entity; turn: var Turn; h: Handle) {.base, gcsafe.} = discard
method message*(e: Entity; turn: var Turn; v: AssertionRef) {.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 using
actor: Actor actor: Actor
@ -90,25 +122,26 @@ using
proc labels(f: Facet): string = proc labels(f: Facet): string =
proc catLabels(f: Facet; labels: var string) = proc catLabels(f: Facet; labels: var string) =
labels.add ':' labels.add ':'
if f.parent.isSome: if not f.parent.isNil:
catLabels(f.parent.get, labels) catLabels(f.parent, labels)
labels.add ':' labels.add ':'
labels.add $f.id when tracing:
labels.add $f.id
result.add f.actor.name result.add f.actor.name
catLabels(f, result) catLabels(f, result)
proc `$`*(f: Facet): string = proc `$`*(f: Facet): string =
"<Facet:" & f.labels & ">" "<Facet:" & f.labels & ">"
proc `$`*(r: Ref): string = proc `$`*(r: Cap): string =
"<Ref:" & r.relay.labels & ">" "<Ref:" & r.relay.labels & ">"
proc `$`*(actor: Actor): string = proc `$`*(actor: Actor): string =
"<Actor:" & actor.name & ">" # TODO: ambigous "<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 if a.len == 0: result = r
else: result = Ref( else: result = Cap(
relay: r.relay, relay: r.relay,
target: r.target, target: r.target,
attenuation: a & r.attenuation) attenuation: a & r.attenuation)
@ -116,11 +149,11 @@ proc attenuate(r: Ref; a: Attenuation): Ref =
proc hash*(facet): Hash = proc hash*(facet): Hash =
facet.id.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 = proc nextHandle(facet: Facet): Handle =
inc facet.actor.handleAllocator result = succ(facet.actor.handleAllocator[])
facet.actor.handleAllocator facet.actor.handleAllocator[] = result
proc facet*(turn: var Turn): Facet = turn.facet proc facet*(turn: var Turn): Facet = turn.facet
@ -130,9 +163,9 @@ proc enqueue(turn: var Turn; target: Facet; action: TurnAction) =
else: else:
turn.queues[target] = @[action] 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 case p.orKind
of PatternKind.Pdiscard: result = true of PatternKind.Pdiscard: result = true
of PatternKind.Patom: of PatternKind.Patom:
@ -148,7 +181,7 @@ proc match(bindings: var Bindings; p: Pattern; v: Assertion): bool =
result = v.isEmbedded result = v.isEmbedded
of PatternKind.Pbind: of PatternKind.Pbind:
if match(bindings, p.pbind.pattern, v): if match(bindings, p.pbind.pattern, v):
bindings[toPreserve(p.pbind.pattern, Ref)] = v bindings[p.pbind.pattern.toPreserves] = v
result = true result = true
of PatternKind.Pand: of PatternKind.Pand:
for pp in p.pand.patterns: for pp in p.pand.patterns:
@ -186,21 +219,22 @@ proc match(bindings: var Bindings; p: Pattern; v: Assertion): bool =
result = true result = true
break break
proc match(p: Pattern; v: Assertion): Option[Bindings] = proc match(p: Pattern; v: Value): Option[Bindings] =
var b: Bindings var b: Bindings
if match(b, p, v): if match(b, p, v):
result = some b result = some b
proc instantiate(t: Template; bindings: Bindings): Assertion = proc instantiate(t: Template; bindings: Bindings): Value =
case t.orKind case t.orKind
of TemplateKind.Tattenuate: of TemplateKind.Tattenuate:
let v = instantiate(t.tattenuate.template, bindings) 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") 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: of TemplateKind.TRef:
let n = $t.tref.binding.int let n = $t.tref.binding.int
try: result = bindings[toPreserve(n, Ref)] try: result = bindings[n.toPreserves]
except KeyError: except KeyError:
raise newException(ValueError, "unbound reference: " & n) raise newException(ValueError, "unbound reference: " & n)
of TemplateKind.Lit: of TemplateKind.Lit:
@ -212,20 +246,20 @@ proc instantiate(t: Template; bindings: Bindings): Assertion =
for i, tt in t.tcompound.rec.fields: for i, tt in t.tcompound.rec.fields:
result[i] = instantiate(tt, bindings) result[i] = instantiate(tt, bindings)
of TCompoundKind.arr: 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: for i, tt in t.tcompound.arr.items:
result[i] = instantiate(tt, bindings) result[i] = instantiate(tt, bindings)
of TCompoundKind.dict: of TCompoundKind.dict:
result = initDictionary(Ref) result = initDictionary()
for key, tt in t.tcompound.dict.entries: for key, tt in t.tcompound.dict.entries:
result[key] = instantiate(tt, bindings) 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) let bindings = match(r.pattern, v)
if bindings.isSome: if bindings.isSome:
result = instantiate(r.template, get bindings) result = instantiate(r.template, get bindings)
proc examineAlternatives(cav: Caveat; v: Assertion): Assertion = proc examineAlternatives(cav: Caveat; v: Value): Value =
case cav.orKind case cav.orKind
of CaveatKind.Rewrite: of CaveatKind.Rewrite:
result = rewrite(cav.rewrite, v) result = rewrite(cav.rewrite, v)
@ -236,13 +270,13 @@ proc examineAlternatives(cav: Caveat; v: Assertion): Assertion =
of CaveatKind.Reject: discard of CaveatKind.Reject: discard
of CaveatKind.unknown: discard of CaveatKind.unknown: discard
proc runRewrites*(a: Attenuation; v: Assertion): Assertion = proc runRewrites*(a: Attenuation; v: Value): Value =
result = v result = v
for stage in a: for stage in a:
result = examineAlternatives(stage, result) result = examineAlternatives(stage, result)
if result.isFalse: break 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) var a = runRewrites(r.attenuation, v)
if not a.isFalse: if not a.isFalse:
let e = OutboundAssertion( 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): enqueue(turn, r.relay) do (turn: var Turn):
e.established = true e.established = true
publish(r.target, turn, AssertionRef(value: a), e.handle) 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() result = turn.facet.nextHandle()
publish(turn, r, a, result) publish(turn, r, a, result)
proc publish*[T](turn: var Turn; r: Ref; a: T): Handle = proc publish*[T](turn: var Turn; r: Cap; a: T): Handle {.discardable.} =
publish(turn, r, toPreserve(a, Ref)) publish(turn, r, a.toPreserves)
proc retract(turn: var Turn; e: OutboundAssertion) = proc retract(turn: var Turn; e: OutboundAssertion) =
enqueue(turn, e.peer.relay) do (turn: var Turn): 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): if turn.facet.outbound.pop(h, e):
turn.retract(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) var a = runRewrites(r.attenuation, v)
if not a.isFalse: if not a.isFalse:
enqueue(turn, r.relay) do (turn: var Turn): enqueue(turn, r.relay) do (turn: var Turn):
r.target.message(turn, AssertionRef(value: a)) r.target.message(turn, AssertionRef(value: a))
proc message*[T](turn: var Turn; r: Ref; v: T) = proc message*[T](turn: var Turn; r: Cap; v: T) =
message(turn, r, toPreserve(v, Ref)) 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) 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): enqueue(turn, r.relay) do (turn: var Turn):
sync(turn, r.target, peer) sync(turn, r.target, peer)
proc replace*[T](turn: var Turn; `ref`: Ref; h: Handle; v: T): Handle = proc replace*[T](turn: var Turn; cap: Cap; h: Handle; v: T): Handle =
result = publish(turn, `ref`, v) result = publish(turn, cap, v)
if h != default(Handle): if h != default(Handle):
retract(turn, h) 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 var old = h
h = publish(turn, `ref`, v) h = publish(turn, cap, v)
if old != default(Handle): if old != default(Handle):
retract(turn, old) retract(turn, old)
h h
@ -303,22 +346,22 @@ proc stop*(turn: var Turn) {.gcsafe.}
proc run*(facet; action: TurnAction; zombieTurn = false) {.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( result = Facet(
id: getMonoTime().ticks.FacetId, id: getMonoTime().ticks.FacetId,
actor: actor, actor: actor,
parent: parent, parent: parent,
outbound: initialAssertions, outbound: initialAssertions,
isAlive: true) 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 var initialAssertions: OutboundTable
newFacet(actor, parent, initialAssertions) newFacet(actor, parent, initialAssertions)
proc isInert(facet): bool = proc isInert(facet): bool =
result = facet.children.len == 0 and 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 facet.inertCheckPreventers == 0
proc preventInertCheck*(facet): (proc() {.gcsafe.}) {.discardable.} = proc preventInertCheck*(facet): (proc() {.gcsafe.}) {.discardable.} =
@ -342,8 +385,8 @@ proc terminate(facet; turn: var Turn; orderly: bool) {.gcsafe.} =
if facet.isAlive: if facet.isAlive:
facet.isAlive = false facet.isAlive = false
let parent = facet.parent let parent = facet.parent
if parent.isSome: if not parent.isNil:
parent.get.children.excl facet parent.children.excl facet
block: block:
var turn = Turn(facet: facet, queues: turn.queues) var turn = Turn(facet: facet, queues: turn.queues)
while facet.children.len > 0: while facet.children.len > 0:
@ -353,55 +396,87 @@ proc terminate(facet; turn: var Turn; orderly: bool) {.gcsafe.} =
act(turn) act(turn)
for a in facet.outbound.values: turn.retract(a) for a in facet.outbound.values: turn.retract(a)
if orderly: if orderly:
if parent.isSome: if not parent.isNil:
if parent.get.isInert: if parent.isInert:
parent.get.terminate(turn, true) parent.terminate(turn, true)
else: else:
terminate(facet.actor, turn, nil) 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 stopIfInertAfter(action: TurnAction): TurnAction =
proc wrapper(turn: var Turn) = proc wrapper(turn: var Turn) =
action(turn) action(turn)
enqueue(turn, turn.facet) do (turn: var Turn): enqueue(turn, turn.facet) do (turn: var Turn):
if (turn.facet.parent.isSome and if (not turn.facet.parent.isNil and
(not turn.facet.parent.get.isAlive)) or (not turn.facet.parent.isAlive)) or
turn.facet.isInert: turn.facet.isInert:
stop(turn) stop(turn)
wrapper wrapper
proc newFacet*(turn: var Turn): Facet = newFacet(turn.facet.actor, turn.facet)
proc inFacet*(turn: var Turn; bootProc: TurnAction): 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)) inFacet(turn, result, stopIfInertAfter(bootProc))
proc facet*(turn: var Turn; bootProc: TurnAction): Facet {.deprecated.} = inFacet(turn, 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 let
now = getTime() now = getTime()
seed = now.toUnix * 1_000_000_000 + now.nanosecond seed = now.toUnix * 1_000_000_000 + now.nanosecond
result = Actor( result = Actor(
name: name, name: name,
id: ActorId(seed)) id: ActorId(seed),
result.root = newFacet(result, none Facet) handleAllocator: handleAlloc,
result.future = newFuture[void]($result) )
run( result.root = newFacet(result, nil)
newFacet(result, some result.root, initialAssertions), when tracing:
stopIfInertAfter(bootProc)) 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 = proc bootActor*(name: string; bootProc: TurnAction): Actor =
var initialAssertions: OutboundTable 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): enqueue(turn, turn.facet) do (turn: var Turn):
var newOutBound: Table[Handle, OutboundAssertion] var newOutBound: Table[Handle, OutboundAssertion]
for key in initialAssertions: for key in initialAssertions:
discard turn.facet.outbound.pop(key, newOutbound[key]) 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() 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 proc atExit*(actor; action) = actor.exitHooks.add action
@ -409,13 +484,16 @@ proc terminate(actor; turn; reason: ref Exception) =
if not actor.exiting: if not actor.exiting:
actor.exiting = true actor.exiting = true
actor.exitReason = reason 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) for hook in actor.exitHooks: hook(turn)
proc finish(turn: var Turn) = proc finish(turn: var Turn) =
actor.root.terminate(turn, not reason.isNil) actor.root.terminate(turn, reason.isNil)
if reason.isNil: actor.exited = true
actor.future.complete()
else:
actor.future.fail reason
callSoon do (): callSoon do ():
run(actor.root, finish, true) run(actor.root, finish, true)
@ -437,28 +515,36 @@ template tryFacet(facet; body: untyped) =
except CatchableError as err: terminate(facet, err) except CatchableError as err: terminate(facet, err)
proc run*(facet; action: TurnAction; zombieTurn = false) = proc run*(facet; action: TurnAction; zombieTurn = false) =
if not zombieTurn: if zombieTurn or (facet.actor.exitReason.isNil and facet.isAlive):
if not facet.actor.exitReason.isNil: return tryFacet(facet):
if not facet.isAlive: return var queues = newTable[Facet, seq[TurnAction]]()
# TODO: not Nim idiom block:
tryFacet(facet): var turn = Turn(facet: facet, queues: queues)
var queues = newTable[Facet, seq[TurnAction]]() action(turn)
block: when tracing:
var turn = Turn(facet: facet, queues: queues) turn.desc.id = facet.nextTurnId.toPreserves
action(turn) facet.actor.trace ActorActivation(
for facet, queue in queues: orKind: ActorActivationKind.turn, turn: turn.desc)
for action in queue: run(facet, action) for facet, queue in queues:
for action in queue: run(facet, action)
proc run*(`ref`: Ref; action: TurnAction) = proc run*(cap: Cap; action: TurnAction) =
## Convenience proc to run a `TurnAction` in the scope of a `Ref`. ## Convenience proc to run a `TurnAction` in the scope of a `Cap`.
run(`ref`.relay, action) run(cap.relay, action)
proc addCallback*(fut: FutureBase; facet: Facet; act: TurnAction) = proc addCallback*(fut: FutureBase; facet: Facet; act: TurnAction) =
## Add a callback to a `Future` that will be called at a later `Turn` ## Add a callback to a `Future` that will be called at a later `Turn`
## within the context of `facet`. ## within the context of `facet`.
addCallback(fut) do (): addCallback(fut) do ():
if fut.failed: terminate(facet, fut.error) 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) = proc addCallback*(fut: FutureBase; turn: var Turn; act: TurnAction) =
## Add a callback to a `Future` that will be called at a later `Turn` ## 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: else:
addCallback(fut, turn.facet, act) 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) = proc stop*(turn: var Turn, facet: Facet) =
enqueue(turn, facet.parent.get) do (turn: var Turn): if facet.parent.isNil:
facet.terminate(turn, true) facet.terminate(turn, true)
else:
enqueue(turn, facet.parent) do (turn: var Turn):
facet.terminate(turn, true)
proc stop*(turn: var Turn) = proc stop*(turn: var Turn) =
stop(turn, turn.facet) stop(turn, turn.facet)
@ -483,20 +581,32 @@ proc onStop*(facet: Facet; act: TurnAction) =
proc stopActor*(turn: var Turn) = proc stopActor*(turn: var Turn) =
let actor = turn.facet.actor 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) terminate(actor, turn, nil)
proc freshen*(turn: var Turn, act: TurnAction) = proc freshen*(turn: var Turn, act: TurnAction) =
assert(turn.queues.len == 0, "Attempt to freshen a non-stale Turn") assert(turn.queues.len == 0, "Attempt to freshen a non-stale Turn")
run(turn.facet, act) run(turn.facet, act)
proc newRef*(relay: Facet; e: Entity): Ref = proc newCap*(relay: Facet; e: Entity): Cap =
Ref(relay: relay, target: e) Cap(relay: relay, target: e)
proc newRef*(turn; e: Entity): Ref = proc newCap*(turn; e: Entity): Cap =
Ref(relay: turn.facet, target: e) Cap(relay: turn.facet, target: e)
proc sync*(turn, refer: Ref, cb: proc(t: Turn) {.gcsafe.}) = proc newCap*(e: Entity; turn): Cap =
discard # TODO 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}: if result in {cdAbsentToAbsent, cdPresentToAbsent}:
bag.del(key) bag.del(key)
iterator items*[T](bag: Bag[T]): (int, T) = iterator items*[T](bag: Bag[T]): T =
for k, v in bag: yield(v, k) 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() let sturdy = mint()
check $sturdy == """<ref {oid: "syndicate" sig: #x"69ca300c1dbfa08fba692102dd82311a"}>""" check $sturdy == """<ref {oid: "syndicate" sig: #x"69ca300c1dbfa08fba692102dd82311a"}>"""
import std/options import std/[options, tables]
from std/sequtils import toSeq from std/sequtils import toSeq
import hashlib/misc/blake2 import hashlib/misc/blake2
import preserves import preserves
import ./protocols/sturdy import ./protocols/sturdy
from ./actors import Ref
export `$` export `$`
proc hmac(key, data: openarray[byte]): seq[byte] = proc hmac(key, data: openarray[byte]): seq[byte] =
count[Hmac[BLAKE2S_256]](key, data).data[0..15].toSeq count[Hmac[BLAKE2S_256]](key, data).data[0..15].toSeq
proc mint*[T](key: openarray[byte]; oid: Preserve[T]): SturdyRef[T] = proc mint*(key: openarray[byte]; oid: Value): SturdyRef =
SturdyRef[T](parameters: { result.parameters.oid = oid
"oid": oid, result.parameters.sig = hmac(key, oid.encode)
"sig": hmac(key, encode(oid)).toPreserve(T),
}.toDictionary,
)
proc mint*(): SturdyRef[Ref] = proc mint*(): SturdyRef =
var key: array[16, byte] 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] = proc attenuate*(r: SturdyRef; caveats: seq[Caveat]): SturdyRef =
result = SturdyRef[T]( if r.parameters.caveats.isSome:
oid: r.oid, result.parameters.caveats = some(r.parameters.caveats.get & caveats.toPreserves)
caveatChain: r.caveatChain, result.parameters.oid = r.parameters.oid
sig: hmac(r.sig, encode caveats)) result.parameters.sig = hmac(r.parameters.sig, caveats.toPreserves.encode)
result.caveatChain.add caveats
proc validate*[T](key: openarray[byte]; sturdy: SturdyRef[T]): bool = proc validate*(key: openarray[byte]; sturdy: SturdyRef): bool =
let oid = step(sturdy.parameters, Symbol"oid") var sig = hmac(key, sturdy.parameters.oid.encode)
if oid.isSome: if sturdy.parameters.caveats.isSome:
let ctrl = step(sturdy.parameters, Symbol"sig") for cav in sturdy.parameters.caveats.get:
if ctrl.isSome: sig = hmac(sig, encode cav)
var sig = hmac(key, oid.get.encode) result = (sig == sturdy.parameters.sig)
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)
when isMainModule: # mint utility moved to syndicate_utils/src/mintsturdyref.nim
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)

View File

@ -1,15 +1,15 @@
# SPDX-FileCopyrightText: ☭ 2022 Emery Hemingway # SPDX-FileCopyrightText: ☭ 2022 Emery Hemingway
# SPDX-License-Identifier: Unlicense # SPDX-License-Identifier: Unlicense
import std/[hashes, tables] import std/[hashes, options, tables]
import preserves import preserves
import ./actors, ./protocols/dataspace, ./skeletons import ./actors, ./protocols/dataspace, ./skeletons
from ./protocols/protocol import Handle from ./protocols/protocol import Handle
type type
Assertion = Preserve[Ref] Assertion = Value
Observe = dataspace.Observe[Ref] Observe = dataspace.Observe
Turn = actors.Turn Turn = actors.Turn
Dataspace {.final.} = ref object of Entity Dataspace {.final.} = ref object of Entity
@ -18,30 +18,33 @@ type
method publish(ds: Dataspace; turn: var Turn; a: AssertionRef; h: Handle) {.gcsafe.} = method publish(ds: Dataspace; turn: var Turn; a: AssertionRef; h: Handle) {.gcsafe.} =
if add(ds.index, turn, a.value): if add(ds.index, turn, a.value):
var obs: Observe var obs = a.value.preservesTo(Observe)
if obs.fromPreserve a.value: if obs.isSome and obs.get.observer of Cap:
ds.index.add(turn, obs.pattern, obs.observer) ds.index.add(turn, obs.get.pattern, Cap(obs.get.observer))
ds.handleMap[h] = a.value ds.handleMap[h] = a.value
method retract(ds: Dataspace; turn: var Turn; h: Handle) {.gcsafe.} = method retract(ds: Dataspace; turn: var Turn; h: Handle) {.gcsafe.} =
try: let v = ds.handleMap[h]
let v = ds.handleMap[h] if remove(ds.index, turn, v):
if remove(ds.index, turn, v): ds.handleMap.del h
ds.handleMap.del h var obs = v.preservesTo(Observe)
var obs: Observe if obs.isSome and obs.get.observer of Cap:
if obs.fromPreserve v: ds.index.remove(turn, obs.get.pattern, Cap(obs.get.observer))
ds.index.remove(turn, obs.pattern, obs.observer)
except KeyError: discard
method message(ds: Dataspace; turn: var Turn; a: AssertionRef) {.gcsafe.} = method message(ds: Dataspace; turn: var Turn; a: AssertionRef) {.gcsafe.} =
ds.index.deliverMessage(turn, a.value) ds.index.deliverMessage(turn, a.value)
proc newDataspace*(turn: var Turn): Ref = proc newDataspace*(turn: var Turn): Cap =
newRef(turn, Dataspace(index: initIndex())) 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): bootActor(name) do (turn: var Turn):
discard turn.facet.preventInertCheck() 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 preserves
import ./actors, ./patterns, ./protocols/dataspace import ./actors, ./patterns, ./protocols/dataspace
from ./protocols/protocol import Handle
type type
Observe = dataspace.Observe[Ref] DuringProc* = proc (turn: var Turn; a: Value; h: Handle): TurnAction {.gcsafe.}
Turn = actors.Turn
type
DuringProc* = proc (turn: var Turn; a: Assertion; h: Handle): TurnAction {.gcsafe.}
DuringActionKind = enum null, dead, act DuringActionKind = enum null, dead, act
DuringAction = object DuringAction = object
case kind: DuringActionKind 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 during*(cb: DuringProc): DuringEntity = DuringEntity(cb: cb)
proc observe*(turn: var Turn; ds: Ref; pat: Pattern; e: Entity): Handle = proc observe*(turn: var Turn; ds: Cap; pat: Pattern; e: Entity): Handle =
publish(turn, ds, Observe(pattern: pat, observer: newRef(turn, e))) publish(turn, ds, Observe(pattern: pat, observer: newCap(turn, e)))

View File

@ -3,43 +3,43 @@
import std/[hashes, tables] import std/[hashes, tables]
from ./actors import Ref, hash from ./actors import Cap, hash
from ./protocols/sturdy import Oid 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 type
Membrane* = object Membrane* = object
## Bidirectional mapping between `Oid` and `Ref` values. ## Bidirectional mapping between `Oid` and `Cap` values.
## https://synit.org/book/protocol.html#membranes ## https://synit.org/book/protocol.html#membranes
byOid: Table[Oid, WireSymbol] byOid: Table[Oid, WireSymbol]
byRef: Table[Ref, WireSymbol] byCap: Table[Cap, WireSymbol]
WireSymbol* = ref object WireSymbol* = ref object
oid: Oid oid: Oid
`ref`: Ref cap: Cap
count: int count: int
proc oid*(sym: WireSymbol): Oid = sym.oid 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 = proc grab*(mem: Membrane; key: Oid): WireSymbol =
## Grab a `WireSymbol` from a `Membrane`. ## Grab a `WireSymbol` from a `Membrane`.
mem.byOid.getOrDefault(key) mem.byOid.getOrDefault(key)
proc grab*(mem: Membrane; key: Ref): WireSymbol = proc grab*(mem: Membrane; key: Cap): WireSymbol =
## Grab a `WireSymbol` from a `Membrane`. ## Grab a `WireSymbol` from a `Membrane`.
mem.byRef.getOrDefault(key) mem.byCap.getOrDefault(key)
proc drop*(mem: var Membrane; sym: WireSymbol) = proc drop*(mem: var Membrane; sym: WireSymbol) =
## Drop a `WireSymbol` from a `Membrane`. ## Drop a `WireSymbol` from a `Membrane`.
dec sym.count dec sym.count
if sym.count < 1: if sym.count < 1:
mem.byOid.del sym.oid 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`. ## 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.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-FileCopyrightText: ☭ Emery Hemingway
# SPDX-License-Identifier: Unlicense # SPDX-License-Identifier: Unlicense
import std/[options, sequtils, tables, typetraits] import std/[algorithm, options, sequtils, tables, typetraits]
import preserves import preserves
import ./protocols/dataspacePatterns import ./protocols/dataspacePatterns
from ./actors import Ref from ./actors import Cap
export dataspacePatterns.`$`, PatternKind, DCompoundKind, AnyAtomKind export dataspacePatterns.`$`, PatternKind, DCompoundKind, AnyAtomKind
type type
AnyAtom = dataspacePatterns.AnyAtom[Ref] AnyAtom = dataspacePatterns.AnyAtom
DBind = dataspacePatterns.DBind[Ref] DBind = dataspacePatterns.DBind
DCompound = dataspacePatterns.DCompound[Ref] DCompound = dataspacePatterns.DCompound
DCompoundArr = dataspacePatterns.DCompoundArr[Ref] DCompoundArr = dataspacePatterns.DCompoundArr
DCompoundDict = dataspacePatterns.DCompoundDict[Ref] DCompoundDict = dataspacePatterns.DCompoundDict
DCompoundRec = dataspacePatterns.DCompoundRec[Ref] DCompoundRec = dataspacePatterns.DCompoundRec
DLit = dataspacePatterns.DLit[Ref] DLit = dataspacePatterns.DLit
Pattern* = dataspacePatterns.Pattern[Ref] 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 = proc toPattern(d: sink DBind): Pattern =
Pattern(orKind: PatternKind.DBind, dbind: d) 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 proc grab*(): Pattern {.inline.} = DBind(pattern: drop()).toPattern
## Create a pattern to capture any value. ## 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`. ## Convert a `Preserve` value to a `Pattern`.
runnableExamples: runnableExamples:
from std/unittest import check 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} <_>>""") == $(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>}> <_>]>""" """<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 case pr.kind
of pkBoolean: of pkBoolean:
AnyAtom(orKind: AnyAtomKind.`bool`, bool: pr.bool).toPattern 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 AnyAtom(orKind: AnyAtomKind.`float`, float: pr.float).toPattern
of pkDouble: of pkDouble:
AnyAtom(orKind: AnyAtomKind.`double`, double: pr.double).toPattern AnyAtom(orKind: AnyAtomKind.`double`, double: pr.double).toPattern
of pkSignedInteger: of pkRegister:
AnyAtom(orKind: AnyAtomKind.`int`, int: pr.int).toPattern AnyAtom(orKind: AnyAtomKind.`int`, int: pr.register).toPattern
of pkString: of pkString:
AnyAtom(orKind: AnyAtomKind.`string`, string: pr.string).toPattern AnyAtom(orKind: AnyAtomKind.`string`, string: pr.string).toPattern
of pkByteString: of pkByteString:
@ -76,21 +86,24 @@ proc grab*[T](pr: Preserve[T]): Pattern =
drop() drop()
else: else:
DCompoundRec( DCompoundRec(
label: cast[Preserve[Ref]](pr.label), # TODO: don't cast like this label: pr.label,
fields: map[Preserve[T], Pattern](pr.fields, grab)).toPattern fields: map[Value, Pattern](pr.fields, grab)).toPattern
of pkSequence: of pkSequence:
DCompoundArr(items: map(pr.sequence, grab)).toPattern DCompoundArr(items: map(pr.sequence, grab)).toPattern
of pkSet: raise newException( of pkSet:
ValueError, "cannot construct a pattern over a set literal") raiseAssert "cannot construct a pattern over a set literal"
of pkDictionary: of pkDictionary:
var dict = DCompoundDict() var 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 dict.toPattern
of pkEmbedded: of pkEmbedded:
# TODO: can patterns be constructed over embedded literals? if pr.embeddedRef.isNil: drop()
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`. ## Construct a `Pattern` from value of type `T`.
runnableExamples: runnableExamples:
from std/unittest import check from std/unittest import check
@ -98,34 +111,7 @@ proc grab*[T](val: T): Pattern =
$grab(true) == "<lit #t>" $grab(true) == "<lit #t>"
$grab(3.14) == "<lit 3.14>" $grab(3.14) == "<lit 3.14>"
$grab([0, 1, 2, 3]) == "<arr [<lit 0> <lit 1> <lit 2> <lit 3>]>" $grab([0, 1, 2, 3]) == "<arr [<lit 0> <lit 1> <lit 2> <lit 3>]>"
grab (toPreserve(val, Ref)) grab(x.toPreserves)
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()
proc grabType*(typ: static typedesc): Pattern = proc grabType*(typ: static typedesc): Pattern =
## Derive a `Pattern` from type `typ`. ## Derive a `Pattern` from type `typ`.
@ -148,43 +134,76 @@ proc grabType*(typ: static typedesc): Pattern =
"<rec rect [<arr [<bind <_>> <bind <_>>]> <arr [<bind <_>> <bind <_>>]>]>" "<rec rect [<arr [<bind <_>> <bind <_>>]> <arr [<bind <_>> <bind <_>>]>]>"
$(grabType ColoredRect) == $(grabType ColoredRect) ==
"<dict {color: <bind <_>> rect: <rec rect [<arr [<bind <_>> <bind <_>>]> <arr [<bind <_>> <bind <_>>]>]>}>" "<dict {color: <bind <_>> rect: <rec rect [<arr [<bind <_>> <bind <_>>]> <arr [<bind <_>> <bind <_>>]>]>}>"
patternOfType(typ, true) when typ is ref:
grabType(pointerBase(typ))
proc dropType*(typ: static typedesc): Pattern = elif typ.hasPreservesRecordPragma:
## Derive a `Pattern` from type `typ` without any bindings. var rec = DCompoundRec(label: typ.recordLabel.toSymbol)
patternOfType(typ, false) 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 = proc fieldCount(T: typedesc): int =
for _, _ in fieldPairs(default T): for _, _ in fieldPairs(default T):
inc result 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: for (j, b) in bindings:
if i == j: if i == j: return b
pat = b return drop()
return true
proc grab*(typ: static typedesc; bindings: sink openArray[(int, Pattern)]): Pattern = proc grab*(typ: static typedesc; bindings: sink openArray[(int, Pattern)]): Pattern =
## Construct a `Pattern` from type `typ` that selectively captures fields. ## Construct a `Pattern` from type `typ` with pattern `bindings` by integer offset.
runnableExamples: when typ is ptr | ref:
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:
grab(pointerBase(typ), bindings) grab(pointerBase(typ), bindings)
elif typ.hasPreservesRecordPragma: elif typ.hasPreservesRecordPragma:
var rec = DCompoundRec(label: typ.recordLabel.tosymbol(Ref)) var rec = DCompoundRec(label: typ.recordLabel.toSymbol)
rec.fields.setLen(fieldCount typ) rec.fields.setLen(fieldCount typ)
var i: int var i: int
for _, f in fieldPairs(default typ): for _, f in fieldPairs(default typ):
if not match(bindings, i, rec.fields[i]): rec.fields[i] = lookup(bindings, i)
rec.fields[i] = dropType(typeof f)
inc i inc i
result = rec.toPattern result = rec.toPattern
elif typ is tuple: elif typ is tuple:
@ -192,27 +211,33 @@ proc grab*(typ: static typedesc; bindings: sink openArray[(int, Pattern)]): Patt
arr.items.setLen(fieldCount typ) arr.items.setLen(fieldCount typ)
var i: int var i: int
for _, f in fieldPairs(default typ): for _, f in fieldPairs(default typ):
if not match(bindings, i, arr.items[i]): arr.items[i] = lookup(bindings, i)
arr.items[i] = dropType(typeof f)
inc i inc i
result = arr.toPattern result = arr.toPattern
else: 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 = proc grabLit*(): Pattern =
runnableExamples: runnableExamples:
from std/unittest import check from std/unittest import check
check: check:
$grabLit() == """<rec lit [<bind <_>>]>""" $grabLit() == """<rec lit [<bind <_>>]>"""
grabType(dataspacePatterns.DLit[void]) grabType(dataspacePatterns.DLit)
proc grabDict*(): Pattern = 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 result = pr
apply(result) do (pr: var Preserve[E]): apply(result) do (pr: var Value):
if pr.isRecord("lit", 1): if pr.isRecord("lit", 1) or pr.isRecord("dict", 1) or pr.isRecord("arr", 1) or pr.isRecord("set", 1):
pr = pr.record[0] pr = pr.record[0]
proc inject*(pat: Pattern; bindings: openArray[(int, Pattern)]): Pattern = proc inject*(pat: Pattern; bindings: openArray[(int, Pattern)]): Pattern =
@ -257,27 +282,98 @@ proc inject*(pat: Pattern; bindings: openArray[(int, Pattern)]): Pattern =
var offset = 0 var offset = 0
inject(pat, bindings, offset) 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: runnableExamples:
from std/unittest import check from std/unittest import check
import syndicate/actors, preserves import syndicate/actors, preserves
check: check:
$recordPattern("Says".toSymbol(Ref), grab(), grab()) == $grabRecord("Says".toSymbol, grab(), grab()) ==
"""<rec Says [<bind <_>> <bind <_>>]>""" """<rec Says [<bind <_>> <bind <_>>]>"""
DCompoundRec(label: label, fields: fields.toSeq).toPattern 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 type
Value = Preserve[Ref] Path* = seq[Value]
Path = seq[Value] Paths* = seq[Path]
Captures* = seq[Value]
Analysis* = tuple Analysis* = tuple
constPaths: seq[Path] constPaths: Paths
constValues: seq[Value] 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; p: Pattern)
func walk(result: var Analysis; path: var Path; key: int|Value; pat: 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) walk(result, path, pat)
discard path.pop discard path.pop
@ -290,51 +386,47 @@ func walk(result: var Analysis; path: var Path; p: Pattern) =
of DCompoundKind.arr: of DCompoundKind.arr:
for k, e in p.dcompound.arr.items: walk(result, path, k, e) for k, e in p.dcompound.arr.items: walk(result, path, k, e)
of DCompoundKind.dict: 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: of PatternKind.DBind:
result.capturePaths.add(path) result.capturePaths.add(path)
walk(result, path, p.dbind.pattern) walk(result, path, p.dbind.pattern)
of PatternKind.DDiscard: discard of PatternKind.DDiscard: discard
of PatternKind.DLit: of PatternKind.DLit:
result.constPaths.add(path) result.constPaths.add(path)
result.constValues.add(p.dlit.value.toPreserve(Ref)) result.constValues.add(p.dlit.value.toPreserves)
func analyse*(p: Pattern): Analysis = func analyse*(p: Pattern): Analysis =
var path: Path var path: Path
walk(result, path, p) walk(result, path, p)
func projectPath*(v: Value; path: Path): Option[Value] = func projectPaths*(v: Value; paths: Paths): Option[Captures] =
result = some(v) var res = newSeq[Value](paths.len)
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)
for i, path in paths: for i, path in paths:
var vv = projectPath(v, path) var vv = step(v, path)
if vv.isSome: result[i] = get(vv) 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) let analysis = analyse(pat)
assert analysis.constPaths.len == analysis.constValues.len assert analysis.constPaths.len == analysis.constValues.len
for i, path in analysis.constPaths: for i, path in analysis.constPaths:
let v = projectPath(pr, path) let v = step(pr, path)
if v.isNone : return false if v.isNone: return false
if analysis.constValues[i] != v.get: return false if analysis.constValues[i] != v.get: return false
for path in analysis.capturePaths: for path in analysis.capturePaths:
if isNone projectPath(pr, path): return false if isNone step(pr, path): return false
true true
func capture*(pat: Pattern; pr: Value): seq[Value] = func capture*(pat: Pattern; pr: Value): seq[Value] =
let analysis = analyse(pat) let analysis = analyse(pat)
assert analysis.constPaths.len == analysis.constValues.len assert analysis.constPaths.len == analysis.constValues.len
for i, path in analysis.constPaths: for i, path in analysis.constPaths:
let v = projectPath(pr, path) let v = step(pr, path)
if v.isNone : return @[] if v.isNone : return @[]
if analysis.constValues[i] != v.get: return @[] if analysis.constValues[i] != v.get: return @[]
for path in analysis.capturePaths: for path in analysis.capturePaths:
let v = projectPath(pr, path) let v = step(pr, path)
if v.isNone: return @[] if v.isNone: return @[]
result.add(get v) result.add(get v)

View File

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

View File

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

View File

@ -3,106 +3,101 @@ import
preserves preserves
type type
Bind*[Cap] {.preservesRecord: "bind".} = object Bind* {.preservesRecord: "bind".} = object
`description`*: Description[Cap] `description`*: Description
`target`*: Cap `target`* {.preservesEmbedded.}: EmbeddedRef
`observer`*: BindObserver[Cap] `observer`*: BindObserver
Route*[Cap] {.preservesRecord: "route".} = object Route* {.preservesRecord: "route".} = object
`transports`*: seq[Preserve[Cap]] `transports`*: seq[Value]
`pathSteps`* {.preservesTupleTail.}: seq[PathStep[Cap]] `pathSteps`* {.preservesTupleTail.}: seq[PathStep]
BindObserverKind* {.pure.} = enum BindObserverKind* {.pure.} = enum
`present`, `absent` `present`, `absent`
BindObserverPresent*[Cap] = Cap `BindObserver`* {.preservesOr.} = object
`BindObserver`*[Cap] {.preservesOr.} = object
case orKind*: BindObserverKind case orKind*: BindObserverKind
of BindObserverKind.`present`: of BindObserverKind.`present`:
`present`*: BindObserverPresent[Cap] `present`* {.preservesEmbedded.}: EmbeddedRef
of BindObserverKind.`absent`: of BindObserverKind.`absent`:
`absent`* {.preservesLiteral: "#f".}: bool `absent`* {.preservesLiteral: "#f".}: bool
TransportConnection*[Cap] {.preservesRecord: "connect-transport".} = object TransportConnection* {.preservesRecord: "connect-transport".} = object
`addr`*: Preserve[Cap] `addr`*: Value
`control`*: Cap `control`* {.preservesEmbedded.}: EmbeddedRef
`resolved`*: Resolved[Cap] `resolved`*: Resolved
Step*[Cap] = Preserve[Cap] Step* = Value
ResolvedPathStep*[Cap] {.preservesRecord: "path-step".} = object ResolvedPathStep* {.preservesRecord: "path-step".} = object
`origin`*: Cap `origin`* {.preservesEmbedded.}: EmbeddedRef
`pathStep`*: PathStep[Cap] `pathStep`*: PathStep
`resolved`*: Resolved[Cap] `resolved`*: Resolved
BoundKind* {.pure.} = enum BoundKind* {.pure.} = enum
`bound`, `Rejected` `bound`, `Rejected`
BoundBound*[Cap] {.preservesRecord: "bound".} = object BoundBound* {.preservesRecord: "bound".} = object
`pathStep`*: PathStep[Cap] `pathStep`*: PathStep
`Bound`*[Cap] {.preservesOr.} = object `Bound`* {.preservesOr.} = object
case orKind*: BoundKind case orKind*: BoundKind
of BoundKind.`bound`: of BoundKind.`bound`:
`bound`*: BoundBound[Cap] `bound`*: BoundBound
of BoundKind.`Rejected`: of BoundKind.`Rejected`:
`rejected`*: Rejected[Cap] `rejected`*: Rejected
ForceDisconnect* {.preservesRecord: "force-disconnect".} = object ForceDisconnect* {.preservesRecord: "force-disconnect".} = object
Description*[Cap] = Preserve[Cap] Description* = Value
Rejected*[Cap] {.preservesRecord: "rejected".} = object Rejected* {.preservesRecord: "rejected".} = object
`detail`*: Preserve[Cap] `detail`*: Value
Resolve*[Cap] {.preservesRecord: "resolve".} = object Resolve* {.preservesRecord: "resolve".} = object
`step`*: Step[Cap] `step`*: Step
`observer`*: Cap `observer`* {.preservesEmbedded.}: EmbeddedRef
ResolvedKind* {.pure.} = enum ResolvedKind* {.pure.} = enum
`accepted`, `Rejected` `accepted`, `Rejected`
ResolvedAccepted*[Cap] {.preservesRecord: "accepted".} = object ResolvedAccepted* {.preservesRecord: "accepted".} = object
`responderSession`*: Cap `responderSession`* {.preservesEmbedded.}: EmbeddedRef
`Resolved`*[Cap] {.preservesOr.} = object `Resolved`* {.preservesOr.} = object
case orKind*: ResolvedKind case orKind*: ResolvedKind
of ResolvedKind.`accepted`: of ResolvedKind.`accepted`:
`accepted`*: ResolvedAccepted[Cap] `accepted`* {.preservesEmbedded.}: ResolvedAccepted
of ResolvedKind.`Rejected`: of ResolvedKind.`Rejected`:
`rejected`*: Rejected[Cap] `rejected`*: Rejected
TransportControl* = ForceDisconnect TransportControl* = ForceDisconnect
ResolvePath*[Cap] {.preservesRecord: "resolve-path".} = object ResolvePath* {.preservesRecord: "resolve-path".} = object
`route`*: Route[Cap] `route`*: Route
`addr`*: Preserve[Cap] `addr`*: Value
`control`*: Cap `control`* {.preservesEmbedded.}: EmbeddedRef
`resolved`*: Resolved[Cap] `resolved`*: Resolved
PathStep*[Cap] = Preserve[Cap] PathStep* = Value
proc `$`*[Cap](x: Bind[Cap] | Route[Cap] | BindObserver[Cap] | proc `$`*(x: Bind | Route | BindObserver | TransportConnection |
TransportConnection[Cap] | ResolvedPathStep |
ResolvedPathStep[Cap] | Bound |
Bound[Cap] | ForceDisconnect |
Rejected[Cap] | Rejected |
Resolve[Cap] | Resolve |
Resolved[Cap] | Resolved |
ResolvePath[Cap]): string = TransportControl |
`$`(toPreserve(x, Cap)) ResolvePath): string =
`$`(toPreserves(x))
proc encode*[Cap](x: Bind[Cap] | Route[Cap] | BindObserver[Cap] | proc encode*(x: Bind | Route | BindObserver | TransportConnection |
TransportConnection[Cap] | ResolvedPathStep |
ResolvedPathStep[Cap] | Bound |
Bound[Cap] | ForceDisconnect |
Rejected[Cap] | Rejected |
Resolve[Cap] | Resolve |
Resolved[Cap] | Resolved |
ResolvePath[Cap]): seq[byte] = TransportControl |
encode(toPreserve(x, Cap)) ResolvePath): seq[byte] =
encode(toPreserves(x))
proc `$`*(x: ForceDisconnect | TransportControl): string =
`$`(toPreserve(x))
proc encode*(x: ForceDisconnect | TransportControl): seq[byte] =
encode(toPreserve(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 type
Error* {.preservesRecord: "error".} = object Error* {.preservesRecord: "error".} = object
`message`*: string `message`*: string
`detail`*: Preserve[void] `detail`*: Value
Turn* = seq[TurnEvent] Turn* = seq[TurnEvent]
Message* {.preservesRecord: "message".} = object Message* {.preservesRecord: "message".} = object
@ -18,23 +18,23 @@ type
`assertion`*: Assertion `assertion`*: Assertion
`handle`*: Handle `handle`*: Handle
Extension* = Preserve[void] Extension* = Value
Sync* {.preservesRecord: "sync".} = object Sync* {.preservesRecord: "sync".} = object
`peer`* {.preservesLiteral: "#!<lit #t>".}: tuple[] `peer`* {.preservesEmbedded.}: Value
TurnEvent* {.preservesTuple.} = object TurnEvent* {.preservesTuple.} = object
`oid`*: Oid `oid`*: Oid
`event`*: Event `event`*: Event
Oid* = BiggestInt Oid* = BiggestInt
Assertion* = Preserve[void] Assertion* = Value
Handle* = BiggestInt Handle* = BiggestInt
PacketKind* {.pure.} = enum PacketKind* {.pure.} = enum
`Turn`, `Error`, `Extension` `Turn`, `Error`, `Extension`
`Packet`* {.preservesOr.} = object `Packet`* {.preservesOr.} = object
case orKind*: PacketKind case orKind*: PacketKind
of PacketKind.`Turn`: of PacketKind.`Turn`:
`turn`*: Turn `turn`* {.preservesEmbedded.}: Turn
of PacketKind.`Error`: of PacketKind.`Error`:
`error`*: Error `error`*: Error
@ -57,18 +57,18 @@ type
`message`*: Message `message`*: Message
of EventKind.`Sync`: of EventKind.`Sync`:
`sync`*: Sync `sync`* {.preservesEmbedded.}: Sync
proc `$`*(x: Error | Turn | Message | Retract | Assert | Sync | TurnEvent | Oid | proc `$`*(x: Error | Turn | Message | Retract | Assert | Sync | TurnEvent | Oid |
Handle | Handle |
Packet | Packet |
Event): string = Event): string =
`$`(toPreserve(x)) `$`(toPreserves(x))
proc encode*(x: Error | Turn | Message | Retract | Assert | Sync | TurnEvent | proc encode*(x: Error | Turn | Message | Retract | Assert | Sync | TurnEvent |
Oid | Oid |
Handle | Handle |
Packet | Packet |
Event): seq[byte] = Event): seq[byte] =
encode(toPreserve(x)) encode(toPreserves(x))

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,9 +1,10 @@
# SPDX-FileCopyrightText: ☭ Emery Hemingway # SPDX-FileCopyrightText: ☭ Emery Hemingway
# SPDX-License-Identifier: Unlicense # SPDX-License-Identifier: Unlicense
import std/[asyncdispatch, options, streams, tables] import std/[asyncdispatch, options, tables]
from std/os import getEnv, `/`
import preserves 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(traceSyndicate):
when defined(posix): when defined(posix):
@ -15,20 +16,21 @@ else:
export `$` export `$`
type Oid = sturdy.Oid type
Oid = sturdy.Oid
export Stdio, Tcp, WebSocket, Unix
type type
Value = Preserve[void] Assertion = Value
Assertion = Preserve[Ref] WireRef = sturdy.WireRef
WireRef = sturdy.WireRef[void] 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 Relay* = ref object
PacketWriter = proc (pkt: sink Packet): Future[void] {.gcsafe.}
RelaySetup = proc (turn: var Turn; relay: Relay) {.gcsafe.}
Relay* = ref object of RootObj
facet: Facet facet: Facet
inboundAssertions: Table[Handle, inboundAssertions: Table[Handle,
tuple[localHandle: Handle, imported: seq[WireSymbol]]] tuple[localHandle: Handle, imported: seq[WireSymbol]]]
@ -37,12 +39,13 @@ type
imported: Membrane imported: Membrane
nextLocalOid: Oid nextLocalOid: Oid
pendingTurn: protocol.Turn pendingTurn: protocol.Turn
wireBuf: BufferedDecoder
packetWriter: PacketWriter packetWriter: PacketWriter
untrusted: bool peer: Cap
SyncPeerEntity = ref object of Entity SyncPeerEntity = ref object of Entity
relay: Relay relay: Relay
peer: Ref peer: Cap
handleMap: Table[Handle, Handle] handleMap: Table[Handle, Handle]
e: WireSymbol e: WireSymbol
@ -51,7 +54,7 @@ type
label: string label: string
relay: Relay relay: Relay
proc releaseRefOut(r: Relay; e: WireSymbol) = proc releaseCapOut(r: Relay; e: WireSymbol) =
r.exported.drop e r.exported.drop e
method publish(spe: SyncPeerEntity; t: var Turn; a: AssertionRef; h: Handle) = 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) = method message(se: SyncPeerEntity; t: var Turn; a: AssertionRef) =
if not se.e.isNil: if not se.e.isNil:
se.relay.releaseRefOut(se.e) se.relay.releaseCapOut(se.e)
message(t, se.peer, a.value) 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) sync(t, se.peer, peer)
proc newSyncPeerEntity(r: Relay; p: Ref): SyncPeerEntity = proc newSyncPeerEntity(r: Relay; p: Cap): SyncPeerEntity =
SyncPeerEntity(relay: r, peer: p) SyncPeerEntity(relay: r, peer: p)
proc rewriteRefOut(relay: Relay; `ref`: Ref; exported: var seq[WireSymbol]): WireRef = proc rewriteCapOut(relay: Relay; cap: Cap; exported: var seq[WireSymbol]): WireRef =
if `ref`.target of RelayEntity and `ref`.target.RelayEntity.relay == relay and `ref`.attenuation.len == 0: if cap.target of RelayEntity and cap.target.RelayEntity.relay == relay and cap.attenuation.len == 0:
WireRef(orKind: WireRefKind.yours, yours: WireRefYours[void](oid: `ref`.target.oid)) result = WireRef(orKind: WireRefKind.yours, yours: WireRefYours(oid: cap.target.oid))
else: else:
var ws = grab(relay.exported, `ref`) var ws = grab(relay.exported, cap)
if ws.isNil: if ws.isNil:
ws = newWireSymbol(relay.exported, relay.nextLocalOid, `ref`) ws = newWireSymbol(relay.exported, relay.nextLocalOid, cap)
inc relay.nextLocalOid inc relay.nextLocalOid
exported.add ws exported.add ws
WireRef( result = WireRef(
orKind: WireRefKind.mine, orKind: WireRefKind.mine,
mine: WireRefMine(oid: ws.oid)) mine: WireRefMine(oid: ws.oid))
proc rewriteOut(relay: Relay; v: Assertion): proc rewriteOut(relay: Relay; v: Assertion):
tuple[rewritten: Value, exported: seq[WireSymbol]] {.gcsafe.} = tuple[rewritten: Value, exported: seq[WireSymbol]] {.gcsafe.} =
var exported: seq[WireSymbol] var exported: seq[WireSymbol]
result.rewritten = contract(v) do (r: Ref) -> Value: result.rewritten = mapEmbeds(v) do (pr: Value) -> Value:
rewriteRefOut(relay, r, exported).toPreserve let o = pr.unembed(Cap); if o.isSome:
rewriteCapOut(relay, o.get, exported).toPreserves
else: pr
result.exported = exported result.exported = exported
proc register(relay: Relay; v: Assertion; h: Handle): tuple[rewritten: Value, exported: seq[WireSymbol]] = 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) = proc deregister(relay: Relay; h: Handle) =
var outbound: seq[WireSymbol] var outbound: seq[WireSymbol]
if relay.outboundAssertions.pop(h, outbound): 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] = proc send(relay: Relay; turn: var Turn; rOid: protocol.Oid; m: Event) =
assert(not r.packetWriter.isNil, "missing packetWriter proc") if relay.pendingTurn.len == 0:
r.packetWriter(pkt) # If the pending queue is empty then schedule a packet
# to be sent after pending I/O is processed.
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
callSoon do (): callSoon do ():
r.facet.run do (turn: var Turn): relay.facet.run do (turn: var Turn):
var pkt = Packet( var pkt = Packet(
orKind: PacketKind.Turn, orKind: PacketKind.Turn,
turn: move r.pendingTurn) turn: move relay.pendingTurn)
trace "C: ", pkt trace "C: ", pkt
asyncCheck(turn, r.send(pkt)) relay.packetWriter(turn, encode pkt)
r.pendingTurn.add TurnEvent(oid: rOid, event: m) relay.pendingTurn.add TurnEvent(oid: rOid, event: m)
proc send(re: RelayEntity; ev: Event) = proc send(re: RelayEntity; turn: var Turn; ev: Event) =
send(re.relay, protocol.Oid re.oid, ev) send(re.relay, turn, protocol.Oid re.oid, ev)
method publish(re: RelayEntity; t: var Turn; a: AssertionRef; h: Handle) {.gcsafe.} = method publish(re: RelayEntity; t: var Turn; a: AssertionRef; h: Handle) {.gcsafe.} =
re.send Event( re.send(t, Event(
orKind: EventKind.Assert, orKind: EventKind.Assert,
`assert`: protocol.Assert( `assert`: protocol.Assert(
assertion: re.relay.register(a.value, h).rewritten, assertion: re.relay.register(a.value, h).rewritten,
handle: h)) handle: h)))
method retract(re: RelayEntity; t: var Turn; h: Handle) {.gcsafe.} = method retract(re: RelayEntity; t: var Turn; h: Handle) {.gcsafe.} =
re.relay.deregister h re.relay.deregister h
re.send Event( re.send(t, Event(
orKind: EventKind.Retract, orKind: EventKind.Retract,
retract: Retract(handle: h)) retract: Retract(handle: h)))
method message(re: RelayEntity; turn: var Turn; msg: AssertionRef) {.gcsafe.} = method message(re: RelayEntity; turn: var Turn; msg: AssertionRef) {.gcsafe.} =
var (value, exported) = rewriteOut(re.relay, msg.value) var (value, exported) = rewriteOut(re.relay, msg.value)
assert(len(exported) == 0, "cannot send a reference in a message") assert(len(exported) == 0, "cannot send a reference in a message")
if len(exported) == 0: 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 var
peerEntity = newSyncPeerEntity(re.relay, peer) peerEntity = newSyncPeerEntity(re.relay, peer)
exported: seq[WireSymbol] exported: seq[WireSymbol]
discard rewriteRefOut(re.relay, turn.newRef(peerEntity), exported) wr = rewriteCapOut(re.relay, turn.newCap(peerEntity), exported)
# TODO: discard?
peerEntity.e = exported[0] 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 = proc newRelayEntity(label: string; r: Relay; o: Oid): RelayEntity =
RelayEntity(label: label, relay: r, oid: o) RelayEntity(label: label, relay: r, oid: o)
@ -157,24 +159,26 @@ using
relay: Relay relay: Relay
facet: Facet facet: Facet
proc lookupLocal(relay; oid: Oid): Ref = proc lookupLocal(relay; oid: Oid): Cap =
let sym = relay.exported.grab oid let sym = relay.exported.grab oid
if sym.isNil: newInertRef() if sym.isNil: newInertCap()
else: sym.`ref` else: sym.cap
proc isInert(r: Ref): bool = proc isInert(r: Cap): bool =
r.target.isNil 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 case n.orKind
of WireRefKind.mine: of WireRefKind.mine:
var e = relay.imported.grab(n.mine.oid) var e = relay.imported.grab(n.mine.oid)
if e.isNil: e = newWireSymbol( if e.isNil:
relay.imported, e = newWireSymbol(
n.mine.oid, relay.imported,
newRef(facet, newRelayEntity("rewriteRefIn", relay, n.mine.oid))) n.mine.oid,
newCap(facet, newRelayEntity("rewriteCapIn", relay, n.mine.oid)),
)
imported.add e imported.add e
result = e.`ref` result = e.cap
of WireRefKind.yours: of WireRefKind.yours:
let r = relay.lookupLocal(n.yours.oid) let r = relay.lookupLocal(n.yours.oid)
if n.yours.attenuation.len == 0 or r.isInert: result = r 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): proc rewriteIn(relay; facet; v: Value):
tuple[rewritten: Assertion; imported: seq[WireSymbol]] {.gcsafe.} = tuple[rewritten: Assertion; imported: seq[WireSymbol]] {.gcsafe.} =
var imported: seq[WireSymbol] var imported: seq[WireSymbol]
result.rewritten = expand(v) do (pr: Value) -> Assertion: result.rewritten = mapEmbeds(v) do (pr: Value) -> Value:
var wr: WireRef let wr = pr.preservesTo WireRef; if wr.isSome:
if not fromPreserve(wr, pr): result = rewriteCapIn(relay, facet, wr.get, imported).embed
raiseAssert "expansion of embedded value failed" else:
rewriteRefIn(relay, facet, wr, imported).toPreserve(Ref) result = pr
result.imported = imported result.imported = imported
proc close(r: Relay) = discard 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 case event.orKind
of EventKind.Assert: of EventKind.Assert:
let (a, imported) = rewriteIn(relay, turn.facet, event.assert.assertion) 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: of EventKind.Retract:
let remoteHandle = event.retract.handle let remoteHandle = event.retract.handle
@ -208,23 +212,23 @@ proc dispatch*(relay: Relay; turn: var Turn; `ref`: Ref; event: Event) {.gcsafe.
of EventKind.Message: of EventKind.Message:
let (a, imported) = rewriteIn(relay, turn.facet, event.message.body) let (a, imported) = rewriteIn(relay, turn.facet, event.message.body)
assert imported.len == 0, "Cannot receive transient reference" assert imported.len == 0, "Cannot receive transient reference"
turn.message(`ref`, a) turn.message(cap, a)
of EventKind.Sync: of EventKind.Sync:
discard # TODO discard # TODO
#[ #[
var imported: seq[WireSymbol] var imported: seq[WireSymbol]
let k = relay.rewriteRefIn(turn, evenr.sync.peer, imported) let k = relay.rewriteCapIn(turn, evenr.sync.peer, imported)
turn.sync(`ref`) do (turn: var Turn): turn.sync(cap) do (turn: var Turn):
turn.message(k, true) turn.message(k, true)
for e in imported: relay.imported.del e 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 trace "S: ", v
run(relay.facet) do (t: var Turn): run(relay.facet) do (t: var Turn):
var pkt: Packet var pkt: Packet
if fromPreserve(pkt, v): if pkt.fromPreserves(v):
case pkt.orKind case pkt.orKind
of PacketKind.Turn: of PacketKind.Turn:
# https://synit.org/book/protocol.html#turn-packets # https://synit.org/book/protocol.html#turn-packets
@ -233,7 +237,7 @@ proc dispatch*(relay: Relay; v: Value) {.gcsafe.} =
if not r.isInert: if not r.isInert:
dispatch(relay, t, r, te.event) dispatch(relay, t, r, te.event)
else: else:
stderr.writeLine("discarding event for unknown Ref; ", te.event) stderr.writeLine("discarding event for unknown Cap; ", te.event)
of PacketKind.Error: of PacketKind.Error:
# https://synit.org/book/protocol.html#error-packets # https://synit.org/book/protocol.html#error-packets
when defined(posix): when defined(posix):
@ -246,172 +250,451 @@ proc dispatch*(relay: Relay; v: Value) {.gcsafe.} =
when defined(posix): when defined(posix):
stderr.writeLine("discarding undecoded packet ", v) 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 type
RelayOptions* = object of RootObj RelayOptions* = object of RootObj
packetWriter*: PacketWriter packetWriter*: PacketWriter
untrusted*: bool
RelayActorOptions* = object of RelayOptions RelayActorOptions* = object of RelayOptions
initialOid*: Option[Oid] initialOid*: Option[Oid]
initialRef*: Ref initialCap*: Cap
nextLocalOid*: Option[Oid] nextLocalOid*: Option[Oid]
proc newRelay(turn: var Turn; opts: RelayOptions; setup: RelaySetup): Relay = proc spawnRelay(name: string; turn: var Turn; opts: RelayActorOptions; setup: RelaySetup) =
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"
spawn(name, turn) do (turn: var Turn): spawn(name, turn) do (turn: var Turn):
let relay = newRelay(turn, opts, setup) let relay = Relay(
if not opts.initialRef.isNil: facet: turn.facet,
packetWriter: opts.packetWriter,
wireBuf: newBufferedDecoder(0),
)
discard relay.facet.preventInertCheck()
if not opts.initialCap.isNil:
var exported: seq[WireSymbol] var exported: seq[WireSymbol]
discard rewriteRefOut(relay, opts.initialRef, exported) discard rewriteCapOut(relay, opts.initialCap, 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)
opts.nextLocalOid.map do (oid: Oid): opts.nextLocalOid.map do (oid: Oid):
relay.nextLocalOid = relay.nextLocalOid =
if oid == 0.Oid: 1.Oid if oid == 0.Oid: 1.Oid
else: 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): 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 import std/asyncfile
export Unix
const stdinReadSize = 128 type StdioControlEntity = ref object of Entity
stdin: AsyncFile
proc connectStdio*(ds: Ref; turn: var Turn) = method message(entity: StdioControlEntity; turn: var Turn; ass: AssertionRef) =
## Connect to an external dataspace over stdin and stdout. if ass.value.preservesTo(ForceDisconnect).isSome:
proc stdoutWriter(packet: sink Packet): Future[void] {.async.} = close(entity.stdin)
var buf = encode(packet) close(stdout)
doAssert writeBytes(stdout, buf, 0, buf.len) == buf.len
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) flushFile(stdout)
if n != buf.len:
stopActor(turn)
var opts = RelayActorOptions( var opts = RelayActorOptions(
packetWriter: stdoutWriter, packetWriter: stdoutWriter,
initialRef: ds, initialCap: ds,
initialOid: 0.Oid.some) initialOid: 0.Oid.some,
asyncCheck spawnRelay("stdio", turn, opts) do (turn: var Turn; relay: Relay): )
spawnRelay("stdio", turn, opts) do (turn: var Turn; relay: Relay):
let let
facet = turn.facet facet = turn.facet
asyncStdin = openAsync("/dev/stdin") # this is universal now? asyncStdin = openAsync("/dev/stdin") # this is universal now?
close(stdin) publish(turn, ds, TransportConnection(
facet.actor.atExit do (turn: var Turn): `addr`: ta.toPreserves,
close(asyncStdin) control: StdioControlEntity(stdin: asyncStdin).newCap(turn),
var wireBuf = newBufferedDecoder() resolved: relay.peer.accepted,
))
const stdinReadSize = 0x2000
proc readCb(pktFut: Future[string]) {.gcsafe.} = proc readCb(pktFut: Future[string]) {.gcsafe.} =
if not pktFut.failed: if not pktFut.failed:
var buf = pktFut.read var buf = pktFut.read
if buf.len == 0: if buf.len == 0:
run(facet) do (turn: var Turn): stopActor(turn) run(facet) do (turn: var Turn): stopActor(turn)
else: else:
feed(wireBuf, buf) relay.recv(cast[seq[byte]](buf))
var (success, pr) = decode(wireBuf)
if success:
dispatch(relay, pr)
asyncStdin.read(stdinReadSize).addCallback(readCb) asyncStdin.read(stdinReadSize).addCallback(readCb)
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 # 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 preserves
import ./actors, ./bags, ./patterns import ./actors, ./bags, ./patterns
import ./protocols/dataspacePatterns import ./protocols/dataspacePatterns
type type
DCompound = dataspacePatterns.DCompound[Ref] DCompound = dataspacePatterns.DCompound
Pattern = dataspacePatterns.Pattern[Ref] Pattern = dataspacePatterns.Pattern
Value = Preserve[Ref]
Path = seq[Value] Path = seq[Value]
ClassKind = enum classNone, classRecord, classSequence, classDictionary ClassKind = enum classNone, classRecord, classSequence, classDictionary
Class = object Class = object
@ -43,71 +44,106 @@ type
AssertionCache = HashSet[Value] AssertionCache = HashSet[Value]
Paths = seq[Path]
ObserverGroup = ref object # Endpoints ObserverGroup = ref object # Endpoints
cachedCaptures: Bag[seq[Value]] cachedCaptures: Bag[Captures]
observers: Table[Ref, TableRef[seq[Value], Handle]] observers: Table[Cap, TableRef[Captures, Handle]]
Leaf = ref object Leaf = ref object
cachedAssertions: AssertionCache cache: AssertionCache
observerGroups: Table[Paths, ObserverGroup] observerGroups: Table[Paths, ObserverGroup]
LeafMap = TableRef[seq[Value], Leaf]
Continuation = ref object Continuation = ref object
cachedAssertions: AssertionCache cache: AssertionCache
leafMap: Table[Paths, TableRef[seq[Value], Leaf]] leafMap: Table[Paths, LeafMap]
Selector = tuple[popCount: int; index: Value]
Node = ref object
edges: Table[Selector, TableRef[Class, Node]]
continuation: Continuation
func isEmpty(leaf: Leaf): bool = 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 type
ContinuationProc = proc (c: Continuation; v: Value) {.gcsafe.} ContinuationProc = proc (c: Continuation; v: Value) {.gcsafe.}
LeafProc = proc (l: Leaf; v: Value) {.gcsafe.} LeafProc = proc (l: Leaf; v: Value) {.gcsafe.}
ObserverProc = proc (turn: var Turn; group: ObserverGroup; vs: seq[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 = proc push(stack: TermStack; val: Value): Termstack =
result = stack result = stack
prepend(result, val) add(result, val)
proc pop(stack: TermStack; n: int): TermStack = proc pop(stack: TermStack; n: int): TermStack =
result = stack assert n <= stack.len
var n = n stack[stack.low..(stack.high-n)]
while n > 0:
result.remove(result.head)
assert not stack.head.isNil, "popped too far"
dec n
proc top(stack: TermStack): Value = proc top(stack: TermStack): Value =
assert not stack.head.isNil, "stack is empty" assert stack.len > 0
stack.head.value stack[stack.high]
proc modify(node: Node; turn: var Turn; outerValue: Value; event: EventKind; proc modify(node: Node; turn: var Turn; outerValue: Value; event: EventKind;
modCont: ContinuationProc; modLeaf: LeafProc; modObs: ObserverProc) = 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) modCont(cont, outerValue)
for constPaths, constValMap in cont.leafMap.pairs: for constPaths, constValMap in cont.leafMap.pairs:
let constVals = projectPaths(outerValue, constPaths) let constVals = projectPaths(outerValue, constPaths)
var leaf = constValMap.getOrDefault(constVals) if constVals.isSome:
if leaf.isNil and event == addedEvent: case event
new leaf of addedEvent, messageEvent:
constValMap[constVals] = leaf let leaf = constValMap.getLeaf(get constVals)
if not leaf.isNil: modLeaf(leaf, outerValue)
modLeaf(leaf, outerValue) for capturePaths, observerGroup in leaf.observerGroups.pairs:
for capturePaths, observerGroup in leaf.observerGroups.pairs: let captures = projectPaths(outerValue, capturePaths)
modObs(turn, observerGroup, projectPaths(outerValue, capturePaths)) if captures.isSome:
# TODO: cleanup dead leaves 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: for selector, table in node.edges:
let let
nextStack = pop(termStack, selector.popCount) nextStack = pop(termStack, selector.popCount)
@ -117,10 +153,11 @@ proc modify(node: Node; turn: var Turn; outerValue: Value; event: EventKind;
if nextClass.kind != classNone: if nextClass.kind != classNone:
let nextNode = table.getOrDefault(nextClass) let nextNode = table.getOrDefault(nextClass)
if not nextNode.isNil: 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].toPreserves])
walk(node, turn, outerValue, event, push(stack, @[outerValue].toPreserve(Ref)))
proc getOrNew[A, B, C](t: var Table[A, TableRef[B, C]], k: A): TableRef[B, C] = proc getOrNew[A, B, C](t: var Table[A, TableRef[B, C]], k: A): TableRef[B, C] =
result = t.getOrDefault(k) result = t.getOrDefault(k)
@ -132,10 +169,10 @@ iterator pairs(dc: DCompound): (Value, Pattern) =
case dc.orKind case dc.orKind
of DCompoundKind.rec: of DCompoundKind.rec:
for i, p in dc.rec.fields: for i, p in dc.rec.fields:
yield (toPreserve(i, Ref), p,) yield (i.toPreserves, p,)
of DCompoundKind.arr: of DCompoundKind.arr:
for i, p in dc.arr.items: for i, p in dc.arr.items:
yield (toPreserve(i, Ref), p,) yield (i.toPreserves, p,)
of DCompoundKind.dict: of DCompoundKind.dict:
for pair in dc.dict.entries.pairs: for pair in dc.dict.entries.pairs:
yield pair 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) result = extendWalk(node, popCount, stepIndex, pat.dbind.pattern, path)
of PatternKind.DCompound: of PatternKind.DCompound:
let let
class = classOf pat
selector: Selector = (popCount, stepIndex,) selector: Selector = (popCount, stepIndex,)
table = node.edges.getOrNew(selector) table = node.edges.getOrNew(selector)
class = classOf pat
result.nextNode = table.getOrDefault(class) result.nextNode = table.getOrDefault(class)
if result.nextNode.isNil: if result.nextNode.isNil:
new result.nextNode new result.nextNode
table[class] = result.nextNode table[class] = result.nextNode
new result.nextNode.continuation new result.nextNode.continuation
for a in node.continuation.cachedAssertions: for a in node.continuation.cache:
var v = projectPath(a, path) var v = step(a, path)
if v.isSome and class == classOf(get v): if v.isSome and class == classOf(get v):
result.nextNode.continuation.cachedAssertions.incl a result.nextNode.continuation.cache.incl a
for i, p in pat.dcompound.pairs: result.popCount = 0
add(path, i) for step, p in pat.dcompound.pairs:
result = extendWalk(result.nextNode, result.popCount, i, p, path) add(path, step)
result = extendWalk(result.nextNode, result.popCount, step, p, path)
discard pop(path) discard pop(path)
inc(result.popCount) inc(result.popCount)
proc extend(node: var Node; pat: Pattern): Continuation = proc extend(node: var Node; pat: Pattern): Continuation =
var path: Path var path: Path
extendWalk(node, 0, toPreserve(0, Ref), pat, path).nextNode.continuation extendWalk(node, 0, 0.toPreserves, pat, path).nextNode.continuation
type type
Index* = object Index* = object
allAssertions: Bag[Value] allAssertions: Bag[Value]
root: Node root: Node
observerCount: int
proc initIndex*(): Index = proc initIndex*(): Index =
Index(root: Node(continuation: Continuation())) Index(root: Node(continuation: Continuation()))
proc add*(index: var Index; turn: var Turn; pattern: Pattern; observer: Ref) = proc getEndpoints(leaf: Leaf; capturePaths: Paths): ObserverGroup =
let result = leaf.observerGroups.getOrDefault(capturePaths)
analysis = analyse pattern if result.isNil:
continuation = index.root.extend pattern new result
var constValMap = continuation.leafMap.getOrDefault(analysis.constPaths) leaf.observerGroups[capturePaths] = result
if constValMap.isNil: for term in leaf.cache:
new constValMap # leaf.cache would be empty if observers come before assertions
for a in continuation.cachedAssertions: let captures = projectPaths(term, capturePaths)
let key = projectPaths(a, analysis.constPaths) if captures.isSome:
var leaf = constValMap.getOrDefault(key) discard result.cachedCaptures.change(get captures, +1)
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 remove*(index: var Index; turn: var Turn; pattern: Pattern; observer: Ref) = proc add*(index: var Index; turn: var Turn; pattern: Pattern; observer: Cap) =
var let
cont = index.root.extend(pattern)
analysis = analyse pattern analysis = analyse pattern
continuation = index.root.extend pattern constValMap = cont.getLeaves(analysis.constPaths)
let constValMap = continuation.leafMap.getOrDefault(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: if not constValMap.isNil:
let leaf = constValMap.getOrDefault(analysis.constValues) let leaf = constValMap.getOrDefault(analysis.constValues)
if not leaf.isNil: if not leaf.isNil:
let observerGroup = leaf.observerGroups.getOrDefault(analysis.capturePaths) let endpoints = leaf.observerGroups.getOrDefault(analysis.capturePaths)
if not observerGroup.isNil: if not endpoints.isNil:
let captureMap = observerGroup.observers.getOrDefault(observer) var captureMap: TableRef[seq[Value], Handle]
if not captureMap.isNil: if endpoints.observers.pop(observer, captureMap):
for handle in captureMap.values: retract(observer.target, turn, handle) for handle in captureMap.values: retract(turn, handle)
observerGroup.observers.del(observer) if endpoints.observers.len == 0:
if observerGroup.observers.len == 0:
leaf.observerGroups.del(analysis.capturePaths) leaf.observerGroups.del(analysis.capturePaths)
if leaf.isEmpty: if leaf.observerGroups.len == 0:
constValMap.del(analysis.constValues) constValMap.del(analysis.constValues)
if constValMap.len == 0: if constValMap.len == 0:
continuation.leafMap.del(analysis.constPaths) 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) case index.allAssertions.change(outerValue, delta)
of cdAbsentToPresent: of cdAbsentToPresent:
result = true result = true
proc modContinuation(c: Continuation; v: Value) = proc modContinuation(c: Continuation; v: Value) =
c.cachedAssertions.incl(v) c.cache.incl(v)
proc modLeaf(l: Leaf; v: Value) = proc modLeaf(l: Leaf; v: Value) =
l.cachedAssertions.incl(v) l.cache.incl(v)
proc modObserver(turn: var Turn; group: ObserverGroup; vs: seq[Value]) = 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: for (observer, captureMap) in group.observers.pairs:
let a = vs.toPreserve(Ref) captureMap[vs] = publish(turn, observer, vs.toPreserves)
captureMap[vs] = publish(turn, observer, a)
# TODO: this handle is coming from the facet? # TODO: this handle is coming from the facet?
modify(index.root, turn, outerValue, addedEvent, modContinuation, modLeaf, modObserver) modify(index.root, turn, outerValue, addedEvent, modContinuation, modLeaf, modObserver)
of cdPresentToAbsent: of cdPresentToAbsent:
result = true result = true
proc modContinuation(c: Continuation; v: Value) = proc modContinuation(c: Continuation; v: Value) =
c.cachedAssertions.excl(v) c.cache.excl(v)
proc modLeaf(l: Leaf; v: Value) = proc modLeaf(l: Leaf; v: Value) =
l.cachedAssertions.excl(v) l.cache.excl(v)
proc modObserver(turn: var Turn; group: ObserverGroup; vs: seq[Value]) = proc modObserver(turn: var Turn; group: ObserverGroup; vs: seq[Value]) =
if group.cachedCaptures.change(vs, -1) == cdPresentToAbsent: if group.cachedCaptures.change(vs, -1) == cdPresentToAbsent:
for (observer, captureMap) in group.observers.pairs: for (observer, captureMap) in group.observers.pairs:
retract(observer.target, turn, captureMap[vs]) var h: Handle
captureMap.del(vs) if captureMap.take(vs, h):
retract(observer.target, turn, h)
modify(index.root, turn, outerValue, removedEvent, modContinuation, modLeaf, modObserver) modify(index.root, turn, outerValue, removedEvent, modContinuation, modLeaf, modObserver)
else: discard else: discard
proc continuationNoop(c: Continuation; v: Value) = discard proc continuationNoop(c: Continuation; v: Value) = discard
proc leafNoop(l: Leaf; 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) 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) adjustAssertion(index, turn, v, -1)
proc deliverMessage*(index: var Index; turn: var Turn; v: Value) = proc deliverMessage*(index: var Index; turn: var Turn; v: Value) =

View File

@ -1,6 +1,6 @@
# Package # Package
version = "20230608" version = "20240120"
author = "Emery Hemingway" author = "Emery Hemingway"
description = "Syndicated actors for conversational concurrency" description = "Syndicated actors for conversational concurrency"
license = "Unlicense" license = "Unlicense"
@ -9,4 +9,4 @@ srcDir = "src"
# Dependencies # 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 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-FileCopyrightText: ☭ Emery Hemingway
# SPDX-License-Identifier: Unlicense # SPDX-License-Identifier: Unlicense
import std/[asyncdispatch, asyncfile, os, parseopt] import std/[asyncdispatch, asyncfile, parseopt]
import preserves, syndicate, syndicate/protocols/transportAddress import preserves, syndicate, syndicate/relays
type type
Present {.preservesRecord: "Present".} = object Present {.preservesRecord: "Present".} = object
@ -10,7 +10,7 @@ type
Says {.preservesRecord: "Says".} = object Says {.preservesRecord: "Says".} = object
who, what: string who, what: string
proc readStdin(facet: Facet; ds: Ref; username: string) = proc readStdin(facet: Facet; ds: Cap; username: string) =
let file = openAsync("/dev/stdin") let file = openAsync("/dev/stdin")
onStop(facet) do (turn: var Turn): close(file) onStop(facet) do (turn: var Turn): close(file)
close(stdin) close(stdin)
@ -18,49 +18,39 @@ proc readStdin(facet: Facet; ds: Ref; username: string) =
let future = readLine(file) let future = readLine(file)
addCallback(future, facet) do (turn: var Turn): addCallback(future, facet) do (turn: var Turn):
var msg = read(future) var msg = read(future)
if msg == "": quit()
message(turn, ds, Says(who: username, what: msg)) message(turn, ds, Says(who: username, what: msg))
readLine() readLine()
readLine() readLine()
proc chat(turn: var Turn; ds: Ref; username: string) = proc chat(turn: var Turn; ds: Cap; username: string) =
during(turn, ds, ?Present) do (who: string): during(turn, ds, ?:Present) do (who: string):
echo who, " joined" echo who, " joined"
do: do:
echo who, " left" echo who, " left"
onMessage(turn, ds, ?Says) do (who: string, what: string): onMessage(turn, ds, ?:Says) do (who: string, what: string):
echo who, ": ", what echo who, ": ", what
discard publish(turn, ds, Present(username: username)) discard publish(turn, ds, Present(username: username))
readStdin(turn.facet, ds, username) readStdin(turn.facet, ds, username)
proc main = proc main =
var let route = envRoute()
transport: Preserve[void] var username = ""
cap: Preserve[Ref]
username = getEnv("USER")
calledWithArguments = false
for kind, key, val in getopt(): for kind, key, val in getopt():
calledWithArguments = true
if kind == cmdLongOption: if kind == cmdLongOption:
case key case key
of "address", "transport":
transport = parsePreserves(val)
of "cap", "sturdy":
cap = parsePreserves(val, Ref)
of "user", "username": of "user", "username":
username = val username = val
if calledWithArguments: if username == "":
runActor("chat") do (root: Ref; turn: var Turn): stderr.writeLine "--user: unspecified"
var else:
unixAddr: transportAddress.Unix runActor("chat") do (turn: var Turn; root: Cap):
tcpAddr: transportAddress.Tcp spawnRelays(turn, root)
if fromPreserve(unixAddr, transport): resolve(turn, root, route) do (turn: var Turn; ds: Cap):
connect(turn, unixAddr, cap) do (turn: var Turn; ds: Ref): chat(turn, ds, username)
chat(turn, ds, username)
elif fromPreserve(tcpAddr, transport):
connect(turn, tcpAddr, cap) do (turn: var Turn; ds: Ref):
chat(turn, ds, username)
main() 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 preserves
import syndicate/relays import syndicate/relays
import syndicate/protocols/[protocol, sturdy] import syndicate/protocols/sturdy
type WireRef = sturdy.WireRef[void] type WireRef = sturdy.WireRef
suite "protocols": suite "protocols":
test "PDiscard": test "PDiscard":
@ -20,9 +20,9 @@ suite "protocols":
var pos = str.getPosition var pos = str.getPosition
echo "decode position: ", pos echo "decode position: ", pos
try: try:
var a = decodePreserves(str, WireRef) var a = decodePreserves(str)
echo a echo a
except: except CatchableError:
str.setPosition pos str.setPosition pos
echo str.readAll.toHex echo str.readAll.toHex
break 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)