Compare commits

..

18 Commits
trunk ... cps

Author SHA1 Message Date
Emery Hemingway 98580c2bb6 Failed continuafication 2024-03-01 09:33:34 +00:00
Emery Hemingway 18609d01ce actors: do not execute actions when turn fails 2024-02-29 09:47:36 +00:00
Emery Hemingway 0a7d129991 Cleanup timer example 2024-02-28 19:46:38 +00:00
Emery Hemingway 3ea91ecd24 Different cps for await timer 2024-02-28 19:29:11 +00:00
Emery Hemingway 3d4b6e0bbc multiple timers 2024-02-28 19:29:11 +00:00
Emery Hemingway 75d8d6d3bf Shared timers 2024-02-28 19:29:11 +00:00
Emery Hemingway 7bf9b3fe48 fixup! Hide Entity action callbacks 2024-02-28 19:29:11 +00:00
Emery Hemingway f54d9ae402 skeletons: early conversion of seq[Value] to Value 2024-02-28 19:29:11 +00:00
Emery Hemingway 9f59bb1e94 Hide Entity action callbacks 2024-02-28 19:29:11 +00:00
Emery Hemingway 9eae178723 CPS refactor 2024-02-28 19:29:11 +00:00
Emery Hemingway a8a17b4978 Rename syndicate directory to sam 2024-02-28 19:28:49 +00:00
Emery Hemingway fe268dea8d have you ever tried making a cps function that takes a closure as an argument? 2024-02-16 13:21:28 +00:00
Emery Hemingway da93f9eb1f Goto trick 2024-02-16 12:58:17 +00:00
Emery Hemingway b83ef93484 Handler attempt 2024-02-15 19:50:11 +00:00
Emery Hemingway b2f680a215 Need an onStop 2024-02-15 14:34:13 +00:00
Emery Hemingway ff1e1e6231 WiP! Continuification 2024-02-15 13:42:58 +00:00
Emery Hemingway d5d5717976 WiP! Continuationification 2024-02-15 10:52:12 +00:00
Emery Hemingway c519d909ab WiP! Depend on nim-sys 2024-02-10 14:00:43 +00:00
63 changed files with 4180 additions and 3925 deletions

View File

@ -54,8 +54,7 @@ The Syndicate DSL can be entered using `runActor` which calls a Nim body with a
### Publish
``` nim
runActor("main") do (turn: Turn):
let dataspace = newDataspace()
runActor("main") do (dataspace: Ref; turn: var Turn):
let presenceHandle = publish(turn, dataspace, Present(username: "Judy"))
# publish <Present "Judy"> to the dataspace
# the assertion can be later retracted by handle
@ -68,8 +67,7 @@ runActor("main") do (turn: Turn):
We can react to assertions and messages within dataspaces using [patterns](https://synit.org/book/glossary.html#dataspace-pattern). Patterns are constructed using a Nim type and the `?` operator. Again a Nim type is used rather than a raw Preserves for schema consistency.
``` nim
runActor("main") do (turn: Turn):
let dataspace = newDataspace()
runActor("main") do (dataspace: Ref; turn: var Turn):
during(turn, dataspace, ?Present) do (who: string):
# This body is active when the ?Present pattern is matched.
# The Present type contains two atomic values that can be matched
@ -91,15 +89,17 @@ runActor("main") do (turn: Turn):
## Examples
### [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).
```sh
SYNDICATE_ROUTE='<route [<unix "/run/user/1000/dataspace">] [<ref {oid: "syndicate" sig: #x"69ca300c1dbfa08fba692102dd82311a"}>]>' nim c -r tests/test_chat.nim --user:fnord
```
### [syndicate_utils](https://git.syndicate-lang.org/ehmry/syndicate_utils)
### [xdg_open_ng](https://git.syndicate-lang.org/ehmry/xdg_open_ng)
Messaging, UNIX sockets, dynamic configuration, [Syndicate server](https://synit.org/book/operation/system-bus.html) interaction.
---
This work has been supported by the [NLnet Foundation](https://nlnet.nl/) and the European Commission's [Next Generation Internet programme](https://www.ngi.eu/). The [Syndicate Actor Model](https://syndicate-lang.org/projects/2021/system-layer/) through the [NGI Zero PET](https://nlnet.nl/PET/) program and this library as a part of the [ERIS project](https://eris.codeberg.page/) through [NGI Assure](https://nlnet.nl/assure/).
[![NLnet](./nlnet.svg)](https://nlnet.nl/)
[![NLnet](https://nlnet.nl/logo/banner.svg)](https://nlnet.nl/)

View File

@ -1,2 +1,3 @@
include_rules
: sbom.json |> !sbom-to-nix |> | ./<lock>
: |> !nim_lk |> {lockfile}
: {lockfile} |> !nim_cfg |> | ./<lock>

View File

@ -1,4 +1,2 @@
PROJECT_DIR = $(TUP_CWD)
NIM_GROUPS += $(TUP_CWD)/<lock>
include depends.tup
NIM_GROUPS += $(TUP_CWD)/<lock>

View File

@ -1,6 +1,3 @@
include ../preserves-nim/depends.tup
NIM = $(DIRENV) $(NIM)
NIM_FLAGS += --path:$(TUP_CWD)/src
NIM_FLAGS += --path:$(TUP_CWD)/../preserves-nim/src
NIM_GROUPS += $(TUP_CWD)/<protocol>
NIM_GROUPS += $(TUP_CWD)/<schema>
NIM_LOCK_EXCLUDES += "syndicate"

92
lock.json Normal file
View File

@ -0,0 +1,92 @@
{
"depends": [
{
"method": "fetchzip",
"packages": [
"bigints"
],
"path": "/nix/store/jvrm392g8adfsgf36prgwkbyd7vh5jsw-source",
"rev": "86ea14d31eea9275e1408ca34e6bfe9c99989a96",
"sha256": "15pcpmnk1bnw3k8769rjzcpg00nahyrypwbxs88jnwr4aczp99j4",
"srcDir": "src",
"url": "https://github.com/ehmry/nim-bigints/archive/86ea14d31eea9275e1408ca34e6bfe9c99989a96.tar.gz"
},
{
"method": "fetchzip",
"packages": [
"cps"
],
"path": "/nix/store/m9vpcf3dq6z2h1xpi1vlw0ycxp91s5p7-source",
"rev": "2a4d771a715ba45cfba3a82fa625ae7ad6591c8b",
"sha256": "0c62k5wpq9z9mn8cd4rm8jjc4z0xmnak4piyj5dsfbyj6sbdw2bf",
"srcDir": "",
"url": "https://github.com/nim-works/cps/archive/2a4d771a715ba45cfba3a82fa625ae7ad6591c8b.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/jwz8pqbv6rsm8w4fjzdb37r0wzjn5hv0-source",
"rev": "d58da671799c69c0b3208b96c154e13c8b1a9e90",
"sha256": "12dm0gsy10ppga7zf7hpf4adaqjrd9b740n2w926xyazq1njf6k9",
"srcDir": "",
"url": "https://github.com/cheatfate/nimcrypto/archive/d58da671799c69c0b3208b96c154e13c8b1a9e90.tar.gz"
},
{
"method": "fetchzip",
"packages": [
"npeg"
],
"path": "/nix/store/ffkxmjmigfs7zhhiiqm0iw2c34smyciy-source",
"rev": "26d62fdc40feb84c6533956dc11d5ee9ea9b6c09",
"sha256": "0xpzifjkfp49w76qmaylan8q181bs45anmp46l4bwr3lkrr7bpwh",
"srcDir": "src",
"url": "https://github.com/zevv/npeg/archive/26d62fdc40feb84c6533956dc11d5ee9ea9b6c09.tar.gz"
},
{
"method": "fetchzip",
"packages": [
"preserves"
],
"path": "/nix/store/6nnn5di5vip1vladlb7z56rbw18d1y7j-source",
"rev": "2825bceecf33a15b9b7942db5331a32cbc39b281",
"sha256": "145vf46fy3wc52j6vs509fm9bi5lx7c53gskbkpcfbkv82l86dgk",
"srcDir": "src",
"url": "https://git.syndicate-lang.org/ehmry/preserves-nim/archive/2825bceecf33a15b9b7942db5331a32cbc39b281.tar.gz"
},
{
"method": "fetchzip",
"packages": [
"stew"
],
"path": "/nix/store/mqg8qzsbcc8xqabq2yzvlhvcyqypk72c-source",
"rev": "3c91b8694e15137a81ec7db37c6c58194ec94a6a",
"sha256": "17lfhfxp5nxvld78xa83p258y80ks5jb4n53152cdr57xk86y07w",
"srcDir": "",
"url": "https://github.com/status-im/nim-stew/archive/3c91b8694e15137a81ec7db37c6c58194ec94a6a.tar.gz"
},
{
"method": "fetchzip",
"packages": [
"sys"
],
"path": "/nix/store/ayplzmq7xdzrp3n6ly6dnskf5c5aiihp-source",
"rev": "3b86a5083a4aa178994fe4ffdc046d340aa13b32",
"sha256": "0qz9hag7synp8sx2b6caazm2kidvd0lv2p0h98sslkyzaf4icnal",
"srcDir": "src",
"url": "https://github.com/alaviss/nim-sys/archive/3b86a5083a4aa178994fe4ffdc046d340aa13b32.tar.gz"
}
]
}

View File

@ -1,45 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:serif="http://www.serif.com/" width="272" height="104" version="1.1" xml:space="preserve" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<g transform="scale(0.25)">
<g>
<g>
<path d="M211.345,190.214c-1.137,4.372 -5.39,7.182 -9.858,6.513c-22.854,-3.548 -44.571,-15.066 -60.591,-34.158c-35.111,-41.843 -29.645,-104.32 12.199,-139.431c41.843,-35.111 104.32,-29.645 139.431,12.198c16.02,19.092 23.592,42.479 23.117,65.603c-0.117,4.516 -3.623,8.215 -8.126,8.575c-4.029,0.353 -8.046,0.928 -12.032,1.727c-2.662,0.545 -5.427,-0.168 -7.494,-1.933c-2.066,-1.764 -3.204,-4.383 -3.084,-7.098c0.798,-16.564 -4.394,-33.46 -15.884,-47.152c-24.226,-28.872 -67.335,-32.644 -96.207,-8.418c-28.872,24.227 -32.644,67.336 -8.417,96.208c11.489,13.693 27.226,21.74 43.677,23.832c2.694,0.353 5.075,1.928 6.454,4.269c1.378,2.341 1.6,5.187 0.602,7.714c-1.477,3.787 -2.741,7.643 -3.787,11.551Z" style="fill:#74aa00;"/>
<path d="M211.306,68.301c16.918,-2.983 33.074,8.33 36.057,25.247c2.983,16.917 -8.33,33.074 -25.247,36.057c-16.918,2.983 -33.074,-8.33 -36.057,-25.248c-2.983,-16.917 8.33,-33.073 25.247,-36.056Z"/>
</g>
<g>
<path d="M224.072,211.345c-4.372,-1.137 -7.183,-5.39 -6.513,-9.858c3.547,-22.854 15.066,-44.571 34.158,-60.591c41.843,-35.111 104.32,-29.645 139.431,12.199c35.111,41.843 29.645,104.32 -12.199,139.431c-19.091,16.02 -42.479,23.592 -65.602,23.117c-4.516,-0.117 -8.216,-3.623 -8.576,-8.126c-0.352,-4.029 -0.927,-8.046 -1.726,-12.032c-0.545,-2.662 0.168,-5.427 1.933,-7.494c1.764,-2.066 4.383,-3.204 7.098,-3.084c16.564,0.798 33.459,-4.394 47.152,-15.884c28.872,-24.226 32.644,-67.335 8.417,-96.207c-24.226,-28.872 -67.335,-32.644 -96.207,-8.417c-13.693,11.489 -21.74,27.226 -23.833,43.677c-0.352,2.694 -1.927,5.075 -4.268,6.454c-2.342,1.378 -5.188,1.6 -7.714,0.602c-3.787,-1.477 -7.644,-2.741 -11.551,-3.787Z" style="fill:#74aa00;"/>
<path d="M345.985,211.306c2.983,16.918 -8.33,33.074 -25.247,36.057c-16.918,2.983 -33.074,-8.33 -36.057,-25.247c-2.983,-16.918 8.33,-33.074 25.247,-36.057c16.918,-2.983 33.074,8.33 36.057,25.247Z"/>
</g>
<g>
<path d="M202.941,224.072c1.136,-4.372 5.389,-7.183 9.857,-6.513c22.855,3.547 44.572,15.066 60.592,34.158c35.11,41.843 29.644,104.32 -12.199,139.431c-41.843,35.111 -104.32,29.645 -139.431,-12.199c-16.02,-19.091 -23.593,-42.479 -23.117,-65.602c0.117,-4.516 3.622,-8.216 8.126,-8.576c4.029,-0.352 8.046,-0.927 12.032,-1.726c2.662,-0.545 5.427,0.168 7.493,1.933c2.067,1.764 3.205,4.383 3.084,7.098c-0.798,16.564 4.395,33.459 15.884,47.152c24.227,28.872 67.336,32.644 96.208,8.417c28.872,-24.226 32.643,-67.335 8.417,-96.207c-11.49,-13.693 -27.227,-21.74 -43.678,-23.833c-2.694,-0.352 -5.075,-1.927 -6.453,-4.268c-1.379,-2.342 -1.601,-5.188 -0.602,-7.714c1.477,-3.787 2.741,-7.644 3.787,-11.551Z" style="fill:#74aa00;"/>
<path d="M202.979,345.985c-16.917,2.983 -33.073,-8.33 -36.056,-25.247c-2.983,-16.918 8.329,-33.074 25.247,-36.057c16.917,-2.983 33.074,8.33 36.057,25.247c2.983,16.918 -8.33,33.074 -25.248,36.057Z"/>
</g>
<g>
<path d="M190.214,202.941c4.372,1.136 7.182,5.389 6.513,9.857c-3.548,22.855 -15.066,44.572 -34.158,60.592c-41.843,35.11 -104.32,29.644 -139.431,-12.199c-35.111,-41.843 -29.645,-104.32 12.198,-139.431c19.092,-16.02 42.479,-23.593 65.603,-23.117c4.516,0.117 8.215,3.622 8.575,8.126c0.353,4.029 0.928,8.046 1.727,12.032c0.545,2.662 -0.168,5.427 -1.933,7.493c-1.764,2.067 -4.383,3.205 -7.098,3.084c-16.564,-0.798 -33.46,4.395 -47.152,15.884c-28.872,24.227 -32.644,67.336 -8.418,96.208c24.227,28.872 67.336,32.643 96.208,8.417c13.693,-11.49 21.74,-27.227 23.832,-43.678c0.353,-2.694 1.928,-5.075 4.269,-6.453c2.341,-1.379 5.187,-1.601 7.714,-0.602c3.787,1.477 7.643,2.741 11.551,3.787Z" style="fill:#74aa00;"/>
<path d="M68.301,202.979c-2.983,-16.917 8.33,-33.073 25.247,-36.056c16.917,-2.983 33.074,8.329 36.057,25.247c2.983,16.917 -8.33,33.074 -25.248,36.057c-16.917,2.983 -33.073,-8.33 -36.056,-25.248Z"/>
</g>
</g>
<g>
<g>
<path d="M467.416,259.709c-1.713,-0 -3.169,-0.6 -4.368,-1.799c-1.199,-1.199 -1.798,-2.654 -1.798,-4.367l0,-121.259c0,-1.713 0.599,-3.169 1.798,-4.368c1.199,-1.199 2.655,-1.798 4.368,-1.798l21.066,-0c1.713,-0 3.168,0.599 4.367,1.798c1.199,1.199 1.799,2.655 1.799,4.368l-0,10.533c4.453,-5.481 10.147,-10.062 17.084,-13.745c6.936,-3.682 15.543,-5.523 25.819,-5.523c10.447,-0 19.525,2.355 27.232,7.065c7.707,4.71 13.659,11.346 17.855,19.91c4.196,8.563 6.294,18.84 6.294,30.829l0,72.19c0,1.713 -0.599,3.168 -1.798,4.367c-1.199,1.199 -2.655,1.799 -4.368,1.799l-22.607,-0c-1.713,-0 -3.169,-0.6 -4.368,-1.799c-1.199,-1.199 -1.798,-2.654 -1.798,-4.367l-0,-70.649c-0,-9.934 -2.441,-17.727 -7.322,-23.378c-4.881,-5.652 -11.946,-8.478 -21.195,-8.478c-8.906,-0 -16.013,2.826 -21.323,8.478c-5.309,5.651 -7.964,13.444 -7.964,23.378l0,70.649c0,1.713 -0.599,3.168 -1.798,4.367c-1.199,1.199 -2.655,1.799 -4.368,1.799l-22.607,-0Z" style="fill:#231f20;fill-rule:nonzero;"/>
<path d="M627.982,259.709c-1.713,-0 -3.169,-0.6 -4.368,-1.799c-1.199,-1.199 -1.798,-2.654 -1.798,-4.367l-0,-170.071c-0,-1.713 0.599,-3.169 1.798,-4.368c1.199,-1.199 2.655,-1.798 4.368,-1.798l21.323,-0c1.713,-0 3.168,0.599 4.367,1.798c1.199,1.199 1.799,2.655 1.799,4.368l-0,170.071c-0,1.713 -0.6,3.168 -1.799,4.367c-1.199,1.199 -2.654,1.799 -4.367,1.799l-21.323,-0Z" style="fill:#231f20;fill-rule:nonzero;"/>
<path d="M695.805,259.709c-1.713,-0 -3.169,-0.6 -4.368,-1.799c-1.199,-1.199 -1.798,-2.654 -1.798,-4.367l0,-121.259c0,-1.713 0.599,-3.169 1.798,-4.368c1.199,-1.199 2.655,-1.798 4.368,-1.798l21.066,-0c1.713,-0 3.169,0.599 4.367,1.798c1.199,1.199 1.799,2.655 1.799,4.368l-0,10.533c4.453,-5.481 10.147,-10.062 17.084,-13.745c6.936,-3.682 15.543,-5.523 25.819,-5.523c10.447,-0 19.525,2.355 27.232,7.065c7.707,4.71 13.659,11.346 17.855,19.91c4.196,8.563 6.294,18.84 6.294,30.829l0,72.19c0,1.713 -0.599,3.168 -1.798,4.367c-1.199,1.199 -2.655,1.799 -4.368,1.799l-22.607,-0c-1.713,-0 -3.169,-0.6 -4.368,-1.799c-1.199,-1.199 -1.798,-2.654 -1.798,-4.367l-0,-70.649c-0,-9.934 -2.441,-17.727 -7.322,-23.378c-4.881,-5.652 -11.946,-8.478 -21.195,-8.478c-8.906,-0 -16.013,2.826 -21.323,8.478c-5.309,5.651 -7.964,13.444 -7.964,23.378l0,70.649c0,1.713 -0.599,3.168 -1.798,4.367c-1.199,1.199 -2.655,1.799 -4.368,1.799l-22.607,-0Z" style="fill:#74aa00;fill-rule:nonzero;"/>
<path d="M907.495,262.278c-19.011,-0 -34.083,-5.481 -45.215,-16.442c-11.133,-10.961 -17.128,-26.547 -17.984,-46.757c-0.171,-1.713 -0.257,-3.896 -0.257,-6.551c0,-2.655 0.086,-4.753 0.257,-6.294c0.685,-13.017 3.64,-24.192 8.863,-33.526c5.224,-9.335 12.46,-16.528 21.709,-21.581c9.248,-5.052 20.124,-7.578 32.627,-7.578c13.873,-0 25.519,2.869 34.939,8.606c9.42,5.738 16.528,13.702 21.323,23.892c4.796,10.191 7.194,21.966 7.194,35.325l-0,5.395c-0,1.713 -0.6,3.168 -1.799,4.367c-1.199,1.199 -2.74,1.799 -4.624,1.799l-85.293,-0l0,2.055c0.172,5.994 1.328,11.518 3.469,16.57c2.14,5.053 5.309,9.12 9.505,12.203c4.196,3.083 9.206,4.625 15.029,4.625c4.796,-0 8.82,-0.728 12.075,-2.184c3.254,-1.456 5.908,-3.126 7.964,-5.01c2.055,-1.884 3.511,-3.425 4.367,-4.624c1.541,-2.055 2.783,-3.297 3.725,-3.725c0.942,-0.428 2.355,-0.642 4.239,-0.642l22.094,-0c1.713,-0 3.126,0.513 4.239,1.541c1.113,1.028 1.584,2.312 1.413,3.854c-0.171,2.74 -1.584,6.08 -4.239,10.019c-2.655,3.939 -6.466,7.793 -11.432,11.561c-4.967,3.768 -11.176,6.893 -18.626,9.377c-7.45,2.483 -15.971,3.725 -25.562,3.725Zm-28.26,-80.925l56.776,-0l0,-0.771c0,-6.68 -1.113,-12.546 -3.339,-17.598c-2.227,-5.053 -5.481,-8.992 -9.763,-11.818c-4.282,-2.826 -9.42,-4.239 -15.414,-4.239c-5.995,0 -11.133,1.413 -15.414,4.239c-4.282,2.826 -7.494,6.765 -9.634,11.818c-2.141,5.052 -3.212,10.918 -3.212,17.598l0,0.771Z" style="fill:#74aa00;fill-rule:nonzero;"/>
<path d="M1058.04,259.709c-10.277,-0 -18.926,-1.799 -25.948,-5.395c-7.022,-3.597 -12.246,-8.949 -15.671,-16.057c-3.426,-7.108 -5.138,-15.971 -5.138,-26.59l-0,-58.317l-20.296,-0c-1.713,-0 -3.168,-0.6 -4.367,-1.799c-1.199,-1.198 -1.799,-2.74 -1.799,-4.624l0,-14.643c0,-1.713 0.6,-3.169 1.799,-4.368c1.199,-1.199 2.654,-1.798 4.367,-1.798l20.296,-0l-0,-42.646c-0,-1.713 0.556,-3.169 1.67,-4.368c1.113,-1.199 2.611,-1.798 4.495,-1.798l20.81,-0c1.713,-0 3.168,0.599 4.367,1.798c1.199,1.199 1.799,2.655 1.799,4.368l-0,42.646l32.113,-0c1.712,-0 3.168,0.599 4.367,1.798c1.199,1.199 1.798,2.655 1.798,4.368l0,14.643c0,1.884 -0.599,3.426 -1.798,4.624c-1.199,1.199 -2.655,1.799 -4.367,1.799l-32.113,-0l-0,55.748c-0,7.022 1.241,12.503 3.725,16.442c2.483,3.939 6.808,5.909 12.973,5.909l17.727,0c1.713,0 3.168,0.6 4.367,1.798c1.199,1.199 1.799,2.655 1.799,4.368l-0,15.928c-0,1.713 -0.6,3.168 -1.799,4.367c-1.199,1.199 -2.654,1.799 -4.367,1.799l-20.809,-0Z" style="fill:#74aa00;fill-rule:nonzero;"/>
</g>
<g>
<path d="M489.774,297.042c0.412,0 0.769,0.15 1.069,0.45c0.3,0.3 0.45,0.657 0.45,1.069l-0,5.963c-0,0.412 -0.15,0.768 -0.45,1.068c-0.3,0.3 -0.657,0.45 -1.069,0.45l-16.819,0l0,7.707l15.694,-0c0.412,-0 0.769,0.15 1.069,0.45c0.3,0.3 0.45,0.656 0.45,1.068l-0,5.963c-0,0.412 -0.15,0.769 -0.45,1.069c-0.3,0.3 -0.657,0.45 -1.069,0.45l-15.694,-0l0,12.15c0,0.412 -0.15,0.768 -0.45,1.068c-0.3,0.3 -0.656,0.45 -1.069,0.45l-7.368,0c-0.413,0 -0.769,-0.15 -1.069,-0.45c-0.3,-0.3 -0.45,-0.656 -0.45,-1.068l-0,-36.338c-0,-0.412 0.15,-0.769 0.45,-1.069c0.3,-0.3 0.656,-0.45 1.069,-0.45l25.706,0Z" style="fill:#231f20;fill-rule:nonzero;"/>
<path d="M542.48,296.48c3.563,-0 6.638,0.675 9.225,2.025c2.588,1.35 4.585,3.178 5.991,5.484c1.406,2.307 2.184,4.885 2.334,7.735c0.075,1.05 0.113,2.737 0.113,5.062c-0,2.288 -0.038,3.938 -0.113,4.95c-0.15,2.85 -0.928,5.428 -2.334,7.735c-1.406,2.306 -3.403,4.134 -5.991,5.484c-2.587,1.35 -5.662,2.025 -9.225,2.025c-3.562,-0 -6.637,-0.675 -9.225,-2.025c-2.587,-1.35 -4.584,-3.178 -5.99,-5.484c-1.407,-2.307 -2.185,-4.885 -2.335,-7.735c-0.075,-2.025 -0.112,-3.675 -0.112,-4.95c-0,-1.275 0.037,-2.962 0.112,-5.062c0.15,-2.85 0.928,-5.428 2.335,-7.735c1.406,-2.306 3.403,-4.134 5.99,-5.484c2.588,-1.35 5.663,-2.025 9.225,-2.025Zm7.144,15.525c-0.188,-1.988 -0.872,-3.572 -2.053,-4.753c-1.181,-1.181 -2.878,-1.772 -5.091,-1.772c-2.212,-0 -3.909,0.591 -5.09,1.772c-1.182,1.181 -1.866,2.765 -2.054,4.753c-0.112,1.237 -0.168,2.794 -0.168,4.669c-0,1.837 0.056,3.431 0.168,4.781c0.188,1.987 0.872,3.572 2.054,4.753c1.181,1.181 2.878,1.772 5.09,1.772c2.213,-0 3.91,-0.591 5.091,-1.772c1.181,-1.181 1.865,-2.766 2.053,-4.753c0.112,-1.35 0.169,-2.944 0.169,-4.781c-0,-1.875 -0.057,-3.432 -0.169,-4.669Z" style="fill:#231f20;fill-rule:nonzero;"/>
<path d="M627.868,297.042c0.412,0 0.768,0.15 1.068,0.45c0.3,0.3 0.45,0.657 0.45,1.069l0,22.444c0,3.375 -0.712,6.262 -2.137,8.662c-1.425,2.4 -3.422,4.219 -5.991,5.457c-2.568,1.237 -5.559,1.856 -8.972,1.856c-3.45,-0 -6.459,-0.619 -9.028,-1.856c-2.568,-1.238 -4.556,-3.057 -5.962,-5.457c-1.406,-2.4 -2.11,-5.287 -2.11,-8.662l0,-22.444c0,-0.412 0.15,-0.769 0.45,-1.069c0.3,-0.3 0.657,-0.45 1.069,-0.45l7.369,0c0.412,0 0.769,0.15 1.069,0.45c0.3,0.3 0.45,0.657 0.45,1.069l-0,22.219c-0,2.287 0.572,4.059 1.715,5.316c1.144,1.256 2.803,1.884 4.978,1.884c2.175,-0 3.835,-0.628 4.979,-1.884c1.143,-1.257 1.715,-3.029 1.715,-5.316l0,-22.219c0,-0.412 0.15,-0.769 0.45,-1.069c0.3,-0.3 0.656,-0.45 1.069,-0.45l7.369,0Z" style="fill:#231f20;fill-rule:nonzero;"/>
<path d="M691.43,336.417c-0.562,0 -1.05,-0.14 -1.462,-0.421c-0.413,-0.282 -0.694,-0.572 -0.844,-0.872l-12.881,-19.238l-0,19.013c-0,0.412 -0.15,0.768 -0.45,1.068c-0.3,0.3 -0.657,0.45 -1.069,0.45l-7.369,0c-0.412,0 -0.769,-0.15 -1.069,-0.45c-0.3,-0.3 -0.45,-0.656 -0.45,-1.068l0,-36.338c0,-0.412 0.15,-0.769 0.45,-1.069c0.3,-0.3 0.657,-0.45 1.069,-0.45l5.85,0c0.563,0 1.05,0.141 1.463,0.422c0.412,0.282 0.693,0.572 0.843,0.872l12.882,19.238l-0,-19.013c-0,-0.412 0.15,-0.769 0.45,-1.069c0.3,-0.3 0.656,-0.45 1.068,-0.45l7.369,0c0.413,0 0.769,0.15 1.069,0.45c0.3,0.3 0.45,0.657 0.45,1.069l-0,36.338c-0,0.412 -0.15,0.768 -0.45,1.068c-0.3,0.3 -0.656,0.45 -1.069,0.45l-5.85,0Z" style="fill:#231f20;fill-rule:nonzero;"/>
<path d="M768.83,312.736c0.075,2.1 0.113,3.45 0.113,4.05c-0,0.6 -0.038,1.913 -0.113,3.938c-0.112,3.075 -0.909,5.793 -2.39,8.156c-1.482,2.362 -3.516,4.209 -6.104,5.541c-2.587,1.331 -5.587,1.996 -9,1.996l-15.187,0c-0.413,0 -0.769,-0.15 -1.069,-0.45c-0.3,-0.3 -0.45,-0.656 -0.45,-1.068l0,-36.338c0,-0.412 0.15,-0.769 0.45,-1.069c0.3,-0.3 0.656,-0.45 1.069,-0.45l14.906,0c3.413,0 6.441,0.666 9.085,1.997c2.643,1.332 4.725,3.188 6.243,5.569c1.519,2.381 2.335,5.091 2.447,8.128Zm-23.794,-6.694l0,21.375l6.019,0c4.725,0 7.181,-2.156 7.369,-6.468c0.075,-2.175 0.112,-3.6 0.112,-4.275c0,-0.75 -0.037,-2.138 -0.112,-4.163c-0.113,-2.137 -0.816,-3.75 -2.109,-4.837c-1.294,-1.088 -3.141,-1.632 -5.541,-1.632l-5.738,0Z" style="fill:#231f20;fill-rule:nonzero;"/>
<path d="M835.936,334.336c0.15,0.375 0.225,0.656 0.225,0.844c0,0.337 -0.121,0.628 -0.365,0.872c-0.244,0.244 -0.535,0.365 -0.872,0.365l-7.088,0c-0.637,0 -1.134,-0.14 -1.49,-0.421c-0.356,-0.282 -0.591,-0.61 -0.703,-0.985l-1.575,-4.669l-14.963,0l-1.575,4.669c-0.112,0.375 -0.347,0.703 -0.703,0.985c-0.356,0.281 -0.853,0.421 -1.491,0.421l-7.087,0c-0.338,0 -0.628,-0.121 -0.872,-0.365c-0.244,-0.244 -0.366,-0.535 -0.366,-0.872c0,-0.188 0.075,-0.469 0.225,-0.844l12.263,-35.437c0.187,-0.525 0.487,-0.966 0.9,-1.322c0.412,-0.356 0.956,-0.535 1.631,-0.535l9.113,0c0.675,0 1.218,0.179 1.631,0.535c0.412,0.356 0.712,0.797 0.9,1.322l12.262,35.437Zm-14.793,-12.712l-4.557,-13.5l-4.556,13.5l9.113,-0Z" style="fill:#231f20;fill-rule:nonzero;"/>
<path d="M898.599,297.042c0.412,0 0.769,0.15 1.069,0.45c0.3,0.3 0.45,0.657 0.45,1.069l-0,5.963c-0,0.412 -0.15,0.768 -0.45,1.068c-0.3,0.3 -0.657,0.45 -1.069,0.45l-9.9,0l-0,28.857c-0,0.412 -0.15,0.768 -0.45,1.068c-0.3,0.3 -0.656,0.45 -1.069,0.45l-7.369,0c-0.412,0 -0.768,-0.15 -1.068,-0.45c-0.3,-0.3 -0.45,-0.656 -0.45,-1.068l-0,-28.857l-9.9,0c-0.413,0 -0.769,-0.15 -1.069,-0.45c-0.3,-0.3 -0.45,-0.656 -0.45,-1.068l-0,-5.963c-0,-0.412 0.15,-0.769 0.45,-1.069c0.3,-0.3 0.656,-0.45 1.069,-0.45l30.206,0Z" style="fill:#231f20;fill-rule:nonzero;"/>
<path d="M935.668,336.417c-0.413,0 -0.769,-0.15 -1.069,-0.45c-0.3,-0.3 -0.45,-0.656 -0.45,-1.068l-0,-36.338c-0,-0.412 0.15,-0.769 0.45,-1.069c0.3,-0.3 0.656,-0.45 1.069,-0.45l7.368,0c0.413,0 0.769,0.15 1.069,0.45c0.3,0.3 0.45,0.657 0.45,1.069l0,36.338c0,0.412 -0.15,0.768 -0.45,1.068c-0.3,0.3 -0.656,0.45 -1.069,0.45l-7.368,0Z" style="fill:#231f20;fill-rule:nonzero;"/>
<path d="M997.655,296.48c3.563,-0 6.638,0.675 9.225,2.025c2.588,1.35 4.585,3.178 5.991,5.484c1.406,2.307 2.184,4.885 2.334,7.735c0.075,1.05 0.113,2.737 0.113,5.062c-0,2.288 -0.038,3.938 -0.113,4.95c-0.15,2.85 -0.928,5.428 -2.334,7.735c-1.406,2.306 -3.403,4.134 -5.991,5.484c-2.587,1.35 -5.662,2.025 -9.225,2.025c-3.562,-0 -6.637,-0.675 -9.225,-2.025c-2.587,-1.35 -4.584,-3.178 -5.99,-5.484c-1.407,-2.307 -2.185,-4.885 -2.335,-7.735c-0.075,-2.025 -0.112,-3.675 -0.112,-4.95c-0,-1.275 0.037,-2.962 0.112,-5.062c0.15,-2.85 0.928,-5.428 2.335,-7.735c1.406,-2.306 3.403,-4.134 5.99,-5.484c2.588,-1.35 5.663,-2.025 9.225,-2.025Zm7.144,15.525c-0.188,-1.988 -0.872,-3.572 -2.053,-4.753c-1.181,-1.181 -2.878,-1.772 -5.091,-1.772c-2.212,-0 -3.909,0.591 -5.09,1.772c-1.182,1.181 -1.866,2.765 -2.054,4.753c-0.112,1.237 -0.168,2.794 -0.168,4.669c-0,1.837 0.056,3.431 0.168,4.781c0.188,1.987 0.872,3.572 2.054,4.753c1.181,1.181 2.878,1.772 5.09,1.772c2.213,-0 3.91,-0.591 5.091,-1.772c1.181,-1.181 1.865,-2.766 2.053,-4.753c0.112,-1.35 0.169,-2.944 0.169,-4.781c-0,-1.875 -0.057,-3.432 -0.169,-4.669Z" style="fill:#231f20;fill-rule:nonzero;"/>
<path d="M1076.35,336.417c-0.563,0 -1.05,-0.14 -1.463,-0.421c-0.412,-0.282 -0.693,-0.572 -0.843,-0.872l-12.882,-19.238l0,19.013c0,0.412 -0.15,0.768 -0.45,1.068c-0.3,0.3 -0.656,0.45 -1.068,0.45l-7.369,0c-0.413,0 -0.769,-0.15 -1.069,-0.45c-0.3,-0.3 -0.45,-0.656 -0.45,-1.068l0,-36.338c0,-0.412 0.15,-0.769 0.45,-1.069c0.3,-0.3 0.656,-0.45 1.069,-0.45l5.85,0c0.562,0 1.05,0.141 1.462,0.422c0.413,0.282 0.694,0.572 0.844,0.872l12.881,19.238l0,-19.013c0,-0.412 0.15,-0.769 0.45,-1.069c0.3,-0.3 0.657,-0.45 1.069,-0.45l7.369,0c0.412,0 0.769,0.15 1.069,0.45c0.3,0.3 0.45,0.657 0.45,1.069l-0,36.338c-0,0.412 -0.15,0.768 -0.45,1.068c-0.3,0.3 -0.657,0.45 -1.069,0.45l-5.85,0Z" style="fill:#231f20;fill-rule:nonzero;"/>
</g>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 16 KiB

538
sbom.json
View File

@ -1,538 +0,0 @@
{
"bomFormat": "CycloneDX",
"specVersion": "1.6",
"metadata": {
"component": {
"type": "library",
"bom-ref": "pkg:nim/syndicate",
"name": "syndicate",
"description": "Syndicated actors for conversational concurrency",
"version": "20240628",
"authors": [
{
"name": "Emery Hemingway"
}
],
"licenses": [
{
"license": {
"id": "Unlicense"
}
}
],
"properties": [
{
"name": "nim:srcDir",
"value": "src"
},
{
"name": "nim:backend",
"value": "c"
}
]
}
},
"components": [
{
"type": "library",
"bom-ref": "pkg:nim/nimcrypto",
"name": "nimcrypto",
"version": "485f7b3cfa83c1beecc0e31be0e964d697aa74d7",
"externalReferences": [
{
"url": "https://github.com/cheatfate/nimcrypto/archive/485f7b3cfa83c1beecc0e31be0e964d697aa74d7.tar.gz",
"type": "source-distribution"
},
{
"url": "https://github.com/cheatfate/nimcrypto",
"type": "vcs"
}
],
"properties": [
{
"name": "nix:fod:method",
"value": "fetchzip"
},
{
"name": "nix:fod:path",
"value": "/nix/store/fkrcpp8lzj2yi21na79xm63xk0ggnqsp-source"
},
{
"name": "nix:fod:rev",
"value": "485f7b3cfa83c1beecc0e31be0e964d697aa74d7"
},
{
"name": "nix:fod:sha256",
"value": "1h3dzdbc9kacwpi10mj73yjglvn7kbizj1x8qc9099ax091cj5xn"
},
{
"name": "nix:fod:url",
"value": "https://github.com/cheatfate/nimcrypto/archive/485f7b3cfa83c1beecc0e31be0e964d697aa74d7.tar.gz"
}
]
},
{
"type": "library",
"bom-ref": "pkg:nim/preserves",
"name": "preserves",
"version": "20240610",
"externalReferences": [
{
"url": "https://git.syndicate-lang.org/ehmry/preserves-nim/archive/560a6417a30a2dff63f24b62498e9fcac2de8354.tar.gz",
"type": "source-distribution"
},
{
"url": "https://git.syndicate-lang.org/ehmry/preserves-nim.git",
"type": "vcs"
}
],
"properties": [
{
"name": "nix:fod:method",
"value": "fetchzip"
},
{
"name": "nix:fod:path",
"value": "/nix/store/0sszsmz84ppwqsgda8cmli4lfh2mjmin-source"
},
{
"name": "nix:fod:rev",
"value": "560a6417a30a2dff63f24b62498e9fcac2de8354"
},
{
"name": "nix:fod:sha256",
"value": "19r983fy7m54mlaj0adxdp8pxi1x8dp6phkcnr8rz5y5cwndfjx2"
},
{
"name": "nix:fod:url",
"value": "https://git.syndicate-lang.org/ehmry/preserves-nim/archive/560a6417a30a2dff63f24b62498e9fcac2de8354.tar.gz"
},
{
"name": "nix:fod:ref",
"value": "20240610"
},
{
"name": "nix:fod:srcDir",
"value": "src"
}
]
},
{
"type": "library",
"bom-ref": "pkg:nim/sys",
"name": "sys",
"version": "4ef3b624db86e331ba334e705c1aa235d55b05e1",
"externalReferences": [
{
"url": "https://github.com/ehmry/nim-sys/archive/4ef3b624db86e331ba334e705c1aa235d55b05e1.tar.gz",
"type": "source-distribution"
},
{
"url": "https://github.com/ehmry/nim-sys.git",
"type": "vcs"
}
],
"properties": [
{
"name": "nix:fod:method",
"value": "fetchzip"
},
{
"name": "nix:fod:path",
"value": "/nix/store/syhxsjlsdqfap0hk4qp3s6kayk8cqknd-source"
},
{
"name": "nix:fod:rev",
"value": "4ef3b624db86e331ba334e705c1aa235d55b05e1"
},
{
"name": "nix:fod:sha256",
"value": "1q4qgw4an4mmmcbx48l6xk1jig1vc8p9cq9dbx39kpnb0890j32q"
},
{
"name": "nix:fod:url",
"value": "https://github.com/ehmry/nim-sys/archive/4ef3b624db86e331ba334e705c1aa235d55b05e1.tar.gz"
},
{
"name": "nix:fod:srcDir",
"value": "src"
}
]
},
{
"type": "library",
"bom-ref": "pkg:nim/taps",
"name": "taps",
"version": "20240405",
"externalReferences": [
{
"url": "https://git.sr.ht/~ehmry/nim_taps/archive/8c8572cd971d1283e6621006b310993c632da247.tar.gz",
"type": "source-distribution"
},
{
"url": "https://git.sr.ht/~ehmry/nim_taps",
"type": "vcs"
}
],
"properties": [
{
"name": "nix:fod:method",
"value": "fetchzip"
},
{
"name": "nix:fod:path",
"value": "/nix/store/6y14ia52kr7jyaa0izx37mlablmq9s65-source"
},
{
"name": "nix:fod:rev",
"value": "8c8572cd971d1283e6621006b310993c632da247"
},
{
"name": "nix:fod:sha256",
"value": "1dp166bv9x773jmfqppg5i3v3rilgff013vb11yzwcid9l7s3iy8"
},
{
"name": "nix:fod:url",
"value": "https://git.sr.ht/~ehmry/nim_taps/archive/8c8572cd971d1283e6621006b310993c632da247.tar.gz"
},
{
"name": "nix:fod:ref",
"value": "20240405"
},
{
"name": "nix:fod:srcDir",
"value": "src"
}
]
},
{
"type": "library",
"bom-ref": "pkg:nim/solo5_dispatcher",
"name": "solo5_dispatcher",
"version": "20240522",
"externalReferences": [
{
"url": "https://git.sr.ht/~ehmry/solo5_dispatcher/archive/cc64ef99416b22b12e4a076d33de9e25a163e57d.tar.gz",
"type": "source-distribution"
},
{
"url": "https://git.sr.ht/~ehmry/solo5_dispatcher",
"type": "vcs"
}
],
"properties": [
{
"name": "nix:fod:method",
"value": "fetchzip"
},
{
"name": "nix:fod:path",
"value": "/nix/store/4jj467pg4hs6warhksb8nsxn9ykz8c7c-source"
},
{
"name": "nix:fod:rev",
"value": "cc64ef99416b22b12e4a076d33de9e25a163e57d"
},
{
"name": "nix:fod:sha256",
"value": "1v9i9fqgx1g76yrmz2xwj9mxfwbjfpar6dsyygr68fv9031cqxq7"
},
{
"name": "nix:fod:url",
"value": "https://git.sr.ht/~ehmry/solo5_dispatcher/archive/cc64ef99416b22b12e4a076d33de9e25a163e57d.tar.gz"
},
{
"name": "nix:fod:ref",
"value": "20240522"
},
{
"name": "nix:fod:srcDir",
"value": "pkg"
}
]
},
{
"type": "library",
"bom-ref": "pkg:nim/npeg",
"name": "npeg",
"version": "1.2.2",
"externalReferences": [
{
"url": "https://github.com/zevv/npeg/archive/ec0cc6e64ea4c62d2aa382b176a4838474238f8d.tar.gz",
"type": "source-distribution"
},
{
"url": "https://github.com/zevv/npeg.git",
"type": "vcs"
}
],
"properties": [
{
"name": "nix:fod:method",
"value": "fetchzip"
},
{
"name": "nix:fod:path",
"value": "/nix/store/xpn694ibgipj8xak3j4bky6b3k0vp7hh-source"
},
{
"name": "nix:fod:rev",
"value": "ec0cc6e64ea4c62d2aa382b176a4838474238f8d"
},
{
"name": "nix:fod:sha256",
"value": "1fi9ls3xl20bmv1ikillxywl96i9al6zmmxrbffx448gbrxs86kg"
},
{
"name": "nix:fod:url",
"value": "https://github.com/zevv/npeg/archive/ec0cc6e64ea4c62d2aa382b176a4838474238f8d.tar.gz"
},
{
"name": "nix:fod:ref",
"value": "1.2.2"
},
{
"name": "nix:fod:srcDir",
"value": "src"
}
]
},
{
"type": "library",
"bom-ref": "pkg:nim/bigints",
"name": "bigints",
"version": "20231006",
"externalReferences": [
{
"url": "https://github.com/ehmry/nim-bigints/archive/86ea14d31eea9275e1408ca34e6bfe9c99989a96.tar.gz",
"type": "source-distribution"
},
{
"url": "https://github.com/ehmry/nim-bigints.git",
"type": "vcs"
}
],
"properties": [
{
"name": "nix:fod:method",
"value": "fetchzip"
},
{
"name": "nix:fod:path",
"value": "/nix/store/jvrm392g8adfsgf36prgwkbyd7vh5jsw-source"
},
{
"name": "nix:fod:rev",
"value": "86ea14d31eea9275e1408ca34e6bfe9c99989a96"
},
{
"name": "nix:fod:sha256",
"value": "15pcpmnk1bnw3k8769rjzcpg00nahyrypwbxs88jnwr4aczp99j4"
},
{
"name": "nix:fod:url",
"value": "https://github.com/ehmry/nim-bigints/archive/86ea14d31eea9275e1408ca34e6bfe9c99989a96.tar.gz"
},
{
"name": "nix:fod:ref",
"value": "20231006"
},
{
"name": "nix:fod:srcDir",
"value": "src"
}
]
},
{
"type": "library",
"bom-ref": "pkg:nim/cps",
"name": "cps",
"version": "0.10.4",
"externalReferences": [
{
"url": "https://github.com/nim-works/cps/archive/2a4d771a715ba45cfba3a82fa625ae7ad6591c8b.tar.gz",
"type": "source-distribution"
},
{
"url": "https://github.com/nim-works/cps",
"type": "vcs"
}
],
"properties": [
{
"name": "nix:fod:method",
"value": "fetchzip"
},
{
"name": "nix:fod:path",
"value": "/nix/store/m9vpcf3dq6z2h1xpi1vlw0ycxp91s5p7-source"
},
{
"name": "nix:fod:rev",
"value": "2a4d771a715ba45cfba3a82fa625ae7ad6591c8b"
},
{
"name": "nix:fod:sha256",
"value": "0c62k5wpq9z9mn8cd4rm8jjc4z0xmnak4piyj5dsfbyj6sbdw2bf"
},
{
"name": "nix:fod:url",
"value": "https://github.com/nim-works/cps/archive/2a4d771a715ba45cfba3a82fa625ae7ad6591c8b.tar.gz"
},
{
"name": "nix:fod:ref",
"value": "0.10.4"
}
]
},
{
"type": "library",
"bom-ref": "pkg:nim/stew",
"name": "stew",
"version": "3c91b8694e15137a81ec7db37c6c58194ec94a6a",
"externalReferences": [
{
"url": "https://github.com/status-im/nim-stew/archive/3c91b8694e15137a81ec7db37c6c58194ec94a6a.tar.gz",
"type": "source-distribution"
},
{
"url": "https://github.com/status-im/nim-stew",
"type": "vcs"
}
],
"properties": [
{
"name": "nix:fod:method",
"value": "fetchzip"
},
{
"name": "nix:fod:path",
"value": "/nix/store/mqg8qzsbcc8xqabq2yzvlhvcyqypk72c-source"
},
{
"name": "nix:fod:rev",
"value": "3c91b8694e15137a81ec7db37c6c58194ec94a6a"
},
{
"name": "nix:fod:sha256",
"value": "17lfhfxp5nxvld78xa83p258y80ks5jb4n53152cdr57xk86y07w"
},
{
"name": "nix:fod:url",
"value": "https://github.com/status-im/nim-stew/archive/3c91b8694e15137a81ec7db37c6c58194ec94a6a.tar.gz"
}
]
},
{
"type": "library",
"bom-ref": "pkg:nim/getdns",
"name": "getdns",
"version": "20230806",
"externalReferences": [
{
"url": "https://git.sr.ht/~ehmry/getdns-nim/archive/e4ae0992ed7c5540e6d498f3074d06c8f454a0b6.tar.gz",
"type": "source-distribution"
},
{
"url": "https://git.sr.ht/~ehmry/getdns-nim",
"type": "vcs"
}
],
"properties": [
{
"name": "nix:fod:method",
"value": "fetchzip"
},
{
"name": "nix:fod:path",
"value": "/nix/store/j8i20k9aarzppg4p234449140nnnaycq-source"
},
{
"name": "nix:fod:rev",
"value": "e4ae0992ed7c5540e6d498f3074d06c8f454a0b6"
},
{
"name": "nix:fod:sha256",
"value": "1dp53gndr6d9s9601dd5ipkiq94j53hlx46mxv8gpr8nd98bqysg"
},
{
"name": "nix:fod:url",
"value": "https://git.sr.ht/~ehmry/getdns-nim/archive/e4ae0992ed7c5540e6d498f3074d06c8f454a0b6.tar.gz"
},
{
"name": "nix:fod:ref",
"value": "20230806"
},
{
"name": "nix:fod:srcDir",
"value": "src"
}
]
}
],
"dependencies": [
{
"ref": "pkg:nim/syndicate",
"dependsOn": [
"pkg:nim/nimcrypto",
"pkg:nim/preserves",
"pkg:nim/sys",
"pkg:nim/taps",
"pkg:nim/solo5_dispatcher"
]
},
{
"ref": "pkg:nim/nimcrypto",
"dependsOn": []
},
{
"ref": "pkg:nim/preserves",
"dependsOn": [
"pkg:nim/npeg",
"pkg:nim/bigints"
]
},
{
"ref": "pkg:nim/sys",
"dependsOn": [
"pkg:nim/cps",
"pkg:nim/stew"
]
},
{
"ref": "pkg:nim/taps",
"dependsOn": [
"pkg:nim/getdns",
"pkg:nim/sys",
"pkg:nim/cps",
"pkg:nim/solo5_dispatcher"
]
},
{
"ref": "pkg:nim/solo5_dispatcher",
"dependsOn": [
"pkg:nim/cps"
]
},
{
"ref": "pkg:nim/npeg",
"dependsOn": []
},
{
"ref": "pkg:nim/bigints",
"dependsOn": []
},
{
"ref": "pkg:nim/cps",
"dependsOn": []
},
{
"ref": "pkg:nim/stew",
"dependsOn": []
},
{
"ref": "pkg:nim/getdns",
"dependsOn": []
}
]
}

View File

@ -1,7 +1,5 @@
{ pkgs ? import <nixpkgs> { } }:
pkgs.buildNimPackage {
name = "dummy";
buildInputs = builtins.attrValues { inherit (pkgs) getdns solo5; };
nativeBuildInputs = builtins.attrValues { inherit (pkgs) pkg-config solo5; };
lockFile = ./lock.json;
}

View File

@ -1,2 +0,0 @@
include_rules
: foreach *.nim |> !nim-doc |>

3
src/sam/Tupfile Normal file
View File

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

699
src/sam/actors.bak Normal file
View File

@ -0,0 +1,699 @@
# SPDX-FileCopyrightText: ☭ Emery Hemingway
# SPDX-License-Identifier: Unlicense
import std/[deques, hashes, monotimes, options, sets, sequtils, tables, times]
import pkg/cps
import preserves
import ../syndicate/protocols/[protocol, sturdy]
export cps
const traceSyndicate {.booldefine.}: bool = true
when traceSyndicate:
import std/streams
from std/os import getEnv
import ./protocols/trace
type TraceSink = ref object
stream: FileStream
proc newTraceSink: TraceSink =
new result
let path = getEnv("SYNDICATE_TRACE_FILE", "")
case path
of "": quit"$SYNDICATE_TRACE_FILE unset"
of "-": actor.stream = newFileStream(stderr)
else: result.stream = openFileStream(path, fmWrite)
proc write(s: TraceSink; e: TraceEntry) = s.write(e.toPreserves)
export Handle
template generateIdType(typ: untyped) =
type typ* = distinct Natural
proc `==`*(x, y: typ): bool {.borrow.}
proc `$`*(id: typ): string {.borrow.}
generateIdType(ActorId)
generateIdType(FacetId)
generateIdType(EndpointId)
generateIdType(FieldId)
generateIdType(TurnId)
type
Oid = sturdy.Oid
Caveat = sturdy.Caveat
Attenuation = seq[Caveat]
Rewrite = sturdy.Rewrite
AssertionRef* = ref object
value*: Value
# if the Enity methods take a Value object then the generated
# C code has "redefinition of struct" problems when orc is enabled
Entity* = ref object of RootObj
oid*: Oid # oid is how Entities are identified over the wire
Cap* {.preservesEmbedded.} = ref object of EmbeddedObj
relay*: Facet
target*: Entity
attenuation*: Attenuation
Ref* {.deprecated: "Ref was renamed to Cap".} = Cap
OutboundAssertion = ref object
handle: Handle
peer: Cap
established: bool
OutboundTable = Table[Handle, OutboundAssertion]
Actor* = ref object
name: string
handleAllocator: ref Handle
# a fresh actor gets a new ref Handle and
# all actors spawned from it get the same ref.
root: Facet
exitReason: ref Exception
exitHooks: seq[TurnAction]
id: ActorId
exiting, exited: bool
when traceSyndicate:
turnIdAllocator: ref TurnId
traceStream: FileStream
TurnAction* = proc (t: Turn)
Queues = TableRef[Facet, Deque[Cont]]
Turn* = ref object
facet: Facet
queues: Queues
when traceSyndicate:
desc: TurnDescription
Cont* = ref object of Continuation
turn*: Turn
Facet* = ref FacetObj
FacetObj = object
actor*: Actor
parent: Facet
children: HashSet[Facet]
outbound: OutboundTable
shutdownActions: seq[TurnAction]
inertCheckPreventers: int
id: FacetId
isAlive: bool
proc pass*(a, b: Cont): Cont =
assert not a.turn.isNil
b.turn = move a.turn
return b
template turnAction*(prc: typed): untyped =
cps(Cont, prc)
proc activeTurn*(c: Cont): Turn {.cpsVoodoo.} =
assert not c.turn.isNil
c.turn
when traceSyndicate:
proc nextTurnId(facet: Facet): TurnId =
result = succ(facet.actor.turnIdAllocator[])
facet.actor.turnIdAllocator[] = result
proc trace(actor: Actor; act: ActorActivation) =
assert not actor.traceStream.isNil
var entry = TraceEntry(
timestamp: getTime().toUnixFloat(),
actor: initRecord("named", actor.name.toPreserves),
item: act)
actor.traceStream.writeLine($entry.toPreserves)
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: Turn; v: AssertionRef; h: Handle) {.base.} = discard
method retract*(e: Entity; turn: Turn; h: Handle) {.base.} = discard
method message*(e: Entity; turn: Turn; v: AssertionRef) {.base.} = discard
method sync*(e: Entity; turn: Turn; peer: Cap) {.base.} = discard
using
actor: Actor
facet: Facet
turn: Turn
action: TurnAction
proc labels(f: Facet): string =
proc catLabels(f: Facet; labels: var string) =
labels.add ':'
if not f.parent.isNil:
catLabels(f.parent, labels)
labels.add ':'
when traceSyndicate:
labels.add $f.id
result.add f.actor.name
catLabels(f, result)
proc `$`*(f: Facet): string =
"<Facet:" & f.labels & ">"
proc `$`*(r: Cap): string =
"<Ref:" & r.relay.labels & ">"
proc `$`*(actor: Actor): string =
"<Actor:" & actor.name & ">" # TODO: ambigous
proc attenuate(r: Cap; a: Attenuation): Cap =
if a.len == 0: result = r
else: result = Cap(
relay: r.relay,
target: r.target,
attenuation: a & r.attenuation)
proc hash*(facet): Hash =
facet.id.hash
proc hash*(r: Cap): Hash = !$(r.relay.hash !& r.target.unsafeAddr.hash)
proc nextHandle(facet: Facet): Handle =
result = succ(facet.actor.handleAllocator[])
facet.actor.handleAllocator[] = result
proc facet*(turn: Turn): Facet = turn.facet
proc enqueue(turn: Turn; target: Facet; cont: Cont) =
cont.turn = turn
if target in turn.queues:
turn.queues[target].addLast cont
else:
turn.queues[target] = toDeque([cont])
type Bindings = Table[Value, Value]
proc match(bindings: var Bindings; p: Pattern; v: Value): bool =
case p.orKind
of PatternKind.Pdiscard: result = true
of PatternKind.Patom:
result = case p.patom
of PAtom.Boolean: v.isBoolean
of PAtom.Double: v.isDouble
of PAtom.Signedinteger: v.isInteger
of PAtom.String: v.isString
of PAtom.Bytestring: v.isByteString
of PAtom.Symbol: v.isSymbol
of PatternKind.Pembedded:
result = v.isEmbedded
of PatternKind.Pbind:
if match(bindings, p.pbind.pattern, v):
bindings[p.pbind.pattern.toPreserves] = v
result = true
of PatternKind.Pand:
for pp in p.pand.patterns:
result = match(bindings, pp, v)
if not result: break
of PatternKind.Pnot:
var b: Bindings
result = not match(b, p.pnot.pattern, v)
of PatternKind.Lit:
result = p.lit.value == v
of PatternKind.PCompound:
case p.pcompound.orKind
of PCompoundKind.rec:
if v.isRecord and
p.pcompound.rec.label == v.label and
p.pcompound.rec.fields.len == v.arity:
result = true
for i, pp in p.pcompound.rec.fields:
if not match(bindings, pp, v[i]):
result = false
break
of PCompoundKind.arr:
if v.isSequence and p.pcompound.arr.items.len == v.sequence.len:
result = true
for i, pp in p.pcompound.arr.items:
if not match(bindings, pp, v[i]):
result = false
break
of PCompoundKind.dict:
if v.isDictionary:
result = true
for key, pp in p.pcompound.dict.entries:
let vv = step(v, key)
if vv.isNone or not match(bindings, pp, get vv):
result = true
break
proc match(p: Pattern; v: Value): Option[Bindings] =
var b: Bindings
if match(b, p, v):
result = some b
proc instantiate(t: Template; bindings: Bindings): Value =
case t.orKind
of TemplateKind.Tattenuate:
let v = instantiate(t.tattenuate.template, bindings)
let cap = v.unembed(Cap)
if cap.isNone:
raise newException(ValueError, "Attempt to attenuate non-capability")
result = attenuate(get cap, t.tattenuate.attenuation).embed
of TemplateKind.TRef:
let n = $t.tref.binding.int
try: result = bindings[n.toPreserves]
except KeyError:
raise newException(ValueError, "unbound reference: " & n)
of TemplateKind.Lit:
result = t.lit.value
of TemplateKind.Tcompound:
case t.tcompound.orKind
of TCompoundKind.rec:
result = initRecord(t.tcompound.rec.label, t.tcompound.rec.fields.len)
for i, tt in t.tcompound.rec.fields:
result[i] = instantiate(tt, bindings)
of TCompoundKind.arr:
result = initSequence(t.tcompound.arr.items.len)
for i, tt in t.tcompound.arr.items:
result[i] = instantiate(tt, bindings)
of TCompoundKind.dict:
result = initDictionary()
for key, tt in t.tcompound.dict.entries:
result[key] = instantiate(tt, bindings)
proc rewrite(r: Rewrite; v: Value): Value =
let bindings = match(r.pattern, v)
if bindings.isSome:
result = instantiate(r.template, get bindings)
proc examineAlternatives(cav: Caveat; v: Value): Value =
case cav.orKind
of CaveatKind.Rewrite:
result = rewrite(cav.rewrite, v)
of CaveatKind.Alts:
for r in cav.alts.alternatives:
result = rewrite(r, v)
if not result.isFalse: break
of CaveatKind.Reject: discard
of CaveatKind.unknown: discard
proc runRewrites*(a: Attenuation; v: Value): Value =
result = v
for stage in a:
result = examineAlternatives(stage, result)
if result.isFalse: break
proc publish(target: Entity; e: OutboundAssertion; a: AssertionRef) {.turnAction.} =
e.established = true
publish(target, activeTurn(), a, e.handle)
proc publish(turn: Turn; r: Cap; v: Value; h: Handle) =
var a = runRewrites(r.attenuation, v)
if not a.isFalse:
let e = OutboundAssertion(
handle: h, peer: r, established: false)
turn.facet.outbound[h] = e
enqueue(turn, r.relay, whelp publish(r.target, e, AssertionRef(value: a)))
when traceSyndicate:
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: Turn; r: Cap; a: Value): Handle =
result = turn.facet.nextHandle()
publish(turn, r, a, result)
proc publish*(r: Cap; a: Value): Handle {.turnAction.} =
publish(activeTurn(), r, a, result)
proc retract(e: OutboundAssertion) {.turnAction.} =
if e.established:
e.established = false
e.peer.target.retract(activeTurn(), e.handle)
proc retract(turn: Turn; e: OutboundAssertion) =
enqueue(turn, e.peer.relay, whelp retract(e))
proc retract*(turn: Turn; h: Handle) =
var e: OutboundAssertion
if turn.facet.outbound.pop(h, e):
turn.retract(e)
proc message(target: Entity; a: AssertionRef) {.turnAction.} =
target.message(activeTurn(), a)
proc message*(turn: Turn; r: Cap; v: Value) =
var a = runRewrites(r.attenuation, v)
if not a.isFalse:
enqueue(turn, r.relay, whelp message(r.target, AssertionRef(value: a)))
proc message*(target: Cap; value: Value) {.turnAction.} =
message(activeTurn(), target, value)
proc sync(e: Entity; peer: Cap) {.turnAction.} =
e.sync(activeTurn(), peer)
proc sync(turn: Turn; e: Entity; peer: Cap) =
e.sync(turn, peer)
proc sync*(turn: Turn; r, peer: Cap) =
enqueue(turn, r.relay, whelp sync(r.target, peer))
proc replace*[T](turn: Turn; cap: Cap; h: Handle; v: T): Handle =
result = publish(turn, cap, v)
if h != default(Handle):
retract(turn, h)
proc replace*[T](turn: Turn; cap: Cap; h: var Handle; v: T): Handle {.discardable.} =
var old = h
h = publish(turn, cap, v)
if old != default(Handle):
retract(turn, old)
h
proc stop*(turn: Turn)
proc run*(facet; action: TurnAction; zombieTurn = false)
proc newFacet(actor; parent: Facet; initialAssertions: OutboundTable): Facet =
result = Facet(
id: getMonoTime().ticks.FacetId,
actor: actor,
parent: parent,
outbound: initialAssertions,
isAlive: true)
if not parent.isNil: parent.children.incl result
proc newFacet(actor; parent: Facet): Facet =
var initialAssertions: OutboundTable
newFacet(actor, parent, initialAssertions)
proc isInert(facet): bool =
result = facet.children.len == 0 and
(facet.outbound.len == 0 or facet.parent.isNil) and
facet.inertCheckPreventers == 0
proc preventInertCheck*(facet): (proc() {.gcsafe.}) {.discardable.} =
var armed = true
inc facet.inertCheckPreventers
proc disarm() =
if armed:
armed = false
dec facet.inertCheckPreventers
result = disarm
proc inFacet(turn: Turn; facet; act: TurnAction) =
## Call an action with a facet using a temporary `Turn`
## that shares the `Queues` of the calling `Turn`.
var t = Turn(facet: facet, queues: turn.queues)
act(t)
proc terminate(actor; turn; reason: ref Exception)
proc terminate(facet; turn: Turn; orderly: bool) =
if facet.isAlive:
facet.isAlive = false
let parent = facet.parent
if not parent.isNil:
parent.children.excl facet
block:
var turn = Turn(facet: facet, queues: turn.queues)
while facet.children.len > 0:
facet.children.pop.terminate(turn, orderly)
if orderly:
for act in facet.shutdownActions:
act(turn)
for a in facet.outbound.values: turn.retract(a)
if orderly:
if not parent.isNil:
if parent.isInert:
parent.terminate(turn, true)
else:
terminate(facet.actor, turn, nil)
when traceSyndicate:
var act = ActionDescription(orKind: ActionDescriptionKind.facetStop)
act.facetstop.path = facet.path
turn.desc.actions.add act
proc stopIfInert() {.turnAction.} =
let turn = activeTurn()
if (not turn.facet.parent.isNil and (not turn.facet.parent.isAlive)) or turn.facet.isInert:
stop(turn)
proc stopIfInertAfter(action: TurnAction): TurnAction =
proc wrapper(turn: Turn) =
action(turn)
enqueue(turn, turn.facet, whelp stopIfInert())
wrapper
proc newFacet*(turn: Turn): Facet = newFacet(turn.facet.actor, turn.facet)
proc inFacet*(turn: Turn; bootProc: TurnAction): Facet =
result = newFacet(turn)
when traceSyndicate:
var act = ActionDescription(orKind: ActionDescriptionKind.facetstart)
act.facetstart.path.add result.path
turn.desc.actions.add act
inFacet(turn, result, stopIfInertAfter(bootProc))
proc facet*(turn: Turn; bootProc: TurnAction): Facet {.deprecated.} = inFacet(turn, bootProc)
proc run(actor; bootProc: TurnAction; initialAssertions: OutboundTable) =
run(newFacet(actor, actor.root, initialAssertions), stopIfInertAfter(bootProc))
proc run(actor; bootProc: TurnAction) =
var initialAssertions: OutboundTable
run(newFacet(actor, actor.root, initialAssertions), stopIfInertAfter(bootProc))
proc newActor(name: string; handleAlloc: ref Handle): Actor =
let
now = getTime()
seed = now.toUnix * 1_000_000_000 + now.nanosecond
result = Actor(
name: name,
id: ActorId(seed),
handleAllocator: handleAlloc,
)
result.root = newFacet(result, nil)
when traceSyndicate:
new result.turnIdAllocator
proc newActor*(name: string): Actor =
newActor(name, new(ref Handle))
proc bootActor*(name: string; bootProc: TurnAction) =
var
initialAssertions: OutboundTable
actor = newActor(name)
when traceSyndicate:
let path = getEnv("SYNDICATE_TRACE_FILE", "/tmp/" & name & ".trace.pr")
case path
of "": stderr.writeLine "$SYNDICATE_TRACE_FILE unset, not tracing actor ", name
of "-": actor.traceStream = newFileStream(stderr)
else: actor.traceStream = openFileStream(path, fmWrite)
when traceSyndicate:
var act = ActorActivation(orKind: ActorActivationKind.start)
act.start.actorName = Name(orKind: NameKind.named)
act.start.actorName.named.name = name.toPreserves
var entry = TraceEntry(
timestamp: getTime().toUnixFloat(),
item: act)
actor.traceStream.writeLine($entry.toPreserves)
let turn = newTurn(actor, TurnCauseExternal(description: "top-level actor"))
run(actor, bootProc, initialAssertions)
proc bootActor*(name: string; cont: Cont) =
bootActor(name) do (turn: Turn):
enqueue(turn, turn.facet, cont)
proc spawnActor(actor: Actor; bootProc: TurnAction; initialAssertions: HashSet[Handle]) {.turnAction.} =
let turn = activeTurn()
var newOutBound: Table[Handle, OutboundAssertion]
for key in initialAssertions:
discard turn.facet.outbound.pop(key, newOutbound[key])
when traceSyndicate:
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)
proc spawn*(name: string; turn: Turn; bootProc: TurnAction; initialAssertions = initHashSet[Handle]()): Actor {.discardable.} =
let actor = newActor(name, turn.facet.actor.handleAllocator)
enqueue(turn, turn.facet, whelp spawnActor(actor, bootProc, initialAssertions))
actor
proc newInertCap*(): Cap =
let a = newActor("inert")
run(a) do (turn: Turn): turn.stop()
Cap(relay: a.root)
proc atExit*(actor; action) = actor.exitHooks.add action
proc terminate(actor: Actor; orderly: bool) {.turnAction.} =
actor.root.terminate(activeTurn(), orderly)
actor.exited = true
proc terminate(actor; turn; reason: ref Exception) =
if not actor.exiting:
actor.exiting = true
actor.exitReason = reason
when traceSyndicate:
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)
enqueue(turn, actor.root, whelp terminate(actor, reason.isNil))
proc terminate*(facet; e: ref Exception) =
run(facet.actor.root) do (turn: Turn):
facet.actor.terminate(turn, e)
#[
proc asyncCheck*(facet: Facet; fut: FutureBase) =
## Sets a callback on `fut` which propagates exceptions to `facet`.
addCallback(fut) do ():
if fut.failed: terminate(facet, fut.error)
proc asyncCheck*(turn; fut: FutureBase) =
## Sets a callback on `fut` which propagates exceptions to the facet of `turn`.
asyncCheck(turn.facet, fut)
]#
template tryFacet(facet; body: untyped) =
try: body
except CatchableError as err: terminate(facet, err)
proc run(facet: Facet; turn: Turn; deq: var Deque[Cont]): int =
## Return the number of continuations processed.
while deq.len > 0:
var c = deq.popFirst()
try:
while not c.isNil and not c.fn.isNil:
c.turn = turn
var y = c.fn
var x = y(c)
inc(result)
c = Cont(x)
except CatchableError as err:
if not c.dismissed:
writeStackFrames c
terminate(facet, err)
stderr.writeLine("ran ", result, " continuations for ", facet)
proc run*(facet; action: TurnAction; zombieTurn = false) =
if zombieTurn or (facet.actor.exitReason.isNil and facet.isAlive):
tryFacet(facet):
var queues = newTable[Facet, Deque[Cont]]()
var turn = Turn(facet: facet, queues: queues)
action(turn)
when traceSyndicate:
turn.desc.id = facet.nextTurnId.toPreserves
facet.actor.trace ActorActivation(
orKind: ActorActivationKind.turn, turn: turn.desc)
assert not turn.isNil
var n = 1
while n > 0:
n = 0
var facets = queues.keys.toSeq
for facet in facets:
n.inc run(facet, turn, queues[facet])
proc run*(cap: Cap; action: TurnAction) =
## Convenience proc to run a `TurnAction` in the scope of a `Cap`.
run(cap.relay, action)
#[
proc addCallback*(fut: FutureBase; facet: Facet; act: TurnAction) =
## Add a callback to a `Future` that will be called at a later `Turn`
## within the context of `facet`.
addCallback(fut) do ():
if fut.failed: terminate(facet, fut.error)
else:
when traceSyndicate:
run(facet) do (turn: 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: Turn; act: TurnAction) =
## Add a callback to a `Future` that will be called at a later `Turn`
## with the same context as the current.
if fut.failed:
terminate(turn.facet, fut.error)
elif fut.finished:
enqueue(turn, turn.facet, act)
else:
addCallback(fut, turn.facet, act)
proc addCallback*[T](fut: Future[T]; turn: Turn; act: proc (t: Turn, x: T) {.gcsafe.}) =
addCallback(fut, turn) do (turn: Turn):
if fut.failed: terminate(turn.facet, fut.error)
else:
when traceSyndicate:
turn.desc.cause = TurnCause(orKind: TurnCauseKind.external)
turn.desc.cause.external.description = "Future".toPreserves
act(turn, read fut)
]#
proc stop*(turn: Turn, facet: Facet) =
if facet.parent.isNil:
facet.terminate(turn, true)
else:
enqueue(turn, facet.parent, whelp terminate(facet.actor, true))
# TODO: terminate the actor?
proc stop*(turn: Turn) =
stop(turn, turn.facet)
proc onStop*(facet: Facet; act: TurnAction) =
## Add a `proc (turn: Turn)` action to `facet` to be called as it stops.
add(facet.shutdownActions, act)
proc stopActor*(turn: Turn) =
let actor = turn.facet.actor
enqueue(turn, actor.root, whelp terminate(actor, true))
proc freshen*(turn: Turn, act: TurnAction) =
assert(turn.queues.len == 0, "Attempt to freshen a non-stale Turn")
run(turn.facet, act)
proc newCap*(relay: Facet; e: Entity): Cap =
Cap(relay: relay, target: e)
proc newCap*(turn; e: Entity): Cap =
Cap(relay: turn.facet, target: e)
proc newCap*(e: Entity; turn): Cap =
Cap(relay: turn.facet, target: e)
type SyncContinuation {.final.} = ref object of Entity
action: TurnAction
method message(entity: SyncContinuation; turn: Turn; v: AssertionRef) =
entity.action(turn)
proc sync*(turn: 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
proc newCap*(e: Entity): Cap {.turnAction.} =
Cap(relay: activeTurn().facet, target: e)

677
src/sam/actors.nim Normal file
View File

@ -0,0 +1,677 @@
# SPDX-FileCopyrightText: ☭ Emery Hemingway
# SPDX-License-Identifier: Unlicense
# Use procedures to call into SAM,
# use continuations to call within SAM.
import std/[deques, hashes, options, sets, tables, times]
import pkg/cps
import preserves
import ./protocols/[protocol, sturdy]
# const traceSyndicate {.booldefine.}: bool = true
const traceSyndicate* = true
when traceSyndicate:
import std/streams
from std/os import getEnv
import ./protocols/trace
export protocol.Handle
type
Cont* = ref object of Continuation
turn: Turn
PublishProc* = proc (e: Entity; v: Value; h: Handle) {.cps: Cont.}
RetractProc* = proc (e: Entity; h: Handle) {.cps: Cont.}
MessageProc* = proc (e: Entity; v: Value) {.cps: Cont.}
SyncProc* = proc (e: Entity; peer: Cap) {.cps: Cont.}
Handler* = proc() {.closure.}
HandlerDeque = seq[ContinuationProc[Continuation]]
FacetState = enum fFresh, fRunning, fEnded
Callback* = proc () {.closure.}
OutboundTable = Table[Handle, OutboundAssertion]
OutboundAssertion = ref object
handle: Handle
peer: Cap
established: bool
Facet* = ref object
## https://synit.org/book/glossary.html#facet
actor: Actor
parent: Facet
children: seq[Facet]
outbound: OutboundTable
stopHandlers: HandlerDeque
stopCallbacks: seq[Callback]
state: FacetState
id: FacetId
FacetProc* = proc (f: Facet) {.closure.}
## Type for callbacks to be called within a turn.
## The `Facet` parameter is the owning facet.
Turn = ref object
## https://synit.org/book/glossary.html#turn
actor: Actor
work: Dequeue[Cont]
actions: seq[Cont]
event: Option[protocol.Event]
rollback: bool
when traceSyndicate:
desc: TurnDescription
Entity* = ref object of RootObj
## https://synit.org/book/glossary.html#entity
publishImpl: PublishProc
retractImpl: RetractProc
messageImpl: MessageProc
syncImpl: SyncProc
facet*: Facet
# This implementation associates Entities to
# Facets, which is not to be taken as a SAM
# axiom.
oid*: sturdy.Oid # oid is how Entities are identified over the wire
Cap* {.final, preservesEmbedded.} = ref object of EmbeddedObj
target*: Entity
attenuation*: seq[sturdy.Caveat]
Actor* = ref object
## https://synit.org/book/glossary.html#actor
# TODO: run on a seperate thread.
# crashHandlers: HandlerDeque
root: Facet
handleAllocator: Handle
facetIdAllocator: int
id: ActorId
when traceSyndicate:
traceStream: FileStream
stopped: bool
var turnQueue {.threadvar, used.}: Deque[Turn]
proc queueTurn(turn: sink Turn) =
turnQueue.addLast(turn)
proc turnsPending*(): bool =
turnQueue.len > 0
template turnWork*(prc: typed): untyped =
## Pragma to mark work that executes in a `Turn` context.
cps(Cont, prc)
proc activeTurn(c: Cont): Turn {.cpsVoodoo.} =
## Return the active `Turn` within a turn context.
assert not c.turn.isNil
c.turn
proc activeFacet(c: Cont): Facet {.cpsVoodoo.} =
## Return the active `Facet` within a turn context.
assert not c.turn.isNil
assert not c.turn.facet.isNil
c.turn.facet
using
actor: Actor
facet: Facet
entity: Entity
cap: Cap
turn: Turn
proc `$`*(facet): string = $facet.id
proc `$`*(cap): string = "#:" & $cast[uint](cap.unsafeAddr)
proc hash*(x: Actor|Facet|Cap): Hash = x.unsafeAddr.hash
proc relay*(cap): Facet =
assert not cap.target.facet.isNil
cap.target.facet
proc collectPath(result: var seq[FacetId]; facet) =
if not facet.parent.isNil:
collectPath(result, facet.parent)
result.add(facet.id)
proc stopped*(facet): bool = facet.state != fRunning
when traceSyndicate:
proc traceFlush(actor) =
if not actor.traceStream.isNil:
actor.traceStream.flush()
proc trace(actor; act: ActorActivation) =
if not actor.traceStream.isNil:
var entry = TraceEntry(
timestamp: getTime().toUnixFloat(),
actor: actor.id,
item: act,
)
actor.traceStream.writeLine($entry.toPreserves)
proc traceTurn(actor) =
if not actor.traceStream.isNil:
actor.trace(ActorActivation(
orKind: ActorActivationKind.turn,
turn: actor.turn.desc,
))
actor.traceFlush()
reset actor.turn.desc
proc traceTarget(facet): trace.Target =
Target(
actor: facet.actor.id,
facet: facet.id,
)
proc traceTarget(cap): trace.Target =
let facet = cap.relay
Target(
actor: facet.actor.id,
facet: facet.id,
oid: cap.target.oid.toPreserves,
)
proc traceEnqueue(actor; e: TargetedTurnEvent) =
actor.turn.desc.actions.add ActionDescription(
orKind: ActionDescriptionKind.enqueue,
enqueue: ActionDescriptionEnqueue(event: e),
)
proc traceDequeue(actor; e: TargetedTurnEvent) =
actor.turn.desc.actions.add ActionDescription(
orKind: ActionDescriptionKind.dequeue,
dequeue: ActionDescriptionDequeue(event: e),
)
proc pass*(a, b: Cont): Cont =
assert not a.facet.isNil
b.facet = a.facet
b
proc queueWork*(facet; c: Cont) =
c.facet = facet
facet.actor.turn.work.addLast(c)
proc queueAction*(facet; c: Cont) =
c.facet = facet
facet.actor.turn.actions.add(c)
proc yieldWork(c: Cont): Cont {.cpsMagic.} =
## Suspend and enqueue the caller until later in the turn.
assert not c.facet.isNil
c.facet.queueWork(c)
nil
proc yieldToActions(c: Cont): Cont {.cpsMagic.} =
assert not c.facet.isNil
c.facet.actor.turn.actions.add(c)
nil
proc terminate(actor; err: ref Exception) =
when traceSyndicate:
actor.traceTurn()
raise err
proc terminate(facet; err: ref Exception) =
terminate(facet.actor, err)
proc complete(c: Cont) =
var c = c
try:
while not c.isNil and not c.fn.isNil:
var y = c.fn
var x = y(c)
c = Cont(x)
except CatchableError as err:
c.facet.actor.turn.rollback = true
if not c.dismissed:
writeStackFrames c
terminate(c.facet, err)
proc run*(actor): bool =
if actor.stopped: return
try:
result = actor.turn.work.len > 0
while actor.turn.work.len > 0:
actor.turn.work.popFirst().complete()
var i: int
while i < actor.turn.actions.len:
complete(move actor.turn.actions[i])
inc i
actor.turn.actions.setLen(0)
when traceSyndicate:
actor.traceTurn()
if actor.stopped:
trace(actor, ActorActivation(orkind: ActorActivationKind.stop))
for child in actor.children:
result = result and run(child)
except Exception as err:
actor.terminate(err)
proc start(actor; cont: Cont) =
when traceSyndicate:
var act = ActorActivation(orkind: ActorActivationKind.start)
trace(actor, act)
actor.root.state = fRunning
actor.root.startExternalTurn()
actor.root.queueWork(cont)
proc stop*(actor)
proc runNextStop(c: Cont; facet: Facet): Cont {.cpsMagic.} =
c.fn = facet.stopHandlers.pop()
result = c
proc runNextFacetStop() {.cps: Cont.} =
activeFacet().runNextStop()
proc stop(facet; reason: FacetStopReason) =
let actor = facet.actor
while facet.stopHandlers.len > 0:
var c = whelp runNextFacetStop()
c.facet = facet
complete(c)
while facet.stopCallbacks.len > 0:
var cb = facet.stopCallbacks.pop()
cb()
while facet.children.len > 0:
stop(facet.children.pop(), FacetStopReason.parentStopping)
when traceSyndicate:
var act = ActionDescription(orKind: ActionDescriptionKind.facetstop)
collectPath(act.facetstop.path, facet)
act.facetStop.reason = reason
actor.turn.desc.actions.add act
if facet.parent.isNil:
actor.root = nil
stop(actor)
proc stop*(actor) =
if not actor.root.isNil:
stop(actor.root, FacetStopReason.actorStopping)
actor.stopped = true
proc stopped*(actor): bool =
actor.`stopped`
# proc stopFacetAction(reason: FacetStopReason) {.syndicate.} =
# stop(c.facet, reason)
proc stopActorAction() {.cps: Cont.} =
yieldToActions()
activeFacet().actor.stop()
proc stopActor*(facet) =
let c = whelp stopActorAction()
c.facet = facet
facet.actor.turn.actions.add(c)
proc stopActor*(cap) =
## Stop an `Actor` from a `Cap`.
stopActor(cap.relay)
type
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
proc newCap*(facet; entity): Cap =
doAssert entity.facet.isNil
entity.facet = facet
Cap(target: entity)
proc nextHandle(facet: Facet): Handle =
inc(facet.actor.handleAllocator)
facet.actor.handleAllocator
proc actor(cap): Actor = cap.relay.actor
type Bindings = Table[Value, Value]
proc attenuate(cap; att: seq[Caveat]): Cap =
if att.len == 0: cap
else: Cap(
target: cap.target,
attenuation: att & cap.attenuation)
proc match(bindings: var Bindings; p: Pattern; v: Value): bool =
case p.orKind
of PatternKind.Pdiscard: result = true
of PatternKind.Patom:
result = case p.patom
of PAtom.Boolean: v.isBoolean
of PAtom.Double: v.isDouble
of PAtom.Signedinteger: v.isInteger
of PAtom.String: v.isString
of PAtom.Bytestring: v.isByteString
of PAtom.Symbol: v.isSymbol
of PatternKind.Pembedded:
result = v.isEmbedded
of PatternKind.Pbind:
if match(bindings, p.pbind.pattern, v):
bindings[p.pbind.pattern.toPreserves] = v
result = true
of PatternKind.Pand:
for pp in p.pand.patterns:
result = match(bindings, pp, v)
if not result: break
of PatternKind.Pnot:
var b: Bindings
result = not match(b, p.pnot.pattern, v)
of PatternKind.Lit:
result = p.lit.value == v
of PatternKind.PCompound:
case p.pcompound.orKind
of PCompoundKind.rec:
if v.isRecord and
p.pcompound.rec.label == v.label and
p.pcompound.rec.fields.len == v.arity:
result = true
for i, pp in p.pcompound.rec.fields:
if not match(bindings, pp, v[i]):
result = false
break
of PCompoundKind.arr:
if v.isSequence and p.pcompound.arr.items.len == v.sequence.len:
result = true
for i, pp in p.pcompound.arr.items:
if not match(bindings, pp, v[i]):
result = false
break
of PCompoundKind.dict:
if v.isDictionary:
result = true
for key, pp in p.pcompound.dict.entries:
let vv = step(v, key)
if vv.isNone or not match(bindings, pp, get vv):
result = true
break
proc match(p: Pattern; v: Value): Option[Bindings] =
var b: Bindings
if match(b, p, v):
result = some b
proc instantiate(t: Template; bindings: Bindings): Value =
case t.orKind
of TemplateKind.Tattenuate:
let v = instantiate(t.tattenuate.template, bindings)
let cap = v.unembed(Cap)
if cap.isNone:
raise newException(ValueError, "Attempt to attenuate non-capability")
result = attenuate(get cap, t.tattenuate.attenuation).embed
of TemplateKind.TRef:
let n = $t.tref.binding.int
try: result = bindings[n.toPreserves]
except KeyError:
raise newException(ValueError, "unbound reference: " & n)
of TemplateKind.Lit:
result = t.lit.value
of TemplateKind.Tcompound:
case t.tcompound.orKind
of TCompoundKind.rec:
result = initRecord(t.tcompound.rec.label, t.tcompound.rec.fields.len)
for i, tt in t.tcompound.rec.fields:
result[i] = instantiate(tt, bindings)
of TCompoundKind.arr:
result = initSequence(t.tcompound.arr.items.len)
for i, tt in t.tcompound.arr.items:
result[i] = instantiate(tt, bindings)
of TCompoundKind.dict:
result = initDictionary()
for key, tt in t.tcompound.dict.entries:
result[key] = instantiate(tt, bindings)
proc rewrite(r: Rewrite; v: Value): Value =
let bindings = match(r.pattern, v)
if bindings.isSome:
result = instantiate(r.template, get bindings)
proc examineAlternatives(cav: Caveat; v: Value): Value =
case cav.orKind
of CaveatKind.Rewrite:
result = rewrite(cav.rewrite, v)
of CaveatKind.Alts:
for r in cav.alts.alternatives:
result = rewrite(r, v)
if not result.isFalse: break
of CaveatKind.Reject: discard
of CaveatKind.unknown: discard
proc runRewrites(v: Value; a: openarray[Caveat]): Value =
result = v
for stage in a:
result = examineAlternatives(stage, result)
if result.isFalse: break
proc setActions*(entity;
publish = PublishProc();
retract = RetractProc();
message = MessageProc();
sync = SyncProc();
): Entity {.discardable} =
## Set the action handlers for an `Entity`.
result = entity
result.publishImpl = publish
result.retractImpl = retract
result.messageImpl = message
result.syncImpl = sync
proc publish(c: Cont; e: Entity; v: Value; h: Handle): Cont {.cpsMagic.} =
if not e.publishImpl.fn.isNil:
result = pass(c, e.publishImpl.call(e, v, h))
proc retract(c: Cont; e: Entity; h: Handle): Cont {.cpsMagic.} =
if not e.retractImpl.fn.isNil:
result = pass(c, e.retractImpl.call(e, h))
proc message(c: Cont; e: Entity; v: Value): Cont {.cpsMagic.} =
if not e.messageImpl.fn.isNil:
result = pass(c, e.messageImpl.call(e, v))
proc sync(c: Cont; e: Entity; p: Cap): Cont {.cpsMagic.} =
if not e.syncImpl.fn.isNil:
result = pass(c, e.syncImpl.call(e, p))
proc turnPublish(cap: Cap; val: Value; h: Handle) {.turnWork.} =
when traceSyndicate:
var traceEvent = TargetedTurnEvent(
target: cap.traceTarget,
detail: trace.TurnEvent(orKind: trace.TurnEventKind.assert)
)
traceEvent.detail = trace.TurnEvent(orKind: TurnEventKind.assert)
traceEvent.detail.assert = TurnEventAssert(
assertion: AssertionDescription(orKind: AssertionDescriptionKind.value),
handle: h,
)
traceEvent.detail.assert.assertion.value.value = val
cap.actor.traceEnqueue(traceEvent)
cap.relay.outbound[h] = OutboundAssertion(handle: h, peer: cap)
yieldToActions()
if activeTurn().rollback:
cap.relay.outbound.del(h)
else:
when traceSyndicate:
cap.actor.traceDequeue(traceEvent)
cap.target.publish(val, h)
cap.relay.outbound[h].established = true
proc turnRetract(cap: Cap; h: Handle) {.turnWork.} =
when traceSyndicate:
var traceEvent = TargetedTurnEvent(
target: cap.traceTarget,
detail: trace.TurnEvent(orKind: trace.TurnEventKind.retract)
)
traceEvent.detail.retract.handle = h
cap.actor.traceEnqueue(traceEvent)
yieldToActions()
if not activeTurn().rollback:
when traceSyndicate:
cap.actor.traceDequeue(traceEvent)
var e: OutboundAssertion
if cap.relay.outbound.pop(h, e):
cap.target.retract(h)
proc turnMessage(cap: Cap; val: Value) {.turnWork.} =
var val = runRewrites(val, cap.attenuation)
when traceSyndicate:
var traceEvent = TargetedTurnEvent(
target: cap.traceTarget,
detail: trace.TurnEvent(orKind: trace.TurnEventKind.message)
)
traceEvent.detail.message.body.value.value = val
cap.actor.traceEnqueue(traceEvent)
yieldToActions()
if not activeTurn().rollback:
when traceSyndicate:
cap.actor.traceDequeue(traceEvent)
cap.target.message(val)
proc turnSync(cap: Cap; peer: Cap) {.turnWork.} =
when traceSyndicate:
var traceEvent = TargetedTurnEvent(
target: cap.traceTarget,
detail: trace.TurnEvent(orKind: trace.TurnEventKind.sync)
)
traceEvent.detail.sync.peer = peer.traceTarget
cap.actor.traceEnqueue(traceEvent)
yieldToActions()
if not activeTurn().rollback:
when traceSyndicate:
cap.actor.traceDequeue(traceEvent)
cap.target.sync(peer)
proc publish*(cap; val: Value): Handle =
## Publish a `Value` to a `Cap` returning `Handle`
## for later retraction.
# The publish action on an Entity always goes through
# here first.
var val = runRewrites(val, cap.attenuation)
# TODO: attenuation to nothing?
result = cap.relay.nextHandle()
cap.relay.queueWork(whelp turnPublish(cap, val, result))
proc publish*[T](cap; x: T): Handle =
## Publish Preserves-convertable value to a `Cap`
## returning `Handle` for later retraction.
publish(cap, x.toPreserves)
proc retract*(cap; h: Handle) =
## Retract a `Handle` from a `Cap`.
cap.relay.queueWork(whelp turnRetract(cap, h))
proc message*(cap; val: Value) =
var val = runRewrites(val, cap.attenuation)
cap.relay.queueWork(whelp turnMessage(cap, val))
proc message*[T](cap; x: T) =
message(cap, x.toPreserves)
proc sync*(cap, peer: Cap) =
cap.relay.queueWork(whelp turnSync(cap, peer))
proc installStopHook(c: Cont, facet: Facet): Cont {.cpsMagic.} =
facet.stopHandlers.add(c.fn)
return c
proc addOnStopHandler(c: Cont; cb: Callback): Cont {.cpsMagic.} =
c.facet.stopCallbacks.add(cb)
result = c
proc onStop*(facet; cb: Callback) =
facet.stopCallbacks.add(cb)
proc facetCall(prc: FacetProc; f: Facet) {.cps: Cont.} =
prc(f)
proc facetCall(prc: FacetProc) {.cps: Cont.} =
prc(activeFacet())
proc workCall(cb: Callback) {.cps: Cont.} =
cb()
proc god(facet): Actor =
## Return the parent of all actors associated with `facet`.
var facet = facet
while not facet.parent.isNil:
facet = facet.parent
facet.actor
proc newExternalTurn(facet; desc: Value) =
result = Turn(facet: facet)
let actor = facet.actor
when traceSyndicate:
result.desc.cause = TurnCause(orKind: TurnCauseKind.external))
result.desc.cause.external description = desc
proc runExternalTurn*(facet: Facet; cb: proc ()) =
echo "startExternalTurn"
startExternalTurn(facet)
facet.queueWork(whelp workCall(cb))
while run(facet.actor): discard
echo "runExternalTurn finished"
assert facet.actor.turn.work.len == 0
assert facet.actor.turn.actions.len == 0
proc newFacet(turn: Turn; actor; parent: Facet): Facet =
inc(actor.facetIdAllocator)
result = Facet(
actor: actor,
parent: parent,
id: actor.facetIdAllocator.toPreserves,
)
if not parent.isNil:
parent.children.add result
when traceSyndicate:
var act = ActionDescription(orKind: ActionDescriptionKind.facetstart)
collectPath(act.facetstart.path, result)
actor.turn.desc.actions.add act
proc newActor(parent: Facet; name: string): Actor =
result = Actor(id: name.toPreserves)
result.root = newFacet(result, parent)
when traceSyndicate:
if parent.isNil:
let path = getEnv("SYNDICATE_TRACE_FILE", "")
case path
of "": discard
of "-": result.traceStream = newFileStream(stderr)
else: result.traceStream = openFileStream(path, fmWrite)
else:
result.traceStream = parent.actor.traceStream
proc spawnActor*(parent: Facet; name: string; bootProc: FacetProc): Actor {.discardable.} =
## Spawn a new `Actor` at `parent`.
result = newActor(parent, name)
var turn = newExternalTurn(result.root, "bootActor".toPreserves)
turn.queueWork(whelp facetCall(bootProc))
queueTurn(turn)
proc bootActor*(name: string; bootProc: FacetProc): Actor =
## Boot a new `Actor`.
result = newActor(nil, name)
var turn = newExternalTurn(result.root, "bootActor".toPreserves)
turn.queueWork(whelp facetCall(bootProc))
queueTurn(turn)
proc run(turn: sink Turn) =
try:
while turn.work.len > 0:
discard trampoline turn.work.pop()
except CatchableError as err:
terminate turn.actor(err)
return
var i: int
while i < turn.actions.len:
discard trampoline turn.actions[i]
proc runTurn*() =
## Run one turn.
turnQueue.pop().run()

113
src/sam/actors/timers.nim Normal file
View File

@ -0,0 +1,113 @@
# SPDX-FileCopyrightText: ☭ Emery Hemingway
# SPDX-License-Identifier: Unlicense
import std/[asyncdispatch, times, oserrors, posix, sets, times, epoll]
import pkg/sys/[handles, ioqueue]
import preserves
import ../[bags, syndicate], ../protocols/[timer, dataspace]
export timer
type
Observe = dataspace.Observe
Time = posix.Time
when defined(linux):
import std/[epoll, posix]
{.pragma: timerfd, importc, header: "<sys/timerfd.h>".}
proc timerfd_create(clock_id: ClockId, flags: cint): cint {.timerfd.}
proc timerfd_settime(ufd: cint, flags: cint,
utmr: var Itimerspec, otmr: var Itimerspec): cint {.timerfd.}
proc timerfd_gettime(ufd: cint, curr: var Itimerspec): cint {.timerfd.}
var
TFD_NONBLOCK {.timerfd.}: cint
TFD_CLOEXEC {.timerfd.}: cint
TFD_TIMER_ABSTIME {.timerfd.}: cint
func toFloat(ts: Timespec): float =
ts.tv_sec.float + ts.tv_nsec.float / 1_000_000_000
func toTimespec(f: float): Timespec =
result.tv_sec = Time(f)
result.tv_nsec = clong(uint64(f * 1_000_000_000) mod 1_000_000_000)
proc `<`(a, b: Timespec): bool =
a.tv_sec.clong <= b.tv_sec.clong and a.tv_nsec <= b.tv_nsec
proc `+`(a, b: Timespec): Timespec =
result.tv_sec = Time a.tv_sec.clong + b.tv_sec.clong
result.tv_nsec = a.tv_nsec + b.tv_nsec
proc clock_realtime: Timespec =
if clock_gettime(CLOCK_REALTIME, result) < 0:
raiseOSError(osLastError(), "clock_gettime")
proc nsec(epoch: float): clong =
clong(uint64(epoch * 1_000_000_000) mod 1_000_000_000'u64)
type
TimerDriver = ref object
facet: Facet
target: Cap
deadlines: Bag[float]
timers: HashSet[cint]
proc spawnTimerDriver(facet: Facet; cap: Cap): TimerDriver =
let driver = TimerDriver(facet: facet, target: cap)
facet.onStop do ():
for fd in driver.timers:
unregister(FD fd)
discard close(fd)
driver
proc earliestFloat(driver: TimerDriver): float =
assert driver.deadlines.len > 0
result = high float
for deadline in driver.deadlines:
if deadline < result:
result = deadline
proc await(driver: TimerDriver; deadline: float) {.asyncio.} =
let fd = timerfd_create(CLOCK_REALTIME, TFD_NONBLOCK or TFD_CLOEXEC)
if fd < 0:
raiseOSError(osLastError(), "failed to acquire timer descriptor")
var
old: Itimerspec
its = Itimerspec(it_value: deadline.toTimeSpec)
if timerfd_settime(fd, TFD_TIMER_ABSTIME, its, old) < 0:
raiseOSError(osLastError(), "failed to set timeout")
driver.timers.incl(fd)
while clock_realtime().toFloat() < deadline:
wait(FD fd, Read)
if deadline in driver.deadlines:
proc pub() =
echo "publishing later-than"
discard publish(driver.target, LaterThan(seconds: deadline))
runExternalTurn(driver.facet, pub)
discard close(fd)
driver.timers.excl(fd)
proc spawnTimerActor*(ds: Cap): Actor {.discardable.} =
## Spawn a timer actor that responds to
## dataspace observations of timeouts on `ds`.
spawnActor(ds.relay, "timers") do (facet: Facet):
let driver = spawnTimerDriver(facet, ds)
let pat = inject(grab Observe(pattern: dropType LaterThan), {0: grabLit()})
during(ds, pat) do (deadline: float):
echo "timer actor sees observation of ", LaterThan(seconds: deadline)
if change(driver.deadlines, deadline, +1) == cdAbsentToPresent:
discard trampoline(whelp await(driver, deadline))
do:
discard change(driver.deadlines, deadline, -1, clamp = true)
proc after*(ds: Cap; dur: Duration; cb: proc () {.closure.}) =
## Execute `cb` after some duration of time.
var
later = clock_realtime().toFloat() +
dur.inMilliseconds.float / 1_000.0
pat = ?LaterThan(seconds: later)
onPublish(ds, pat):
cb()

View File

@ -4,7 +4,7 @@
## An unordered association of items to counts.
## An item count may be negative, unlike CountTable.
import std/[assertions, tables]
import tables
type
ChangeDescription* = enum

View File

@ -6,21 +6,17 @@ runnableExamples:
let sturdy = mint()
check $sturdy == """<ref {oid: "syndicate" sig: #x"69ca300c1dbfa08fba692102dd82311a"}>"""
import
std/[options, tables],
pkg/nimcrypto/[blake2, hmac],
pkg/preserves,
./protocols/sturdy
import std/[options, tables]
from std/sequtils import toSeq
import hashlib/misc/blake2
import preserves
import ./protocols/sturdy
export `$`
proc hmac(key, data: openarray[byte]): seq[byte] =
result = newSeq[byte](32)
var ctx: HMAC[blake2_256]
ctx.init key
ctx.update data
discard ctx.finish result
result.setLen 16
count[Hmac[BLAKE2S_256]](key, data).data[0..15].toSeq
proc mint*(key: openarray[byte]; oid: Value): SturdyRef =
result.parameters.oid = oid

View File

@ -1,9 +1,8 @@
# SPDX-FileCopyrightText: ☭ Emery Hemingway
# SPDX-FileCopyrightText: 2021 ☭ Emery Hemingway
# SPDX-License-Identifier: Unlicense
import
std/[options, sets, tables],
pkg/preserves
import preserves
import options, sets, tables
type
Set = HashSet
@ -52,7 +51,7 @@ iterator observersOf[Sid, Oid](g: Graph[Sid, Oid]; oid: Oid): Sid =
if g.edgesForward.hasKey(oid):
for sid in g.edgesForward[oid]: yield sid
proc repairDamage*[Sid, Oid](g: var Graph[Sid, Oid]; repairNode: proc (sid: Sid) {.closure.}) =
proc repairDamage*[Sid, Oid](g: var Graph[Sid, Oid]; repairNode: proc (sid: Sid) {.gcsafe.}) =
var repairedThisRound: Set[Oid]
while true:
var workSet = move g.damagedNodes

52
src/sam/dataspaces.nim Normal file
View File

@ -0,0 +1,52 @@
# SPDX-FileCopyrightText: ☭ Emery Hemingway
# SPDX-License-Identifier: Unlicense
import std/[hashes, options, tables]
import pkg/cps
import preserves
import ./[actors, patterns, skeletons]
from ./protocols/protocol import Handle
from ./protocols/dataspace import Observe
type
Dataspace {.final.} = ref object of Entity
index: Index
handleMap: Table[Handle, Value]
proc dsPublish(e: Entity; v: Value; h: Handle) {.cps: Cont.} =
var ds = Dataspace(e)
if ds.index.add(v):
var obs = v.preservesTo(Observe)
if obs.isSome and obs.get.observer of Cap:
ds.index.add(obs.get.pattern, Cap(obs.get.observer))
ds.handleMap[h] = v
proc dsRetract(e: Entity; h: Handle) {.cps: Cont.} =
var ds = Dataspace(e)
var v = ds.handleMap[h]
if ds.index.remove(v):
ds.handleMap.del h
var obs = v.preservesTo(Observe)
if obs.isSome and obs.get.observer of Cap:
ds.index.remove(obs.get.pattern, Cap(obs.get.observer))
proc dsMessage(e: Entity; v: Value) {.cps: Cont.} =
var ds = Dataspace(e)
ds.index.deliverMessage(v)
proc newDataspace*(f: Facet): Cap =
var ds = Dataspace(
index: initIndex(),
).setActions(
publish = whelp dsPublish,
retract = whelp dsRetract,
message = whelp dsMessage,
)
newCap(f, ds)
proc observe*(cap: Cap; pat: Pattern; peer: Cap): Handle =
publish(cap, Observe(pattern: pat, observer: peer))
proc observe*(cap: Cap; pat: Pattern; e: Entity): Handle =
observe(cap, pat, newCap(cap.relay, e))

49
src/sam/durings.nim Normal file
View File

@ -0,0 +1,49 @@
# SPDX-FileCopyrightText: ☭ Emery Hemingway
# SPDX-License-Identifier: Unlicense
import std/[hashes, tables]
import preserves
import pkg/cps
import ./[actors, patterns]
type
DuringProc* = proc (f: Facet; a: Value; h: Handle): FacetProc {.closure.}
DuringActionKind = enum null, dead, act
DuringAction = object
case kind: DuringActionKind
of null, dead: discard
of act:
retractProc: FacetProc
DuringEntity {.final.}= ref object of Entity
publishProc: DuringProc
assertionMap: Table[Handle, DuringAction]
proc duringPublish(e: Entity; v: Value; h: Handle) {.cps: Cont.} =
var de = DuringEntity(e)
let g = de.assertionMap.getOrDefault h
case g.kind
of null, dead:
var cb = de.publishProc(e.facet, v, h)
de.assertionMap[h] = DuringAction(kind: act, retractProc: cb)
of act:
raiseAssert("during: duplicate handle in publish: " & $h)
proc duringRetract(e: Entity; h: Handle) {.cps: Cont.} =
var de = DuringEntity(e)
let g = de.assertionMap.getOrDefault h
case g.kind
of null:
de.assertionMap[h] = DuringAction(kind: dead)
of dead:
raiseAssert("during: duplicate handle in retract: " & $h)
of act:
de.assertionMap.del h
if not g.retractProc.isNil:
g.retractProc(de.facet)
proc during*(cb: DuringProc): Entity =
## TODO: this doesn't follow Nim idom well.
DuringEntity(publishProc: cb).setActions(
publish = whelp duringPublish,
retract = whelp duringRetract,
)

File diff suppressed because one or more lines are too long

View File

@ -3,7 +3,7 @@
import std/[hashes, tables]
import ./actors
from ./actors import Cap, hash
from ./protocols/sturdy import Oid
proc hash(r: Cap): Hash = !$(r.relay.hash !& r.target.unsafeAddr.hash)

9
src/sam/observers.nim Normal file
View File

@ -0,0 +1,9 @@
# SPDX-FileCopyrightText: ☭ Emery Hemingway
# SPDX-License-Identifier: Unlicense
import preserves
import ./actors, ./patterns, ./protocols/dataspace
proc observe*(turn: var Turn; ds: Cap; pat: Pattern; e: Entity): Cap {.discardable.} =
result = newCap(turn, e)
publish(turn, ds, Observe(pattern: pat, observer: result))

437
src/sam/patterns.nim Normal file
View File

@ -0,0 +1,437 @@
# SPDX-FileCopyrightText: ☭ Emery Hemingway
# SPDX-License-Identifier: Unlicense
import std/[algorithm, options, sequtils, tables, typetraits]
import preserves
import ./protocols/dataspacePatterns
from ./actors import Cap
export dataspacePatterns.`$`, PatternKind, DCompoundKind, AnyAtomKind
type
AnyAtom = dataspacePatterns.AnyAtom
DBind = dataspacePatterns.DBind
DCompound = dataspacePatterns.DCompound
DCompoundArr = dataspacePatterns.DCompoundArr
DCompoundDict = dataspacePatterns.DCompoundDict
DCompoundRec = dataspacePatterns.DCompoundRec
DLit = dataspacePatterns.DLit
Pattern* = dataspacePatterns.Pattern
iterator orderedEntries*(dict: DCompoundDict): (Value, Pattern) =
## Iterate a `DCompoundDict` in Preserves order.
## Values captured from a dictionary are represented as an
## array of values ordered by their former key, so using an
## ordered iterator is sometimes essential.
var keys = dict.entries.keys.toSeq
sort(keys, preserves.cmp)
for k in keys:
yield(k, dict.entries.getOrDefault(k))
# getOrDefault doesn't raise and we know the keys will match
proc toPattern(d: sink DBind): Pattern =
Pattern(orKind: PatternKind.DBind, dbind: d)
proc toPattern(d: sink DLit): Pattern =
Pattern(orKind: PatternKind.DLit, dlit: d)
proc toPattern(aa: sink AnyAtom): Pattern =
DLit(value: aa).toPattern
proc toPattern(d: sink DCompound): Pattern =
Pattern(orKind: PatternKind.DCompound, dcompound: d)
proc toPattern(d: sink DCompoundRec): Pattern =
DCompound(orKind: DCompoundKind.rec, rec: d).toPattern
proc toPattern(d: sink DCompoundArr): Pattern =
DCompound(orKind: DCompoundKind.arr, arr: d).toPattern
proc toPattern(d: sink DCompoundDict): Pattern =
DCompound(orKind: DCompoundKind.dict, dict: d).toPattern
proc drop*(): Pattern {.inline.} = Pattern(orKind: PatternKind.DDiscard)
## Create a pattern to match any value without capture.
proc grab*(): Pattern {.inline.} = DBind(pattern: drop()).toPattern
## Create a pattern to capture any value.
proc grab*(pr: Value): Pattern =
## Convert a `Preserve` value to a `Pattern`.
runnableExamples:
from std/unittest import check
import preserves
check:
$(grab parsePreserves"""<foo "bar" #"00" [0 1 2.0] {maybe: #t} <_>>""") ==
"""<rec foo [<lit "bar"> <lit #"00"> <arr [<lit 0> <lit 1> <lit 2.0>]> <dict {maybe: <lit #t>}> <_>]>"""
case pr.kind
of pkBoolean:
AnyAtom(orKind: AnyAtomKind.`bool`, bool: pr.bool).toPattern
of pkFloat:
AnyAtom(orKind: AnyAtomKind.`double`, double: pr.float).toPattern
of pkRegister:
AnyAtom(orKind: AnyAtomKind.`int`, int: pr.register).toPattern
of pkString:
AnyAtom(orKind: AnyAtomKind.`string`, string: pr.string).toPattern
of pkByteString:
AnyAtom(orKind: AnyAtomKind.`bytes`, bytes: pr.bytes).toPattern
of pkSymbol:
AnyAtom(orKind: AnyAtomKind.`symbol`, symbol: pr.symbol).toPattern
of pkRecord:
if (pr.isRecord("_") and pr.arity == 0) or (pr.isRecord("bind") and pr.arity == 1):
drop()
else:
DCompoundRec(
label: pr.label,
fields: map[Value, Pattern](pr.fields, grab)).toPattern
of pkSequence:
DCompoundArr(items: map(pr.sequence, grab)).toPattern
of pkSet:
raiseAssert "cannot construct a pattern over a set literal"
of pkDictionary:
var dict = DCompoundDict()
for key, val in pr.pairs: dict.entries[key] = grab val
dict.toPattern
of pkEmbedded:
if pr.embeddedRef.isNil: drop()
else:
AnyAtom(orKind: AnyAtomKind.`embedded`, embedded: pr.embeddedRef).toPattern
else:
raise newException(ValueError, "cannot generate a pattern for unhandled Value type")
proc grab*[T](x: T): Pattern =
## Construct a `Pattern` from value of type `T`.
runnableExamples:
from std/unittest import check
check:
$grab(true) == "<lit #t>"
$grab(3.14) == "<lit 3.14>"
$grab([0, 1, 2, 3]) == "<arr [<lit 0> <lit 1> <lit 2> <lit 3>]>"
grab(x.toPreserves)
proc grabType*(typ: static typedesc): Pattern =
## Derive a `Pattern` from type `typ`.
## This works for `tuple` and `object` types but in the
## general case will return a wildcard binding.
runnableExamples:
import preserves
from std/unittest import check
check:
$grabType(array[3, int]) ==
"""<arr [<bind <_>> <bind <_>> <bind <_>>]>"""
type
Point = tuple[x: int; y: int]
Rect {.preservesRecord: "rect".} = tuple[a: Point; B: Point]
ColoredRect {.preservesDictionary.} = tuple[color: string; rect: Rect]
check:
$(grabType Point) ==
"<arr [<bind <_>> <bind <_>>]>"
$(grabType Rect) ==
"<rec rect [<arr [<bind <_>> <bind <_>>]> <arr [<bind <_>> <bind <_>>]>]>"
$(grabType ColoredRect) ==
"<dict {color: <bind <_>> rect: <rec rect [<arr [<bind <_>> <bind <_>>]> <arr [<bind <_>> <bind <_>>]>]>}>"
when typ is ref:
grabType(pointerBase(typ))
elif typ.hasPreservesRecordPragma:
var rec = DCompoundRec(label: typ.recordLabel.toSymbol)
for _, f in fieldPairs(default typ):
add(rec.fields, grabType(typeof f))
result = rec.toPattern
elif typ.hasPreservesDictionaryPragma:
var dict = DCompoundDict()
for key, val in fieldPairs(default typ):
dict.entries[key.toSymbol] = grabType(typeof val)
dict.toPattern
elif typ is tuple:
var arr = DCompoundArr()
for _, f in fieldPairs(default typ):
add(arr.items, grabType(typeof f))
arr.toPattern
elif typ is array:
var arr = DCompoundArr()
arr.items.setLen(len(typ))
for e in arr.items.mitems: e = grab()
arr.toPattern
else:
grab()
proc fieldCount(T: typedesc): int =
for _, _ in fieldPairs(default T):
inc result
proc dropType*(typ: static typedesc): Pattern =
## Derive a `Pattern` from type `typ` without any bindings.
when typ is ref:
dropType(pointerBase(typ))
elif typ.hasPreservesRecordPragma:
var rec = DCompoundRec(label: typ.recordLabel.toSymbol)
rec.fields.setLen(fieldCount typ)
for i, _ in rec.fields:
rec.fields[i] = drop()
result = rec.toPattern
elif typ.hasPreservesDictionaryPragma:
DCompoundDict().toPattern
elif typ is tuple:
var arr = DCompoundArr()
arr.items.setLen(len typ)
for i, _ in arr.items:
arr.items[i] = drop()
arr.toPattern
elif typ is array:
var arr = DCompoundArr()
arr.items.setLen(len(typ))
for e in arr.items.mitems: e = drop()
arr.toPattern
else:
drop()
proc lookup(bindings: openArray[(int, Pattern)]; i: int): Pattern =
for (j, b) in bindings:
if i == j: return b
return drop()
proc grab*(typ: static typedesc; bindings: sink openArray[(int, Pattern)]): Pattern =
## Construct a `Pattern` from type `typ` with pattern `bindings` by integer offset.
when typ is ptr | ref:
grab(pointerBase(typ), bindings)
elif typ.hasPreservesRecordPragma:
var rec = DCompoundRec(label: typ.recordLabel.toSymbol)
rec.fields.setLen(fieldCount typ)
var i: int
for _, f in fieldPairs(default typ):
rec.fields[i] = lookup(bindings, i)
inc i
result = rec.toPattern
elif typ is tuple:
var arr = DCompoundArr()
arr.items.setLen(fieldCount typ)
var i: int
for _, f in fieldPairs(default typ):
arr.items[i] = lookup(bindings, i)
inc i
result = arr.toPattern
else:
{.error: "grab with indexed bindings not implemented for " & $typ.}
proc grab*(typ: static typedesc; bindings: sink openArray[(Value, Pattern)]): Pattern =
## Construct a `Pattern` from type `typ` with dictionary field `bindings`.
when typ.hasPreservesDictionaryPragma:
DCompoundDict(entries: bindings.toTable).toPattern
else:
{.error: "grab with dictionary bindings not implemented for " & $typ.}
proc grabLit*(): Pattern =
runnableExamples:
from std/unittest import check
check:
$grabLit() == """<rec lit [<bind <_>>]>"""
grabType(dataspacePatterns.DLit)
proc grabDict*(): Pattern =
grabType(dataspacePatterns.DCompoundDict)
proc unpackLiterals*(pr: Value): Value =
result = pr
apply(result) do (pr: var Value):
if pr.isRecord("lit", 1) or pr.isRecord("dict", 1) or pr.isRecord("arr", 1) or pr.isRecord("set", 1):
pr = pr.record[0]
proc inject*(pat: Pattern; bindings: openArray[(int, Pattern)]): Pattern =
## Construct a `Pattern` from `pat` with injected overrides from `bindings`.
## Injects are made at offsets indexed by the discard (`<_>`) patterns in `pat`.
proc inject(pat: Pattern; bindings: openArray[(int, Pattern)]; offset: var int): Pattern =
case pat.orKind
of PatternKind.DDiscard:
result = pat
for (off, injection) in bindings:
if off == offset:
result = injection
break
inc offset
of PatternKind.DBind:
let bindOff = offset
result = pat
result.dbind.pattern = inject(pat.dbind.pattern, bindings, offset)
if result.orKind == PatternKind.DBind:
for (off, injection) in bindings:
if (off == bindOff) and (result.dbind.pattern == injection):
result = result.dbind.pattern
break # promote the injected pattern over the bind
of PatternKind.DLit:
result = pat
of PatternKind.DCompound:
result = pat
case pat.dcompound.orKind
of DCompoundKind.rec:
var fields = mapIt(pat.dcompound.rec.fields):
inject(it, bindings, offset)
result = DCompoundRec(label: result.dcompound.rec.label, fields: fields).toPattern
of DCompoundKind.arr:
var items = mapIt(pat.dcompound.arr.items):
inject(it, bindings, offset)
result = DCompoundArr(items: items).toPattern
of DCompoundKind.dict:
var dict = DCompoundDict()
for key, val in pat.dcompound.dict.entries:
dict.entries[key] = inject(val, bindings, offset)
result = toPattern(dict)
var offset = 0
inject(pat, bindings, offset)
proc inject*(pat: Pattern; bindings: openArray[(Value, Pattern)]): Pattern =
## Inject `bindings` into a dictionary pattern.
assert pat.orKind == PatternKind.DCompound
assert pat.dcompound.orKind == DCompoundKind.dict
result = pat
for (key, val) in bindings:
result.dcompound.dict.entries[key] = val
proc grabRecord*(label: Value, fields: varargs[Pattern]): Pattern =
runnableExamples:
from std/unittest import check
import syndicate/actors, preserves
check:
$grabRecord("Says".toSymbol, grab(), grab()) ==
"""<rec Says [<bind <_>> <bind <_>>]>"""
DCompoundRec(label: label, fields: fields.toSeq).toPattern
proc grabRecord*(label: string, fields: varargs[Pattern]): Pattern =
## Sugar for creating record patterns.
## `label` is converted to a symbol value.
grabRecord(label.toSymbol, fields)
proc grabDictionary*(bindings: sink openArray[(Value, Pattern)]): Pattern =
## Construct a pattern that grabs some dictionary pairs.
DCompoundDict(entries: bindings.toTable).toPattern
proc grabDictionary*(bindings: sink openArray[(string, Pattern)]): Pattern =
## Construct a pattern that grabs some dictionary pairs.
## Keys are converted from strings to symbols.
result = DCompoundDict().toPattern
for (key, val) in bindings.items:
result.dcompound.dict.entries[key.toSymbol] = val
proc depattern(comp: DCompound; values: var seq[Value]; index: var int): Value {.gcsafe.}
proc depattern(pat: Pattern; values: var seq[Value]; index: var int): Value =
case pat.orKind
of PatternKind.DDiscard:
discard
of PatternKind.DBind:
if index < values.len:
result = move values[index]
inc index
of PatternKind.DLit:
result = pat.dlit.value.toPreserves
of PatternKind.DCompound:
result = depattern(pat.dcompound, values, index)
proc depattern(comp: DCompound; values: var seq[Value]; index: var int): Value {.gcsafe.} =
case comp.orKind
of DCompoundKind.rec:
result = initRecord(comp.rec.label, comp.rec.fields.len)
for i, f in comp.rec.fields:
result[i] = depattern(f, values, index)
of DCompoundKind.arr:
result = initSequence(comp.arr.items.len)
for i, e in comp.arr.items:
result[i] = depattern(e, values, index)
of DCompoundKind.dict:
result = initDictionary(Cap)
for key, val in comp.dict.entries:
result[key] = depattern(val, values, index)
proc depattern*(pat: Pattern; values: sink seq[Value]): Value =
## Convert a `Pattern` to a `Value` while replacing binds with `values`.
var index: int
depattern(pat, values, index)
type Literal*[T] = object
## A wrapper type to deserialize patterns to native values.
value*: T
proc fromPreservesHook*[T](lit: var Literal[T]; pr: Value): bool =
var pat: Pattern
pat.fromPreserves(pr) and lit.value.fromPreserves(depattern(pat, @[]))
proc toPreservesHook*[T](lit: Literal[T]): Value =
lit.value.grab.toPreserves
type
Path* = seq[Value]
Paths* = seq[Path]
Captures* = seq[Value]
Analysis* = tuple
constPaths: Paths
constValues: seq[Value]
capturePaths: Paths
func walk(result: var Analysis; path: var Path; p: Pattern)
func walk(result: var Analysis; path: var Path; key: int|Value; pat: Pattern) =
path.add(key.toPreserves)
walk(result, path, pat)
discard path.pop
func walk(result: var Analysis; path: var Path; p: Pattern) =
case p.orKind
of PatternKind.DCompound:
case p.dcompound.orKind
of DCompoundKind.rec:
for k, e in p.dcompound.rec.fields: walk(result, path, k, e)
of DCompoundKind.arr:
for k, e in p.dcompound.arr.items: walk(result, path, k, e)
of DCompoundKind.dict:
for k, e in p.dcompound.dict.orderedEntries: walk(result, path, k, e)
of PatternKind.DBind:
result.capturePaths.add(path)
walk(result, path, p.dbind.pattern)
of PatternKind.DDiscard: discard
of PatternKind.DLit:
result.constPaths.add(path)
result.constValues.add(p.dlit.value.toPreserves)
func analyse*(p: Pattern): Analysis =
var path: Path
walk(result, path, p)
func projectPaths*(v: Value; paths: Paths): Option[Captures] =
var res = newSeq[Value](paths.len)
for i, path in paths:
var vv = step(v, path)
if vv.isSome: res[i] = get(vv)
else: return
some res
proc matches*(pat: Pattern; pr: Value): bool =
let analysis = analyse(pat)
assert analysis.constPaths.len == analysis.constValues.len
for i, path in analysis.constPaths:
let v = step(pr, path)
if v.isNone: return false
if analysis.constValues[i] != v.get: return false
for path in analysis.capturePaths:
if isNone step(pr, path): return false
true
func capture*(pat: Pattern; pr: Value): seq[Value] =
let analysis = analyse(pat)
assert analysis.constPaths.len == analysis.constValues.len
for i, path in analysis.constPaths:
let v = step(pr, path)
if v.isNone : return @[]
if analysis.constValues[i] != v.get: return @[]
for path in analysis.capturePaths:
let v = step(pr, path)
if v.isNone: return @[]
result.add(get v)
when isMainModule:
let txt = readAll stdin
if txt != "":
let
v = parsePreserves(txt)
pat = grab v
stdout.writeLine(pat)

View File

@ -5,7 +5,6 @@ modules += gatekeeper.nim
modules += http.nim
modules += noise.nim
modules += protocol.nim
modules += rpc.nim
modules += service.nim
modules += stdenv.nim
modules += stream.nim
@ -17,7 +16,7 @@ modules += transportAddress.nim
modules += worker.nim
: ../../../../syndicate-protocols/schema-bundle.bin \
|> !preserves-schema-nim \
|> !preserves_schema_nim \
|> $(modules) | $(SYNDICATE_PROTOCOL)
: foreach $(modules) | $(modules) |> !nim_check |>

View File

@ -0,0 +1,86 @@
import
preserves, std/tables
type
AnyAtomKind* {.pure.} = enum
`bool`, `double`, `int`, `string`, `bytes`, `symbol`, `embedded`
`AnyAtom`* {.preservesOr.} = object
case orKind*: AnyAtomKind
of AnyAtomKind.`bool`:
`bool`*: bool
of AnyAtomKind.`double`:
`double`*: float
of AnyAtomKind.`int`:
`int`*: BiggestInt
of AnyAtomKind.`string`:
`string`*: string
of AnyAtomKind.`bytes`:
`bytes`*: seq[byte]
of AnyAtomKind.`symbol`:
`symbol`*: Symbol
of AnyAtomKind.`embedded`:
`embedded`* {.preservesEmbedded.}: EmbeddedRef
DLit* {.preservesRecord: "lit".} = object
`value`*: AnyAtom
DBind* {.preservesRecord: "bind".} = object
`pattern`*: Pattern
DDiscard* {.preservesRecord: "_".} = object
DCompoundKind* {.pure.} = enum
`rec`, `arr`, `dict`
DCompoundRec* {.preservesRecord: "rec".} = object
`label`*: Value
`fields`*: seq[Pattern]
DCompoundArr* {.preservesRecord: "arr".} = object
`items`*: seq[Pattern]
DCompoundDict* {.preservesRecord: "dict".} = object
`entries`*: Table[Value, Pattern]
`DCompound`* {.preservesOr.} = object
case orKind*: DCompoundKind
of DCompoundKind.`rec`:
`rec`* {.preservesEmbedded.}: DCompoundRec
of DCompoundKind.`arr`:
`arr`* {.preservesEmbedded.}: DCompoundArr
of DCompoundKind.`dict`:
`dict`* {.preservesEmbedded.}: DCompoundDict
PatternKind* {.pure.} = enum
`DDiscard`, `DBind`, `DLit`, `DCompound`
`Pattern`* {.acyclic, preservesOr.} = ref object
case orKind*: PatternKind
of PatternKind.`DDiscard`:
`ddiscard`*: DDiscard
of PatternKind.`DBind`:
`dbind`* {.preservesEmbedded.}: DBind
of PatternKind.`DLit`:
`dlit`* {.preservesEmbedded.}: DLit
of PatternKind.`DCompound`:
`dcompound`* {.preservesEmbedded.}: DCompound
proc `$`*(x: AnyAtom | DLit | DBind | DDiscard | DCompound | Pattern): string =
`$`(toPreserves(x))
proc encode*(x: AnyAtom | DLit | DBind | DDiscard | DCompound | Pattern): seq[
byte] =
encode(toPreserves(x))

View File

@ -13,15 +13,15 @@ type
`pathSteps`* {.preservesTupleTail.}: seq[PathStep]
BindObserverKind* {.pure.} = enum
`absent`, `present`
`present`, `absent`
`BindObserver`* {.preservesOr.} = object
case orKind*: BindObserverKind
of BindObserverKind.`absent`:
`absent`* {.preservesLiteral: "#f".}: bool
of BindObserverKind.`present`:
`present`* {.preservesEmbedded.}: EmbeddedRef
of BindObserverKind.`absent`:
`absent`* {.preservesLiteral: "#f".}: bool
TransportConnection* {.preservesRecord: "connect-transport".} = object
`addr`*: Value
@ -35,18 +35,18 @@ type
`resolved`*: Resolved
BoundKind* {.pure.} = enum
`Rejected`, `bound`
`bound`, `Rejected`
BoundBound* {.preservesRecord: "bound".} = object
`pathStep`*: PathStep
`Bound`* {.preservesOr.} = object
case orKind*: BoundKind
of BoundKind.`Rejected`:
`rejected`*: Rejected
of BoundKind.`bound`:
`bound`*: BoundBound
of BoundKind.`Rejected`:
`rejected`*: Rejected
ForceDisconnect* {.preservesRecord: "force-disconnect".} = object
@ -59,18 +59,18 @@ type
`observer`* {.preservesEmbedded.}: EmbeddedRef
ResolvedKind* {.pure.} = enum
`Rejected`, `accepted`
`accepted`, `Rejected`
ResolvedAccepted* {.preservesRecord: "accepted".} = object
`responderSession`* {.preservesEmbedded.}: EmbeddedRef
`Resolved`* {.preservesOr.} = object
case orKind*: ResolvedKind
of ResolvedKind.`Rejected`:
`rejected`*: Rejected
of ResolvedKind.`accepted`:
`accepted`* {.preservesEmbedded.}: ResolvedAccepted
of ResolvedKind.`Rejected`:
`rejected`*: Rejected
TransportControl* = ForceDisconnect
ResolvePath* {.preservesRecord: "resolve-path".} = object

View File

@ -47,14 +47,25 @@ type
HttpRequest* {.preservesRecord: "http-request".} = object
`sequenceNumber`*: BiggestInt
`host`*: RequestHost
`host`*: string
`port`*: BiggestInt
`method`*: Symbol
`path`*: seq[string]
`headers`*: Headers
`query`*: Table[Symbol, seq[QueryValue]]
`body`*: Value
`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`
@ -104,17 +115,6 @@ type
`req`*: HttpRequest
`res`* {.preservesEmbedded.}: Value
RequestHostKind* {.pure.} = enum
`absent`, `present`
`RequestHost`* {.preservesOr.} = object
case orKind*: RequestHostKind
of RequestHostKind.`absent`:
`absent`* {.preservesLiteral: "#f".}: bool
of RequestHostKind.`present`:
`present`*: string
PathPatternElementKind* {.pure.} = enum
`label`, `wildcard`, `rest`
`PathPatternElement`* {.preservesOr.} = object
@ -143,12 +143,12 @@ type
PathPattern* = seq[PathPatternElement]
proc `$`*(x: HostPattern | HttpListener | MethodPattern | MimeType | QueryValue |
HttpRequest |
RequestBody |
Headers |
HttpResponse |
HttpService |
HttpBinding |
HttpContext |
RequestHost |
PathPatternElement |
Chunk |
PathPattern): string =
@ -157,12 +157,12 @@ proc `$`*(x: HostPattern | HttpListener | MethodPattern | MimeType | QueryValue
proc encode*(x: HostPattern | HttpListener | MethodPattern | MimeType |
QueryValue |
HttpRequest |
RequestBody |
Headers |
HttpResponse |
HttpService |
HttpBinding |
HttpContext |
RequestHost |
PathPatternElement |
Chunk |
PathPattern): seq[byte] =

View File

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

View File

@ -30,12 +30,9 @@ type
Assertion* = Value
Handle* = BiggestInt
PacketKind* {.pure.} = enum
`Nop`, `Turn`, `Error`, `Extension`
`Turn`, `Error`, `Extension`
`Packet`* {.preservesOr.} = object
case orKind*: PacketKind
of PacketKind.`Nop`:
`nop`* {.preservesLiteral: "#f".}: bool
of PacketKind.`Turn`:
`turn`* {.preservesEmbedded.}: Turn

View File

@ -11,7 +11,6 @@ type
`label`*: Value
`seconds`*: float
`kind`*: TimerKind
`peer`* {.preservesEmbedded.}: Value
`TimerKind`* {.preservesOr, pure.} = enum
`relative`, `absolute`, `clear`

547
src/sam/relays.nim Normal file
View File

@ -0,0 +1,547 @@
# SPDX-FileCopyrightText: ☭ Emery Hemingway
# SPDX-License-Identifier: Unlicense
import std/[options, tables]
from std/os import getEnv, `/`
import pkg/sys/[ioqueue, sockets]
import preserves
import ./syndicate, /capabilities, ./durings, ./membranes, ./protocols/[gatekeeper, protocol, sturdy, transportAddress]
when defined(traceSyndicate):
when defined(posix):
template trace(args: varargs[untyped]): untyped = stderr.writeLine(args)
else:
template trace(args: varargs[untyped]): untyped = echo(args)
else:
template trace(args: varargs[untyped]): untyped = discard
export `$`
export Stdio, Tcp, WebSocket, Unix
type
Assertion = Value
Event = protocol.Event
Handle = actors.Handle
Oid = sturdy.Oid
Turn = syndicate.Turn
WireRef = sturdy.WireRef
PacketWriter = proc (turn: Turn; buf: seq[byte]) {.closure, gcsafe.}
RelaySetup = proc (turn: Turn; relay: Relay) {.closure, gcsafe.}
Relay* = ref object
facet: Facet
inboundAssertions: Table[Handle,
tuple[localHandle: Handle, imported: seq[WireSymbol]]]
outboundAssertions: Table[Handle, seq[WireSymbol]]
exported: Membrane
imported: Membrane
nextLocalOid: Oid
pendingTurn: protocol.Turn
wireBuf: BufferedDecoder
packetWriter: PacketWriter
peer: Cap
SyncPeerEntity = ref object of Entity
relay: Relay
peer: Cap
handleMap: Table[Handle, Handle]
e: WireSymbol
RelayEntity = ref object of Entity
## https://synit.org/book/protocol.html#relay-entities
label: string
relay: Relay
proc releaseCapOut(r: Relay; e: WireSymbol) =
r.exported.drop e
method publish(spe: SyncPeerEntity; t: Turn; a: AssertionRef; h: Handle) =
spe.handleMap[h] = publish(t, spe.peer, a.value)
method retract(se: SyncPeerEntity; t: Turn; h: Handle) =
var other: Handle
if se.handleMap.pop(h, other):
retract(t, other)
method message(se: SyncPeerEntity; t: Turn; a: AssertionRef) =
if not se.e.isNil:
se.relay.releaseCapOut(se.e)
message(t, se.peer, a.value)
method sync(se: SyncPeerEntity; t: Turn; peer: Cap) =
sync(t, se.peer, peer)
proc newSyncPeerEntity(r: Relay; p: Cap): SyncPeerEntity =
SyncPeerEntity(relay: r, peer: p)
proc rewriteCapOut(relay: Relay; cap: Cap; exported: var seq[WireSymbol]): WireRef =
if cap.target of RelayEntity and cap.target.RelayEntity.relay == relay and cap.attenuation.len == 0:
result = WireRef(orKind: WireRefKind.yours, yours: WireRefYours(oid: cap.target.oid))
else:
var ws = grab(relay.exported, cap)
if ws.isNil:
ws = newWireSymbol(relay.exported, relay.nextLocalOid, cap)
inc relay.nextLocalOid
exported.add ws
result = WireRef(
orKind: WireRefKind.mine,
mine: WireRefMine(oid: ws.oid))
proc rewriteOut(relay: Relay; v: Assertion):
tuple[rewritten: Value, exported: seq[WireSymbol]] {.gcsafe.} =
var exported: seq[WireSymbol]
result.rewritten = mapEmbeds(v) do (pr: Value) -> Value:
let o = pr.unembed(Cap); if o.isSome:
rewriteCapOut(relay, o.get, exported).toPreserves
else: pr
result.exported = exported
proc register(relay: Relay; v: Assertion; h: Handle): tuple[rewritten: Value, exported: seq[WireSymbol]] =
result = rewriteOut(relay, v)
relay.outboundAssertions[h] = result.exported
proc deregister(relay: Relay; h: Handle) =
var outbound: seq[WireSymbol]
if relay.outboundAssertions.pop(h, outbound):
for e in outbound: releaseCapOut(relay, e)
proc send(relay: Relay; turn: Turn; rOid: protocol.Oid; m: Event) =
if relay.pendingTurn.len > 0:
stderr.writeLine "relay has pending turn events"
# If the pending queue is empty then schedule a packet
# to be sent after pending I/O is processed.
#[
assert turn.finalizer.isNil
turn.finalizer = proc () =
relay.facet.run do (turn: Turn):
var pkt = Packet(
orKind: PacketKind.Turn,
turn: move relay.pendingTurn)
trace "C: ", pkt
relay.packetWriter(turn, encode pkt)
]#
relay.pendingTurn.add TurnEvent(oid: rOid, event: m)
proc send(re: RelayEntity; turn: Turn; ev: Event) =
send(re.relay, turn, protocol.Oid re.oid, ev)
method publish(re: RelayEntity; t: Turn; a: AssertionRef; h: Handle) {.gcsafe.} =
re.send(t, Event(
orKind: EventKind.Assert,
`assert`: protocol.Assert(
assertion: re.relay.register(a.value, h).rewritten,
handle: h)))
method retract(re: RelayEntity; t: Turn; h: Handle) {.gcsafe.} =
re.relay.deregister h
re.send(t, Event(
orKind: EventKind.Retract,
retract: Retract(handle: h)))
method message(re: RelayEntity; turn: Turn; msg: AssertionRef) {.gcsafe.} =
var (value, exported) = rewriteOut(re.relay, msg.value)
assert(len(exported) == 0, "cannot send a reference in a message")
if len(exported) == 0:
re.send(turn, Event(orKind: EventKind.Message, message: Message(body: value)))
method sync(re: RelayEntity; turn: Turn; peer: Cap) {.gcsafe.} =
var
peerEntity = newSyncPeerEntity(re.relay, peer)
exported: seq[WireSymbol]
wr = rewriteCapOut(re.relay, turn.newCap(peerEntity), exported)
peerEntity.e = exported[0]
var ev = Event(orKind: EventKind.Sync)
ev.sync.peer = wr.toPreserves.embed
re.send(turn, ev)
proc newRelayEntity(label: string; r: Relay; o: Oid): RelayEntity =
RelayEntity(label: label, relay: r, oid: o)
using
relay: Relay
facet: Facet
proc lookupLocal(relay; oid: Oid): Cap =
let sym = relay.exported.grab oid
if sym.isNil: newInertCap()
else: sym.cap
proc isInert(r: Cap): bool =
r.target.isNil
proc rewriteCapIn(relay; facet; n: WireRef, imported: var seq[WireSymbol]): Cap =
case n.orKind
of WireRefKind.mine:
var e = relay.imported.grab(n.mine.oid)
if e.isNil:
e = newWireSymbol(
relay.imported,
n.mine.oid,
newCap(facet, newRelayEntity("rewriteCapIn", relay, n.mine.oid)),
)
imported.add e
result = e.cap
of WireRefKind.yours:
let r = relay.lookupLocal(n.yours.oid)
if n.yours.attenuation.len == 0 or r.isInert: result = r
else: raiseAssert "attenuation not implemented"
proc rewriteIn(relay; facet; v: Value):
tuple[rewritten: Assertion; imported: seq[WireSymbol]] {.gcsafe.} =
var imported: seq[WireSymbol]
result.rewritten = mapEmbeds(v) do (pr: Value) -> Value:
let wr = pr.preservesTo WireRef; if wr.isSome:
result = rewriteCapIn(relay, facet, wr.get, imported).embed
else:
result = pr
result.imported = imported
proc close(r: Relay) = discard
proc dispatch(relay: Relay; turn: Turn; cap: Cap; event: Event) {.gcsafe.} =
case event.orKind
of EventKind.Assert:
let (a, imported) = rewriteIn(relay, turn.facet, event.assert.assertion)
relay.inboundAssertions[event.assert.handle] = (publish(turn, cap, a), imported,)
of EventKind.Retract:
let remoteHandle = event.retract.handle
var outbound: tuple[localHandle: Handle, imported: seq[WireSymbol]]
if relay.inboundAssertions.pop(remoteHandle, outbound):
for e in outbound.imported: relay.imported.drop e
turn.retract(outbound.localHandle)
of EventKind.Message:
let (a, imported) = rewriteIn(relay, turn.facet, event.message.body)
assert imported.len == 0, "Cannot receive transient reference"
turn.message(cap, a)
of EventKind.Sync:
discard # TODO
#[
var imported: seq[WireSymbol]
let k = relay.rewriteCapIn(turn, evenr.sync.peer, imported)
turn.sync(cap) do (turn: Turn):
turn.message(k, true)
for e in imported: relay.imported.del e
]#
proc dispatch(relay: Relay; v: Value) {.gcsafe.} =
trace "S: ", v
run(relay.facet) do (t: Turn):
var pkt: Packet
if pkt.fromPreserves(v):
case pkt.orKind
of PacketKind.Turn:
# https://synit.org/book/protocol.html#turn-packets
for te in pkt.turn:
let r = lookupLocal(relay, te.oid.Oid)
if not r.isInert:
dispatch(relay, t, r, te.event)
else:
stderr.writeLine("discarding event for unknown Cap; ", te.event)
of PacketKind.Error:
# https://synit.org/book/protocol.html#error-packets
when defined(posix):
stderr.writeLine("Error from server: ", pkt.error.message, " (detail: ", pkt.error.detail, ")")
close relay
of PacketKind.Extension:
# https://synit.org/book/protocol.html#extension-packets
discard
else:
when defined(posix):
stderr.writeLine("discarding undecoded packet ", v)
proc recv(relay: Relay; buf: seq[byte]) =
feed(relay.wireBuf, buf)
var pr = decode(relay.wireBuf)
if pr.isSome: dispatch(relay, pr.get)
type
RelayOptions* = object of RootObj
packetWriter*: PacketWriter
RelayActorOptions* = object of RelayOptions
initialOid*: Option[Oid]
initialCap*: Cap
nextLocalOid*: Option[Oid]
proc spawnRelay(name: string; turn: Turn; opts: RelayActorOptions; setup: RelaySetup) =
spawn(name, turn) do (turn: Turn):
let relay = Relay(
facet: turn.facet,
packetWriter: opts.packetWriter,
wireBuf: newBufferedDecoder(0),
)
discard relay.facet.preventInertCheck()
if not opts.initialCap.isNil:
var exported: seq[WireSymbol]
discard rewriteCapOut(relay, opts.initialCap, exported)
opts.nextLocalOid.map do (oid: Oid):
relay.nextLocalOid =
if oid == 0.Oid: 1.Oid
else: oid
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
when defined(posix):
#[
import std/asyncfile
export Unix
type StdioControlEntity = ref object of Entity
stdin: AsyncFile
method message(entity: StdioControlEntity; turn: Turn; ass: AssertionRef) =
if ass.value.preservesTo(ForceDisconnect).isSome:
close(entity.stdin)
close(stdout)
proc connectTransport(turn: Turn; ds: Cap; ta: transportAddress.Stdio) =
## Connect to an external dataspace over stdio.
proc stdoutWriter(turn: Turn; buf: seq[byte]) =
## Blocking write to stdout.
let n = writeBytes(stdout, buf, 0, buf.len)
flushFile(stdout)
if n != buf.len:
stopActor(turn)
var opts = RelayActorOptions(
packetWriter: stdoutWriter,
initialCap: ds,
initialOid: 0.Oid.some,
)
spawnRelay("stdio", turn, opts) do (turn: Turn; relay: Relay):
let
facet = turn.facet
asyncStdin = openAsync("/dev/stdin") # this is universal now?
publish(turn, ds, TransportConnection(
`addr`: ta.toPreserves,
control: StdioControlEntity(stdin: asyncStdin).newCap(turn),
resolved: relay.peer.accepted,
))
const stdinReadSize = 0x2000
proc readCb(pktFut: Future[string]) {.gcsafe.} =
if not pktFut.failed:
var buf = pktFut.read
if buf.len == 0:
run(facet) do (turn: Turn): stopActor(turn)
else:
relay.recv(cast[seq[byte]](buf))
asyncStdin.read(stdinReadSize).addCallback(readCb)
asyncStdin.read(stdinReadSize).addCallback(readCb)
proc connectStdio*(turn: 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: Turn; ass: AssertionRef) =
if ass.value.preservesTo(ForceDisconnect).isSome:
close(entity.socket)
type ShutdownEntity* = ref object of Entity
method retract(e: ShutdownEntity; turn: Turn; h: Handle) =
stopActor(turn)
proc connect(turn: Turn; ds: Cap; transAddr: Value; socket: AsyncSocket) =
proc socketWriter(turn: 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: Turn; relay: Relay):
let facet = turn.facet
facet.actor.atExit do (turn: 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: 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: Turn; ds: Cap; ta: Value; socket: AsyncSocket; fut: Future[void]) =
let facet = turn.facet
fut.addCallback do ():
run(facet) do (turn: 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: 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: Turn; ds: Cap; ta: transportAddress.Tcp) {.asyncio.} =
var conn = connectTcpAsync(ta.host, Port ta.port)
# connect(turn, ds, ta.toPreserves, conn)
#[
proc connectTransport(turn: 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: 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: 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)
type StepCallback = proc (turn: Turn; step: Value; origin, next: Cap) {.gcsafe.}
proc spawnStepResolver(turn: Turn; ds: Cap; stepType: Value; cb: StepCallback) =
spawn($stepType & "-step", turn) do (turn: Turn):
let stepPat = grabRecord(stepType, grab())
let pat = ?Observe(pattern: ResolvedPathStep?:{1: stepPat}) ?? {0: grabLit(), 1: grab()}
during(turn, ds, pat) do (origin: Cap; stepDetail: Literal[Value]):
let step = toRecord(stepType, stepDetail.value)
proc duringCallback(turn: Turn; ass: Value; h: Handle): TurnAction =
var res = ass.preservesTo Resolved
if res.isSome:
if res.get.orKind == ResolvedKind.accepted and
res.get.accepted.responderSession of Cap:
cb(turn, step, origin, res.get.accepted.responderSession.Cap)
else:
publish(turn, ds, ResolvedPathStep(
origin: origin, pathStep: step, resolved: res.get))
proc action(turn: Turn) =
stop(turn)
result = action
publish(turn, origin, Resolve(
step: step, observer: newCap(turn, during(duringCallback))))
proc spawnRelays*(turn: Turn; ds: Cap) =
## Spawn actors that manage routes and appeasing gatekeepers.
spawn("transport-connector", turn) do (turn: 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: 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)
spawnStepResolver(turn, ds, "ref".toSymbol) do (
turn: Turn, step: Value, origin: Cap, next: Cap):
publish(turn, ds, ResolvedPathStep(
origin: origin, pathStep: step, resolved: next.accepted))
type BootProc* = proc (turn: 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: 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

@ -3,35 +3,38 @@
## https://git.syndicate-lang.org/syndicate-lang/syndicate-rkt/src/commit/90c4c60699069b496491b81ee63b5a45ffd638cb/syndicate/HOWITWORKS.md
import
std/[assertions, hashes, options, sets, tables],
pkg/preserves,
./actors, ./bags, ./patterns,
./protocols/dataspacePatterns
import std/[hashes, options, sets, tables]
import preserves
import ./actors, ./bags, ./patterns
import ./protocols/dataspacePatterns
type
DCompound = dataspacePatterns.DCompound
Pattern = dataspacePatterns.Pattern
Path = seq[Value]
ClassKind = enum classNone, classRecord, classSequence, classDictionary
Class = object
kind: ClassKind
label: Value
arity: int
func classOf(v: Value): Class =
case v.kind
of pkRecord: Class(kind: classRecord, label: v.label)
of pkSequence: Class(kind: classSequence)
of pkRecord: Class(kind: classRecord, label: v.label, arity: v.arity)
of pkSequence: Class(kind: classSequence, arity: v.len)
of pkDictionary: Class(kind: classDictionary)
else: Class(kind: classNone)
proc classOf(p: Pattern): Class =
if p.orKind == PatternKind.group:
case p.group.type.orKind
of GroupTypeKind.rec:
Class(kind: classRecord, label: p.group.`type`.rec.label)
of GroupTypeKind.arr:
Class(kind: classSequence)
of GroupTypeKind.dict:
if p.orKind == PatternKind.DCompound:
case p.dcompound.orKind
of DCompoundKind.rec:
Class(kind: classRecord,
label: p.dcompound.rec.label,
arity: p.dcompound.rec.fields.len)
of DCompoundKind.arr:
Class(kind: classSequence, arity: p.dcompound.arr.items.len)
of DCompoundKind.dict:
Class(kind: classDictionary)
else:
Class(kind: classNone)
@ -42,8 +45,8 @@ type
AssertionCache = HashSet[Value]
ObserverGroup = ref object # Endpoints
cachedCaptures: Bag[Captures]
observers: Table[Cap, TableRef[Captures, Handle]]
cachedCaptures: Bag[Value]
observers: Table[Cap, TableRef[Value, Handle]]
Leaf = ref object
cache: AssertionCache
@ -64,17 +67,16 @@ func isEmpty(cont: Continuation): bool =
type
ContinuationProc = proc (c: Continuation; v: Value) {.closure.}
LeafProc = proc (l: Leaf; v: Value) {.closure.}
ObserverProc = proc (turn: Turn; group: ObserverGroup; vs: seq[Value]) {.closure.}
ObserverProc = proc (group: ObserverGroup; vs: Value) {.closure.}
proc getLeaves(cont: Continuation; presentPaths, constPaths: Paths): LeafMap =
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:
# TODO: check presence
let key = projectPaths(ass, constPaths)
var key = projectPaths(ass, constPaths)
if key.isSome:
var leaf = result.getOrDefault(get key)
if leaf.isNil:
@ -112,36 +114,36 @@ proc top(stack: TermStack): Value =
assert stack.len > 0
stack[stack.high]
proc modify(node: Node; turn: Turn; outerValue: Value; event: EventKind;
proc modify(node: Node; outerValue: Value; event: EventKind;
modCont: ContinuationProc; modLeaf: LeafProc; modObs: ObserverProc) =
proc walk(cont: Continuation; turn: Turn) =
proc walk(cont: Continuation) =
modCont(cont, outerValue)
for constPaths, constValMap in cont.leafMap.pairs:
let constVals = projectPaths(outerValue, constPaths)
var constVals = projectPaths(outerValue, constPaths)
if constVals.isSome:
case event
of addedEvent, messageEvent:
let leaf = constValMap.getLeaf(get constVals)
modLeaf(leaf, outerValue)
for capturePaths, observerGroup in leaf.observerGroups.pairs:
let captures = projectPaths(outerValue, capturePaths)
var captures = projectPaths(outerValue, capturePaths)
if captures.isSome:
modObs(turn, observerGroup, get captures)
modObs(observerGroup, captures.get.toPreserves)
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)
var captures = projectPaths(outerValue, capturePaths)
if captures.isSome:
modObs(turn, observerGroup, get captures)
modObs(observerGroup, captures.get.toPreserves)
if leaf.isEmpty:
constValMap.del(get constVals)
proc walk(node: Node; turn: Turn; termStack: TermStack) =
walk(node.continuation, turn)
proc walk(node: Node; termStack: TermStack) =
walk(node.continuation)
for selector, table in node.edges:
let
nextStack = pop(termStack, selector.popCount)
@ -151,11 +153,11 @@ proc modify(node: Node; turn: Turn; outerValue: Value; event: EventKind;
if nextClass.kind != classNone:
let nextNode = table.getOrDefault(nextClass)
if not nextNode.isNil:
walk(nextNode, turn, push(nextStack, get nextValue))
walk(nextNode, push(nextStack, get nextValue))
if event == removedEvent and nextNode.isEmpty:
table.del(nextClass)
walk(node, turn, @[@[outerValue].toPreserves])
walk(node, @[@[outerValue].toPreserves])
proc getOrNew[A, B, C](t: var Table[A, TableRef[B, C]], k: A): TableRef[B, C] =
result = t.getOrDefault(k)
@ -163,13 +165,25 @@ proc getOrNew[A, B, C](t: var Table[A, TableRef[B, C]], k: A): TableRef[B, C] =
result = newTable[B, C]()
t[k] = result
iterator pairs(dc: DCompound): (Value, Pattern) =
case dc.orKind
of DCompoundKind.rec:
for i, p in dc.rec.fields:
yield (i.toPreserves, p,)
of DCompoundKind.arr:
for i, p in dc.arr.items:
yield (i.toPreserves, p,)
of DCompoundKind.dict:
for pair in dc.dict.entries.pairs:
yield pair
proc extendWalk(node: Node; popCount: Natural; stepIndex: Value; pat: Pattern; path: var Path): tuple[popCount: Natural, nextNode: Node] =
case pat.orKind
of PatternKind.`discard`, PatternKind.lit:
of PatternKind.DDiscard, PatternKind.DLit:
result = (popCount, node)
of PatternKind.`bind`:
result = extendWalk(node, popCount, stepIndex, pat.`bind`.pattern, path)
of PatternKind.`group`:
of PatternKind.DBind:
result = extendWalk(node, popCount, stepIndex, pat.dbind.pattern, path)
of PatternKind.DCompound:
let
selector: Selector = (popCount, stepIndex,)
table = node.edges.getOrNew(selector)
@ -184,7 +198,7 @@ proc extendWalk(node: Node; popCount: Natural; stepIndex: Value; pat: Pattern; p
if v.isSome and class == classOf(get v):
result.nextNode.continuation.cache.incl a
result.popCount = 0
for step, p in pat.group.entries:
for step, p in pat.dcompound.pairs:
add(path, step)
result = extendWalk(result.nextNode, result.popCount, step, p, path)
discard pop(path)
@ -209,24 +223,24 @@ proc getEndpoints(leaf: Leaf; capturePaths: Paths): ObserverGroup =
leaf.observerGroups[capturePaths] = result
for term in leaf.cache:
# leaf.cache would be empty if observers come before assertions
let captures = projectPaths(term, capturePaths)
var captures = projectPaths(term, capturePaths)
if captures.isSome:
discard result.cachedCaptures.change(get captures, +1)
discard result.cachedCaptures.change(captures.get.toPreserves, +1)
proc add*(index: var Index; turn: Turn; pattern: Pattern; observer: Cap) =
proc add*(index: var Index; pattern: Pattern; observer: Cap) =
let
cont = index.root.extend(pattern)
analysis = analyse pattern
constValMap = cont.getLeaves(analysis.presentPaths, analysis.constPaths)
constValMap = cont.getLeaves(analysis.constPaths)
leaf = constValMap.getLeaf(analysis.constValues)
endpoints = leaf.getEndpoints(analysis.capturePaths)
# TODO if endpoints.cachedCaptures.len > 0:
var captureMap = newTable[seq[Value], Handle]()
var captureMap = newTable[Value, Handle]()
for capture in endpoints.cachedCaptures.items:
captureMap[capture] = publish(turn, observer, capture)
captureMap[capture] = publish(observer, capture.toPreserves)
endpoints.observers[observer] = captureMap
proc remove*(index: var Index; turn: Turn; pattern: Pattern; observer: Cap) =
proc remove*(index: var Index; pattern: Pattern; observer: Cap) =
let
cont = index.root.extend(pattern)
analysis = analyse pattern
@ -236,9 +250,9 @@ proc remove*(index: var Index; turn: Turn; pattern: Pattern; observer: Cap) =
if not leaf.isNil:
let endpoints = leaf.observerGroups.getOrDefault(analysis.capturePaths)
if not endpoints.isNil:
var captureMap: TableRef[seq[Value], Handle]
var captureMap: TableRef[Value, Handle]
if endpoints.observers.pop(observer, captureMap):
for handle in captureMap.values: retract(turn, handle)
for handle in captureMap.values: retract(observer, handle)
if endpoints.observers.len == 0:
leaf.observerGroups.del(analysis.capturePaths)
if leaf.observerGroups.len == 0:
@ -246,7 +260,7 @@ proc remove*(index: var Index; turn: Turn; pattern: Pattern; observer: Cap) =
if constValMap.len == 0:
cont.leafMap.del(analysis.constPaths)
proc adjustAssertion(index: var Index; turn: Turn; outerValue: Value; delta: int): bool =
proc adjustAssertion(index: var Index; outerValue: Value; delta: int): bool =
case index.allAssertions.change(outerValue, delta)
of cdAbsentToPresent:
result = true
@ -254,37 +268,37 @@ proc adjustAssertion(index: var Index; turn: Turn; outerValue: Value; delta: int
c.cache.incl(v)
proc modLeaf(l: Leaf; v: Value) =
l.cache.incl(v)
proc modObserver(turn: Turn; group: ObserverGroup; vs: seq[Value]) =
proc modObserver(group: ObserverGroup; vs: Value) =
let change = group.cachedCaptures.change(vs, +1)
if change == cdAbsentToPresent:
for (observer, captureMap) in group.observers.pairs:
captureMap[vs] = publish(turn, observer, vs.toPreserves)
captureMap[vs] = publish(observer, vs.toPreserves)
# TODO: this handle is coming from the facet?
modify(index.root, turn, outerValue, addedEvent, modContinuation, modLeaf, modObserver)
modify(index.root, outerValue, addedEvent, modContinuation, modLeaf, modObserver)
of cdPresentToAbsent:
result = true
proc modContinuation(c: Continuation; v: Value) =
c.cache.excl(v)
proc modLeaf(l: Leaf; v: Value) =
l.cache.excl(v)
proc modObserver(turn: Turn; group: ObserverGroup; vs: seq[Value]) =
proc modObserver(group: ObserverGroup; vs: Value) =
if group.cachedCaptures.change(vs, -1) == cdPresentToAbsent:
for (observer, captureMap) in group.observers.pairs:
var h: Handle
if captureMap.take(vs, h):
retract(observer.target, turn, h)
modify(index.root, turn, outerValue, removedEvent, modContinuation, modLeaf, modObserver)
retract(observer, h)
modify(index.root, outerValue, removedEvent, modContinuation, modLeaf, modObserver)
else: discard
proc continuationNoop(c: Continuation; v: Value) = discard
proc leafNoop(l: Leaf; v: Value) = discard
proc add*(index: var Index; turn: Turn; v: Value): bool =
adjustAssertion(index, turn, v, +1)
proc remove*(index: var Index; turn: Turn; v: Value): bool =
adjustAssertion(index, turn, v, -1)
proc add*(index: var Index; v: Value): bool =
adjustAssertion(index, v, +1)
proc remove*(index: var Index; v: Value): bool =
adjustAssertion(index, v, -1)
proc deliverMessage*(index: var Index; turn: Turn; v: Value) =
proc observersCb(turn: Turn; group: ObserverGroup; vs: seq[Value]) =
for observer in group.observers.keys: message(turn, observer, vs)
index.root.modify(turn, v, messageEvent, continuationNoop, leafNoop, observersCb)
proc deliverMessage*(index: var Index; v: Value) =
proc observersCb(group: ObserverGroup; vs: Value) =
for observer in group.observers.keys: message(observer, vs)
index.root.modify(v, messageEvent, continuationNoop, leafNoop, observersCb)

View File

@ -4,25 +4,27 @@
## This module implements the `Syndicate DSL <https://syndicate-lang.org/doc/syndicate/>`_.
import std/[macros, tables, typetraits]
import pkg/cps
# import pkg/sys/ioqueue
import pkg/preserves
export fromPreserves, toPreserves
import preserves
export preserves
import ./syndicate/[actors, dataspaces, durings, patterns]
import ./syndicate/protocols/dataspace
import ./[actors, dataspaces, durings, patterns]
import ./protocols/dataspace
export actors, dataspace, dataspaces, patterns
type Assertion* {.deprecated: "Assertion and Preserve[void] replaced by Value".} = Value
proc `!`*(typ: static typedesc): Pattern {.inline.} =
patterns.matchType(typ)
patterns.dropType(typ)
proc `?`*[T](val: T): Pattern {.inline.} =
patterns.drop[T](val)
patterns.grab[T](val)
proc `?:`*(typ: static typedesc): Pattern {.inline.} =
patterns.grabTypeFlat(typ)
patterns.grabType(typ)
proc `?:`*(typ: static typedesc; bindings: sink openArray[(int, Pattern)]): Pattern {.inline.} =
patterns.grab(typ, bindings)
@ -30,23 +32,30 @@ proc `?:`*(typ: static typedesc; bindings: sink openArray[(int, Pattern)]): Patt
proc `?:`*(typ: static typedesc; bindings: sink openArray[(Value, Pattern)]): Pattern {.inline.} =
patterns.grab(typ, bindings)
proc `??`*(pat: Pattern; bindings: openArray[(int, Pattern)]): Pattern {.inline.} =
patterns.inject(pat, bindings)
type
PublishProc = proc (turn: Turn; v: Value; h: Handle) {.closure.}
RetractProc = proc (turn: Turn; h: Handle) {.closure.}
MessageProc = proc (turn: Turn; v: Value) {.closure.}
PublishProc = proc (v: Value; h: Handle) {.closure.}
RetractProc = proc (h: Handle) {.closure.}
MessageProc = proc (v: Value) {.closure.}
ClosureEntity = ref object of Entity
publishImpl*: PublishProc
retractImpl*: RetractProc
messageImpl*: MessageProc
publishCb*: PublishProc
retractCb*: RetractProc
messageCb*: MessageProc
method publish(e: ClosureEntity; turn: Turn; a: AssertionRef; h: Handle) =
if not e.publishImpl.isNil: e.publishImpl(turn, a.value, h)
proc publishCont(e: Entity; v: Value; h: Handle) {.cps: Cont.} =
var ce = ClosureEntity(e)
if not ce.publishCb.isNil: ce.publishCb(v, h)
method retract(e: ClosureEntity; turn: Turn; h: Handle) =
if not e.retractImpl.isNil: e.retractImpl(turn, h)
proc retractCont(e: Entity; h: Handle) {.cps: Cont.} =
var ce = ClosureEntity(e)
if not ce.retractCb.isNil: ce.retractCb(h)
method message(e: ClosureEntity; turn: Turn; a: AssertionRef) =
if not e.messageImpl.isNil: e.messageImpl(turn, a.value)
proc messageCont(e: Entity; v: Value) {.cps: Cont.} =
var ce = ClosureEntity(e)
if not ce.messageCb.isNil: ce.messageCb(v)
proc argumentCount(handler: NimNode): int =
handler.expectKind {nnkDo, nnkStmtList}
@ -82,7 +91,7 @@ proc generateHandlerNodes(handler: NimNode): HandlerNodes =
result.varSection = newNimNode(nnkVarSection, handler).
add(newIdentDefs(result.valuesSym, valuesTuple))
proc wrapPublishHandler(turn, handler: NimNode): NimNode =
proc wrapPublishHandler(handler: NimNode): NimNode =
var
(valuesSym, varSection, publishBody) =
generateHandlerNodes(handler)
@ -90,24 +99,24 @@ proc wrapPublishHandler(turn, handler: NimNode): NimNode =
handlerSym = genSym(nskProc, "publish")
bindingsSym = ident"bindings"
quote do:
proc `handlerSym`(`turn`: Turn; `bindingsSym`: Value; `handleSym`: Handle) =
proc `handlerSym`(`bindingsSym`: Value; `handleSym`: Handle) =
`varSection`
if fromPreserves(`valuesSym`, bindings):
`publishBody`
proc wrapMessageHandler(turn, handler: NimNode): NimNode =
proc wrapMessageHandler(handler: NimNode): NimNode =
var
(valuesSym, varSection, body) =
generateHandlerNodes(handler)
handlerSym = genSym(nskProc, "message")
bindingsSym = ident"bindings"
quote do:
proc `handlerSym`(`turn`: Turn; `bindingsSym`: Value) =
proc `handlerSym`(`bindingsSym`: Value) =
`varSection`
if fromPreserves(`valuesSym`, bindings):
`body`
proc wrapDuringHandler(turn, entryBody, exitBody: NimNode): NimNode =
proc wrapDuringHandler(entryBody, exitBody: NimNode): NimNode =
var
(valuesSym, varSection, publishBody) =
generateHandlerNodes(entryBody)
@ -116,46 +125,54 @@ proc wrapDuringHandler(turn, entryBody, exitBody: NimNode): NimNode =
duringSym = genSym(nskProc, "during")
if exitBody.isNil:
quote do:
proc `duringSym`(`turn`: Turn; `bindingsSym`: Value; `handleSym`: Handle): TurnAction =
proc `duringSym`(f: Facet; `bindingsSym`: Value; `handleSym`: Handle): FacetProc =
`varSection`
if fromPreserves(`valuesSym`, `bindingsSym`):
`publishBody`
else:
quote do:
proc `duringSym`(`turn`: Turn; `bindingsSym`: Value; `handleSym`: Handle): TurnAction =
proc `duringSym`(f: Facet; `bindingsSym`: Value; `handleSym`: Handle): FacetProc =
`varSection`
if fromPreserves(`valuesSym`, `bindingsSym`):
`publishBody`
proc action(`turn`: Turn) =
result = proc (facet: Facet) =
`exitBody`
result = action
macro onPublish*(turn: untyped; ds: Cap; pattern: Pattern; handler: untyped) =
## Call `handler` when an assertion matching `pattern` is published at `ds`.
macro onPublish*(cap: Cap; pattern: Pattern; handler: untyped) =
## Call `handler` when an assertion matching `pattern` is published at `cap`.
let
argCount = argumentCount(handler)
handlerProc = wrapPublishHandler(turn, handler)
handlerProc = wrapPublishHandler(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`))
var ce = ClosureEntity(
publishCb: `handlerSym`,
).setActions(
publish = whelp publishCont,
)
discard observe(`cap`, `pattern`, ce)
macro onMessage*(turn: untyped; ds: Cap; pattern: Pattern; handler: untyped) =
## Call `handler` when an message matching `pattern` is broadcasted at `ds`.
macro onMessage*(cap: Cap; pattern: Pattern; handler: untyped) =
## Call `handler` when an message matching `pattern` is broadcasted at `cap`.
let
argCount = argumentCount(handler)
handlerProc = wrapMessageHandler(turn, handler)
handlerProc = wrapMessageHandler(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`))
discard observe(`cap`, `pattern`, ClosureEntity(
messageImpl: whelp messageCont,
messageCb: `handlerSym`,
name: "ClosureEntity",
))
macro during*(turn: untyped; ds: Cap; pattern: Pattern; publishBody, retractBody: untyped) =
## Call `publishBody` when an assertion matching `pattern` is published to `ds` and
macro during*(cap: Cap; pattern: Pattern; publishBody, retractBody: untyped) =
## Call `publishBody` when an assertion matching `pattern` is published to `cap` and
## call `retractBody` on retraction. Assertions that match `pattern` but are not
## convertable to the arguments of `publishBody` are silently discarded.
##
@ -164,34 +181,47 @@ macro during*(turn: untyped; ds: Cap; pattern: Pattern; publishBody, retractBody
## - `duringHandle` - dataspace handle of the assertion that triggered `publishBody`
let
argCount = argumentCount(publishBody)
callbackProc = wrapDuringHandler(turn, publishBody, retractBody)
callbackProc = wrapDuringHandler(publishBody, retractBody)
callbackSym = callbackProc[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`)
`callbackProc`
discard observe(`turn`, `ds`, `pattern`, during(`callbackSym`))
discard observe(`cap`, `pattern`, during(`callbackSym`))
macro during*(turn: untyped; ds: Cap; pattern: Pattern; publishBody: untyped) =
macro during*(cap: Cap; pattern: Pattern; publishBody: untyped) =
## Variant of `during` without a retract body.
let
`argCount` = argumentCount(publishBody)
callbackProc = wrapDuringHandler(turn, publishBody, nil)
callbackProc = wrapDuringHandler(publishBody, nil)
callbackSym = callbackProc[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`)
`callbackProc`
discard observe(`turn`, `ds`, `pattern`, during(`callbackSym`))
discard observe(`cap`, `pattern`, during(`callbackSym`))
when defined(solo5):
echo """
______
/ \_\
/ ,__/ \ ____ __
/\__/ \, \ _______ ______ ____/ /_/________ / /____
\/ \__/ / / ___/ / / / __ \/ __ / / ___/ __ \/ __/ _ \
\ ' \__/ _\_ \/ /_/ / / / / /_/ / / /__/ /_/ / /_/ __/
\____/_/ /____/\__, /_/ /_/\____/_/\___/\__/_/\__/\___/
/____/
"""
proc wrapHandler(body: NimNode; ident: string): NimNode =
var sym = genSym(nskProc, ident)
quote do:
proc `sym`() =
`body`
#[
macro onStop*(facet: Facet; body: untyped) =
let
handlerDef = wrapHandler(body, "onStop")
handlerSym = handlerDef[0]
result = quote do:
`handlerDef`
addOnStopHandler(facet, `handlerSym`)
macro onStop*(body: untyped) =
quote do:
block:
let facet = activeFacet()
facet.installStopHook()
if facet.stopped():
`body`
return
]#

19
src/sam/tracing.nim Normal file
View File

@ -0,0 +1,19 @@
# SPDX-FileCopyrightText: ☭ Emery Hemingway
# SPDX-License-Identifier: Unlicense
import ./protocols/[protocol, trace]
export trace
proc traceAction*(e: protocol.Event): trace.TurnEvent =
var act = ActionDescription(orKind: ActionDescriptionKind.enqueue)
act.enqueue.event = TargetedTurnEvent(
target: cap.traceTarget,
detail: trace.TurnEvent(orKind: trace.TurnEventKind.assert)
)
act.enqueue.event.detail = trace.TurnEvent(orKind: TurnEventKind.assert)
act.enqueue.event.detail.assert = TurnEventAssert(
assertion: AssertionDescription(orKind: AssertionDescriptionKind.value),
handle: result,
)
act.enqueue.event.detail.assert.assertion.value.value = val
turn.desc.actions.add act

View File

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

View File

@ -1,875 +0,0 @@
# SPDX-FileCopyrightText: ☭ Emery Hemingway
# SPDX-License-Identifier: Unlicense
import
std/[assertions, deques, hashes, monotimes, options, sets, tables, times],
pkg/cps,
pkg/preserves,
../syndicate/protocols/[protocol, sturdy, trace]
when defined(solo5):
import solo5_dispatcher
else:
import pkg/sys/ioqueue
const tracing = defined(traceSyndicate)
when tracing:
import std/streams
when not defined(solo5):
from std/os import getEnv
export Handle
export sturdy.Caveat
type
Oid = sturdy.Oid
Caveat = sturdy.Caveat
Attenuation = seq[Caveat]
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
facet*: Facet
oid*: Oid # oid is how Entities are identified over the wire
Cap* {.preservesEmbedded.} = ref object of EmbeddedObj
target*: Entity
relay*: Facet
# Entity has facet but a Cap is also scoped to a relay Facet
caveats*: seq[Caveat]
Ref* {.deprecated: "Ref was renamed to Cap".} = Cap
OutboundAssertion = ref object
handle: Handle
peer: Cap
established: bool
OutboundTable = Table[Handle, OutboundAssertion]
Actor* = ref object
next: Actor
name: string
handleAllocator: ref Handle
# a fresh actor gets a new ref Handle and
# all actors spawned from it get the same ref.
root: Facet
exitReason: ref Exception
exitHooks: seq[TurnAction]
id: ActorId
facetIdAllocator: uint
exiting, exited: bool
TurnAction* = proc (t: Turn) {.closure.}
Turn* = var TurnRef
TurnRef* = ref object
facet: Facet # active facet that may change during a turn
work: Deque[tuple[facet: Facet, act: TurnAction]]
effects: Table[Actor, TurnRef]
when tracing:
desc: TurnDescription
Facet* = ref FacetObj
FacetObj = object
actor*: Actor
parent: Facet
children: HashSet[Facet]
outbound: OutboundTable
shutdownActions: seq[TurnAction]
inertCheckPreventers: int
id: FacetId
isAlive: bool
var
rootActors {.threadvar.}: HashSet[Actor]
turnQueue {.threadvar.}: Deque[TurnRef]
when tracing:
when defined(solo5):
proc traceActivation(actor: Actor; act: ActorActivation) =
discard #[
echo TraceEntry(
timestamp: getTime().toUnixFloat(),
actor: initRecord("named", actor.name.toPreserves),
item: act,
).toPreserves
]#
else:
proc openTraceStream: FileStream =
let path = getEnv("SYNDICATE_TRACE_FILE")
case path
of "": stderr.writeLine "$SYNDICATE_TRACE_FILE unset"
of "-": result = newFileStream(stderr)
else: result = openFileStream(path, fmWrite)
let traceStream = openTraceStream()
proc traceActivation(actor: Actor; act: ActorActivation) =
if not traceStream.isNil:
var entry = TraceEntry(
timestamp: getTime().toUnixFloat(),
actor: initRecord("named", actor.name.toPreserves),
item: act)
traceStream.write(entry.toPreserves)
traceStream.flush()
var turnIdAllocator: uint
proc nextTurnId(): TurnId =
inc(turnIdAllocator)
turnIdAllocator.toPreserves
proc path(facet: Facet): seq[FacetId] =
var f = facet
while not f.isNil:
result.add f.id.toPreserves
f = f.parent
proc initEnqueue(turn: Turn; cap: Cap): ActionDescription =
result = ActionDescription(orKind: ActionDescriptionKind.enqueue)
result.enqueue.event.target.actor = turn.facet.actor.id.toPreserves
result.enqueue.event.target.facet = turn.facet.id.toPreserves
result.enqueue.event.target.oid = cap.target.oid.toPreserves
proc toDequeue(act: sink ActionDescription): ActionDescription =
result = ActionDescription(orKind: ActionDescriptionKind.dequeue)
result.dequeue.event = move act.enqueue.event
proc toTraceTarget(cap: Cap): Target =
assert not cap.target.isNil
assert not cap.target.facet.isNil
result.actor = cap.target.facet.actor.id
result.facet = cap.target.facet.id
result.oid = cap.target.oid.toPreserves
converter toActor(f: Facet): Actor = f.actor
converter toActor(t: Turn): Actor = t.facet.actor
converter toFacet(a: Actor): Facet = a.root
converter toFacet(t: Turn): Facet = t.facet
using
actor: Actor
facet: Facet
turn: Turn
action: TurnAction
proc labels(f: Facet): string =
assert not f.isNil
assert not f.actor.isNil
result.add f.actor.name
proc catLabels(f: Facet; labels: var string) =
if not f.parent.isNil:
catLabels(f.parent, labels)
labels.add ':'
labels.add $f.id
catLabels(f, result)
proc `$`*(f: Facet): string =
"<Facet:" & f.labels & ">"
proc `$`*(actor: Actor): string =
"<Actor:" & actor.name & ">" # TODO: ambigous
when tracing:
proc `$`*(r: Cap): string =
"<Ref:" & r.relay.labels & ">"
proc `$`*(t: Turn): string =
"<Turn:" & $t.desc.id & ">"
proc attenuate*(cap: Cap; caveats: openarray[Caveat]): Cap =
## Create a new `Cap` attenuated by a `caveats`.
## These caveats are applied before those already present at `cap`.
if caveats.len == 0: result = cap
else:
var att = newSeqOfCap[Caveat](caveats.len + cap.caveats.len)
att.add cap.caveats
att.add caveats
result = Cap(
target: cap.target,
relay: cap.relay,
caveats: att,
)
proc attenuate*(cap: Cap; cav: Caveat): Cap =
## Create a new `Cap` attenuated by a `Caveat`.
result = Cap(
target: cap.target,
relay: cap.relay,
caveats: cap.caveats,
)
result.caveats.add cav
proc hash*(actor): Hash =
result = actor[].unsafeAddr.hash
proc hash*(facet): Hash =
facet[].unsafeAddr.hash
proc hash*(r: Cap): Hash = !$(r.relay.hash !& r.target.unsafeAddr.hash)
proc actor*(turn): Actor = turn.facet.actor
proc nextHandle(facet: Facet): Handle =
result = succ(facet.actor.handleAllocator[])
facet.actor.handleAllocator[] = result
template recallFacet(turn: Turn; body: untyped): untyped =
let facet = turn.facet
block:
body
assert facet.actor == turn.facet.actor
turn.facet = facet
proc queueWork*(turn: Turn; facet: Facet; act: TurnAction) =
assert not facet.isNil
turn.work.addLast((facet, act,))
proc queueTurn*(facet: Facet; act: TurnAction) =
var turn = TurnRef(facet: facet)
assert not facet.isNil
turn.work.addLast((facet, act,))
when tracing:
turn.desc.id = nextTurnId()
turnQueue.addLast(turn)
proc queueTurn*(prev: Turn; facet: Facet; act: TurnAction) =
var next = TurnRef(facet: facet)
assert not facet.isNil
next.work.addLast((facet, act,))
when tracing:
next.desc.id = nextTurnId()
next.desc.cause = TurnCause(orKind: TurnCauseKind.turn)
next.desc.cause.turn.id = prev.desc.id
turnQueue.addLast(next)
proc run*(facet: Facet; action: TurnAction) = queueTurn(facet, action)
## Alias to queueTurn_.
proc run*(cap: Cap; action: TurnAction) = queueTurn(cap.relay, action)
## Run `action` with a new `Turn` at the `Facet` that relays for `cap`.
proc facet*(turn: Turn): Facet = turn.facet
proc queueEffect*(turn: Turn; target: Facet; act: TurnAction) =
let fremd = target.actor
if fremd == turn.facet.actor:
turn.work.addLast((target, act,))
else:
var fremdTurn = turn.effects.getOrDefault(fremd)
if fremdTurn.isNil:
fremdTurn = TurnRef(facet: target)
turn.effects[fremd] = fremdTurn
when tracing:
fremdTurn.desc.id = nextTurnId()
fremdTurn.desc.cause = TurnCause(orKind: TurnCauseKind.turn)
fremdTurn.desc.cause.turn.id = turn.desc.id
fremdTurn.work.addLast((target, act,))
type Bindings = Table[Value, Value]
proc match(bindings: var Bindings; p: Pattern; v: Value): bool =
case p.orKind
of PatternKind.Pdiscard: result = true
of PatternKind.Patom:
result = case p.patom
of PAtom.Boolean: v.isBoolean
of PAtom.Double: v.isFloat
of PAtom.Signedinteger: v.isInteger
of PAtom.String: v.isString
of PAtom.Bytestring: v.isByteString
of PAtom.Symbol: v.isSymbol
of PatternKind.Pembedded:
result = v.isEmbedded
of PatternKind.Pbind:
if match(bindings, p.pbind.pattern, v):
bindings[p.pbind.pattern.toPreserves] = v
result = true
of PatternKind.Pand:
for pp in p.pand.patterns:
result = match(bindings, pp, v)
if not result: break
of PatternKind.Pnot:
var b: Bindings
result = not match(b, p.pnot.pattern, v)
of PatternKind.Lit:
result = p.lit.value == v
of PatternKind.PCompound:
case p.pcompound.orKind
of PCompoundKind.rec:
if v.isRecord and
p.pcompound.rec.label == v.label and
p.pcompound.rec.fields.len == v.arity:
result = true
for i, pp in p.pcompound.rec.fields:
if not match(bindings, pp, v[i]):
result = false
break
of PCompoundKind.arr:
if v.isSequence and p.pcompound.arr.items.len == v.sequence.len:
result = true
for i, pp in p.pcompound.arr.items:
if not match(bindings, pp, v[i]):
result = false
break
of PCompoundKind.dict:
if v.isDictionary:
result = true
for key, pp in p.pcompound.dict.entries:
let vv = step(v, key)
if vv.isNone or not match(bindings, pp, get vv):
result = true
break
proc match(p: Pattern; v: Value): Option[Bindings] =
var b: Bindings
if match(b, p, v):
result = some b
proc instantiate(t: Template; bindings: Bindings): Value =
case t.orKind
of TemplateKind.Tattenuate:
let v = instantiate(t.tattenuate.template, bindings)
let cap = v.unembed(Cap)
if cap.isNone:
raise newException(ValueError, "Attempt to attenuate non-capability")
result = attenuate(get cap, t.tattenuate.attenuation).embed
of TemplateKind.TRef:
let n = $t.tref.binding.int
try: result = bindings[n.toPreserves]
except KeyError:
raise newException(ValueError, "unbound reference: " & n)
of TemplateKind.Lit:
result = t.lit.value
of TemplateKind.Tcompound:
case t.tcompound.orKind
of TCompoundKind.rec:
result = initRecord(t.tcompound.rec.label, t.tcompound.rec.fields.len)
for i, tt in t.tcompound.rec.fields:
result[i] = instantiate(tt, bindings)
of TCompoundKind.arr:
result = initSequence(t.tcompound.arr.items.len)
for i, tt in t.tcompound.arr.items:
result[i] = instantiate(tt, bindings)
of TCompoundKind.dict:
result = initDictionary(t.tcompound.dict.entries.len)
for key, tt in t.tcompound.dict.entries:
result[key] = instantiate(tt, bindings)
proc rewrite(r: Rewrite; v: Value): Value =
let bindings = match(r.pattern, v)
if bindings.isSome:
result = instantiate(r.template, get bindings)
proc attenuate*(v: Value; cav: Caveat): Value =
## Attenuate a `Value` by applying a `Caveat` function.
runnableExamples:
import pkg/preserves
proc parseCaveat(s: string): Caveat =
doAssert result.fromPreserves(parsePreserves s)
let val = parsePreserves"""<foo { x: [ #t "bar" ] }> """
doAssert val.attenuate(parseCaveat"""<reject <_>>""").isFalse
doAssert val.attenuate(parseCaveat"""<reject <not <_>>>""") == val
doAssert val.attenuate(parseCaveat"""<reject <not <rec foo [<_>]>>>""") == val
case cav.orKind
of CaveatKind.Rewrite:
result = rewrite(cav.rewrite, v)
of CaveatKind.Alts:
for r in cav.alts.alternatives:
result = rewrite(r, v)
if not result.isFalse: break
of CaveatKind.Reject:
if cav.reject.pattern.match(v).isNone:
result = v
of CaveatKind.unknown: discard
proc attenuate*(v: Value; caveats: openarray[Caveat]): Value =
## Attenuate a `Value` by applying `caveats`.
## Application is in reverse order.
result = v
var i = caveats.high
while i > -1 and not result.isFalse:
result = result.attenuate(caveats[i])
dec i
method publish*(e: Entity; turn: Turn; v: AssertionRef; h: Handle) {.base.} = discard
proc publish(turn: Turn; cap: Cap; v: Value; h: Handle) =
var a = v.attenuate(cap.caveats)
if not a.isFalse:
let e = OutboundAssertion(handle: h, peer: cap)
turn.facet.outbound[h] = e
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 = cap.target.oid.toPreserves
act.enqueue.event.detail = trace.TurnEvent(orKind: trace.TurnEventKind.assert)
act.enqueue.event.detail.assert.assertion.value.value =
mapEmbeds(v) do (cap: Value) -> Value:
result.embedded = true # #:#f
act.enqueue.event.detail.assert.handle = h
turn.desc.actions.add act
queueEffect(turn, cap.relay) do (turn: Turn):
e.established = true
when tracing:
turn.desc.actions.add act.toDequeue
publish(cap.target, turn, AssertionRef(value: a), e.handle)
proc publish*(turn: Turn; r: Cap; a: Value): Handle {.discardable.} =
result = turn.facet.nextHandle()
publish(turn, r, a, result)
proc publish*[T](turn: Turn; r: Cap; a: T): Handle {.discardable.} =
publish(turn, r, a.toPreserves)
method retract*(e: Entity; turn: Turn; h: Handle) {.base.} = discard
proc retract(turn: Turn; e: OutboundAssertion) =
when tracing:
var act = initEnqueue(turn, e.peer)
act.enqueue.event.detail = trace.TurnEvent(orKind: TurnEventKind.retract)
act.enqueue.event.detail.retract.handle = e.handle
turn.desc.actions.add act
queueEffect(turn, e.peer.relay) do (turn: Turn):
when tracing:
turn.desc.actions.add act.toDequeue
if e.established:
e.established = false
e.peer.target.retract(turn, e.handle)
proc retract*(turn: Turn; h: Handle) =
var e: OutboundAssertion
if turn.facet.outbound.pop(h, e):
turn.retract(e)
method message*(e: Entity; turn: Turn; v: AssertionRef) {.base.} = discard
proc message*(turn: Turn; r: Cap; v: Value) =
var a = v.attenuate(r.caveats)
if not a.isFalse:
when tracing:
var act = initEnqueue(turn, r)
act.enqueue.event.detail = trace.TurnEvent(orKind: TurnEventKind.message)
act.enqueue.event.detail.message.body.value.value =
mapEmbeds(a) do (cap: Value) -> Value:
result.embedded = true # #:#f
turn.desc.actions.add act
queueEffect(turn, r.relay) do (turn: Turn):
when tracing:
turn.desc.actions.add act.toDequeue
r.target.message(turn, AssertionRef(value: a))
proc message*[T](turn: Turn; r: Cap; v: T) =
message(turn, r, v.toPreserves)
method sync*(e: Entity; turn: Turn; peer: Cap) {.base.} =
queueTurn(e.facet) do (turn: Turn):
message(turn, peer, true.toPreserves)
# complete sync on a later turn
proc sync*(turn: Turn; r, peer: Cap) =
when tracing:
var act = initEnqueue(turn, peer)
act.enqueue.event.detail = trace.TurnEvent(orKind: TurnEventKind.sync)
act.enqueue.event.detail.sync.peer = peer.toTraceTarget
turn.desc.actions.add act
queueEffect(turn, r.relay) do (turn: Turn):
when tracing:
turn.desc.actions.add act.toDequeue
r.target.sync(turn, peer)
proc replace*[T](turn: Turn; cap: Cap; h: Handle; v: T): Handle =
result = publish(turn, cap, v)
if h != default(Handle):
retract(turn, h)
proc replace*[T](turn: Turn; cap: Cap; h: var Handle; v: T): Handle {.discardable.} =
var old = h
h = publish(turn, cap, v)
if old != default(Handle):
retract(turn, old)
h
proc stop*(turn: Turn)
proc newFacet(actor; parent: Facet; initialAssertions: OutboundTable): Facet =
inc actor.facetIdAllocator
result = Facet(
id: actor.facetIdAllocator.toPreserves,
actor: actor,
parent: parent,
outbound: initialAssertions,
isAlive: true)
if not parent.isNil: parent.children.incl result
proc newFacet(actor; parent: Facet): Facet =
var initialAssertions: OutboundTable
newFacet(actor, parent, initialAssertions)
proc isInert(facet): bool =
let
noKids = facet.children.len == 0
noOutboundHandles = facet.outbound.len == 0
isRootFacet = facet.parent.isNil
noInertCheckPreventers = facet.inertCheckPreventers == 0
result = noKids and (noOutboundHandles or isRootFacet) and noInertCheckPreventers
proc preventInertCheck*(turn: Turn) =
inc turn.facet.inertCheckPreventers
proc terminateActor(turn; reason: ref Exception)
proc terminateFacetOrderly(turn: Turn) =
let facet = turn.facet
if facet.isAlive:
facet.isAlive = false
var i = 0
while i < facet.shutdownActions.len:
facet.shutdownActions[i](turn)
inc i
setLen facet.shutdownActions, 0
for e in facet.outbound.values:
retract(turn, e)
clear facet.outbound
proc inertCheck(turn: Turn) =
if (not turn.facet.parent.isNil and
(not turn.facet.parent.isAlive)) or
turn.facet.isInert:
when tracing:
var act = ActionDescription(orKind: ActionDescriptionKind.facetStop)
act.facetstop.path = turn.facet.path
act.facetstop.reason = FacetStopReason.inert
turn.desc.actions.add act
stop(turn)
proc terminateFacet(turn: Turn) =
let facet = turn.facet
for child in facet.children:
queueWork(turn, child, terminateFacetOrderly)
# terminate all children
facet.children.clear()
# detach all children
queueWork(turn, facet, terminateFacetOrderly)
# self-termination
proc stopIfInertAfter(action: TurnAction): TurnAction =
proc work(turn: Turn) =
queueEffect(turn, turn.facet, inertCheck)
action(turn)
work
proc newFacet(turn: Turn): Facet = newFacet(turn.facet.actor, turn.facet)
proc inFacet*(turn: Turn; bootProc: TurnAction): Facet {.discardable.} =
result = newFacet(turn)
recallFacet turn:
turn.facet = result
when tracing:
var act = ActionDescription(orKind: ActionDescriptionKind.facetstart)
act.facetstart.path.add result.path
turn.desc.actions.add act
stopIfInertAfter(bootProc)(turn)
proc newActor(name: string; parent: Facet): Actor =
result = Actor(
name: name,
id: name.toPreserves,
)
if parent.isNil:
new result.handleAllocator
else:
result.handleAllocator = parent.actor.handleAllocator
result.root = newFacet(result, parent)
when tracing:
var act = ActorActivation(orKind: ActorActivationKind.start)
act.start.actorName = Name(orKind: NameKind.named)
act.start.actorName.named.name = name.toPreserves
traceActivation(result, act)
proc run(actor: Actor; bootProc: TurnAction; initialAssertions: OutboundTable) =
queueTurn(newFacet(actor, actor.root, initialAssertions), stopIfInertAfter(bootProc))
proc bootActor*(name: string; bootProc: TurnAction): Actor {.discardable.} =
## Boot a top-level actor.
result = newActor(name, nil)
new result.handleAllocator
var turn = TurnRef(facet: result.root)
assert not result.root.isNil
turn.work.addLast((result.root, bootProc,))
when tracing:
turn.desc.id = nextTurnId()
turn.desc.cause = TurnCause(orKind: TurnCauseKind.external)
turn.desc.cause.external.description = "bootActor".toPreserves
turnQueue.addLast turn
proc spawnActor*(turn: Turn; name: string; bootProc: TurnAction; initialAssertions = initHashSet[Handle]()): Actor {.discardable.} =
let actor = newActor(name, turn.facet)
queueEffect(turn, actor.root) do (turn: Turn):
var newOutBound: Table[Handle, OutboundAssertion]
for key in initialAssertions:
discard turn.facet.outbound.pop(key, newOutbound[key])
when tracing:
var act = ActionDescription(orKind: ActionDescriptionKind.spawn)
act.spawn.id = actor.id.toPreserves
turn.desc.actions.add act
run(actor, bootProc, newOutBound)
actor
proc spawn*(name: string; turn: Turn; bootProc: TurnAction; initialAssertions = initHashSet[Handle]()): Actor {.discardable.} =
spawnActor(turn, name, bootProc, initialAssertions)
type StopOnRetract = ref object of Entity
method retract*(e: StopOnRetract; turn: Turn; h: Handle) =
stop(turn)
proc halfLink(facet, other: Facet) =
let h = facet.nextHandle()
facet.outbound[h] = OutboundAssertion(
handle: h,
peer: Cap(relay: other, target: StopOnRetract(facet: facet)),
established: true,
)
proc linkActor*(turn: Turn; name: string; bootProc: TurnAction; initialAssertions = initHashSet[Handle]()): Actor {.discardable.} =
result = spawnActor(turn, name, bootProc, initialAssertions)
halfLink(turn.facet, result.root)
halfLink(result.root, turn.facet)
var inertActor {.threadvar.}: Actor
proc newInertCap*(): Cap =
if inertActor.isNil:
inertActor = bootActor("inert") do (turn: Turn): turn.stop()
Cap(relay: inertActor.root)
proc atExit*(actor; action) = actor.exitHooks.add action
proc terminateActor(turn; reason: ref Exception) =
let actor = turn.actor
if not actor.exiting:
actor.exiting = true
actor.exitReason = reason
when tracing:
var act = ActorActivation(orKind: ActorActivationKind.stop)
if not reason.isNil:
act.stop.status = ExitStatus(orKind: ExitStatusKind.Error)
act.stop.status.error.message = reason.msg
traceActivation(actor, act)
while actor.exitHooks.len > 0:
var hook = actor.exitHooks.pop()
try: hook(turn)
except CatchableError as err:
if reason.isNil:
terminateActor(turn, err)
return
proc finish(turn: Turn) =
assert not actor.root.isNil, actor.name
terminateFacet(turn)
actor.root = nil
actor.exited = true
queueTurn(actor.root, finish)
proc terminateFacet*(facet; e: ref Exception) =
run(facet.actor.root) do (turn: Turn):
terminateActor(turn, e)
proc terminate*(turn: Turn; e: ref Exception) =
terminateActor(turn, e)
proc stop*(turn: Turn, facet: Facet) =
queueEffect(turn, facet) do (turn: Turn):
when tracing:
var act = ActionDescription(orKind: ActionDescriptionKind.facetStop)
act.facetstop.path = facet.path
act.facetstop.reason = FacetStopReason.explicitAction
turn.desc.actions.add act
terminateFacet(turn)
proc stopFacet*(turn: Turn) = stop(turn, turn.facet)
## Stop the `Facet` currently active for this `Turn`.
proc stop*(turn: Turn) {.deprecated:"use stopFacet(turn)".} =
stop(turn, turn.facet)
proc stop*(facet: Facet) =
run(facet, stop)
proc onStop*(facet: Facet; act: TurnAction) =
## Add a `proc (turn: Turn)` action to `facet` to be called as it stops.
add(facet.shutdownActions, act)
proc onStop*(turn: Turn; act: TurnAction) =
onStop(turn.facet, act)
proc isAlive(actor): bool =
not(actor.exited or actor.exiting)
proc stop*(actor: Actor) =
if actor.isAlive:
queueTurn(actor.root) do (turn: Turn):
assert(not turn.facet.isNil)
when tracing:
var act = ActionDescription(orKind: ActionDescriptionKind.facetStop)
act.facetstop.path = turn.facet.path
act.facetstop.reason = FacetStopReason.actorStopping
turn.desc.actions.add act
stop(turn, turn.facet)
proc stopActor*(facet: Facet) =
stop(facet.actor)
proc stopActor*(turn: Turn) =
stop(turn, turn.facet.actor.root)
proc freshen*(turn: Turn, act: TurnAction) {.deprecated.} =
run(turn.facet, act)
proc newCap*(relay: Facet; entity: Entity): Cap =
## Create a new capability for `entity` via `relay`.
# An Entity has an owning facet and a Cap does as well?
if entity.facet.isNil: entity.facet = relay
Cap(relay: relay, target: entity)
proc newCap*(turn; e: Entity): Cap =
newCap(turn.facet, e)
proc newCap*(e: Entity; turn): Cap =
newCap(turn.facet, e)
type SyncContinuation {.final.} = ref object of Entity
action: TurnAction
method message(entity: SyncContinuation; turn: Turn; v: AssertionRef) =
entity.action(turn)
proc sync*(turn: 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
proc facet*(actor): Facet = actor.root
proc run(turn: Turn) =
while turn.work.len > 0:
var (facet, act) = turn.work.popFirst()
assert not act.isNil
turn.facet = facet
act(turn)
when tracing:
var act = ActorActivation(orKind: ActorActivationKind.turn)
act.turn = move turn.desc
traceActivation(turn.facet.actor, act)
# TODO: catch exceptions here
for eff in turn.effects.mvalues:
assert not eff.facet.isNil
turnQueue.addLast(move eff)
turn.facet = nil # invalidate the turn
proc runPendingTurns* =
while turnQueue.len > 0:
var turn = turnQueue.popFirst()
# TODO: check if actor is still valid
try: run(turn)
except CatchableError as err:
terminateActor(turn, err)
raise err
proc runOnce*(timeout = none(Duration)): bool {.discardable.} =
## Run pending turns if there are any, otherwise
## poll for external events and run any resulting turns.
## Return true if any turns have been processed.
if turnQueue.len == 0:
when defined(solo5):
discard solo5_dispatcher.runOnce(timeout)
else:
var ready: seq[Continuation]
ioqueue.poll(ready, timeout)
while ready.len > 0:
discard trampoline:
ready.pop()
result = turnQueue.len > 0
runPendingTurns()
proc run* =
## Run actors to completion.
when defined(solo5):
while turnQueue.len > 0 or solo5_dispatcher.runOnce():
runPendingTurns()
else:
var ready: seq[Continuation]
while true:
runPendingTurns()
try: ioqueue.poll(ready)
except OSError: discard
if ready.len == 0: break
while ready.len > 0:
discard trampoline:
ready.pop()
proc runActor*(name: string; bootProc: TurnAction) =
## Boot an actor `Actor` and churn ioqueue.
let actor = bootActor(name, bootProc)
if not actor.exitReason.isNil:
raise actor.exitReason
actor.atExit do (turn: Turn):
rootActors.excl actor
rootActors.incl actor
when defined(solo5):
runPendingTurns()
while (actor.isAlive and solo5_dispatcher.runOnce()) or turnQueue.len > 0:
runPendingTurns()
else:
actors.run()
if not actor.exitReason.isNil:
raise actor.exitReason
when defined(posix):
proc stopActorsHook() {.noconv.} =
stderr.writeLine " stopping actors "
if rootActors.len == 0: quit(-1) # Ctrl-C double-tap, scram
let act: TurnAction = stopActor
for actor in rootActors:
let turn = TurnRef(facet: actor.root)
turn.work.addLast((actor.root, act,))
when tracing:
turn.desc.id = nextTurnId()
turnQueue.addLast(turn)
rootActors.clear()
# dump all actors
setControlCHook(stopActorsHook)
type FacetGuard* = object
facet: Facet
proc initGuard*(f: Facet): FacetGuard =
result.facet = f
inc result.facet.inertCheckPreventers
proc disarm*(g: var FacetGuard) =
if not g.facet.isNil:
assert g.facet.inertCheckPreventers > 0
dec g.facet.inertCheckPreventers
g.facet = nil
proc `=destroy`*(g: FacetGuard) =
if not g.facet.isNil:
dec g.facet.inertCheckPreventers
proc `=copy`*(dst: var FacetGuard, src: FacetGuard) =
dst.facet = src.facet
inc dst.facet.inertCheckPreventers

View File

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

View File

@ -1,398 +0,0 @@
# SPDX-FileCopyrightText: ☭ Emery Hemingway
# SPDX-License-Identifier: Unlicense
import
std/[httpcore, options, parseutils, sets, streams, strutils, tables, times, uri],
pkg/taps,
pkg/preserves,
../../syndicate, ../bags, ./timers, ../protocols/http
const
CRLF = "\x0d\x0a"
SP = { ' ', '\x09', '\x0b', '\x0c', '\x0d' }
SupportedVersion = "HTTP/1.1"
IMF = initTimeFormat"ddd, dd MMM yyyy HH:mm:ss"
when defined(posix):
proc echo(args: varargs[string, `$`]) {.used.} =
stderr.writeLine(args)
type
Driver = ref object
facet: Facet
ds, timers: Cap
sequenceNumber: BiggestInt
Session = ref object
facet: Facet
driver: Driver
conn: Connection
exch: Exchange
pendingLen: int
port: Port
Exchange = ref object of Entity
cap: Cap
ses: Session
# TODO: store what we need fom Session locally.
req: HttpRequest
binding: Option[HttpBinding]
stream: StringStream
contentType: string
contentLen: int
mode: HttpResponseKind
active: bool
proc badRequest(conn: Connection; msg: string) =
conn.send(SupportedVersion & " " & msg, endOfMessage = true)
proc extractQuery(s: var string): Table[Symbol, seq[QueryValue]] =
let start = succ skipUntil(s, '?')
if start < s.len:
var query = s[start..s.high]
s.setLen(pred start)
for key, val in uri.decodeQuery(query):
var list = result.getOrDefault(Symbol key)
list.add QueryValue(orKind: QueryValueKind.string, string: val)
result[Symbol key] = list
proc parseRequest(conn: Connection; exch: Exchange; text: string): int =
## Parse an `HttpRequest` request out of a `text` from a `Connection`.
var
token: string
off: int
template advanceSp =
let n = skipWhile(text, SP, off)
if n < 1:
badRequest(conn, "400 invalid request")
return
inc(off, n)
# method
off.inc parseUntil(text, token, SP, off)
exch.req.method = token.toLowerAscii.Symbol
advanceSp()
# target
if text[off] == '/': inc(off) #TODO: always a leading slash?
off.inc parseUntil(text, token, SP, off)
advanceSp()
block:
var version: string
off.inc parseUntil(text, version, SP, off)
advanceSp()
if version != SupportedVersion:
badRequest(conn, "400 version not supported")
return
exch.req.query = extractQuery(token)
if token != "":
exch.req.path = split(token, '/')
for p in exch.req.path.mitems:
# normalize the path
for i, c in p:
if c in {'A'..'Z'}:
p[i] = char c.ord + 0x20
exch.req.host = RequestHost(orKind: RequestHostKind.absent)
template advanceLine =
inc off, skipWhile(text, {'\x0d'}, off)
if text.high < off or text[off] != '\x0a':
badRequest(conn, "400 invalid request")
return
inc off, 1
advanceLine()
while off < text.len:
off.inc parseUntil(text, token, {'\x0d', '\x0a'}, off)
if token == "": break
advanceLine()
var
(key, vals) = httpcore.parseHeader(token)
k = key.toLowerAscii
v = exch.req.headers.getOrDefault(cast[Symbol](k))
for e in vals.mitems:
var e = e.move.toLowerAscii
case k
of "host":
exch.req.host = RequestHost(orKind: RequestHostKind.`present`, present: v)
of "content-length":
discard parseInt(e, exch.contentLen)
if exch.contentLen > (1 shl 23):
badRequest(conn, "413 Content Too Large")
if exch.contentLen > 0:
exch.req.body = Value(
kind: pkByteString, bytes: newSeqOfCap[byte](exch.contentLen))
of "content-type":
exch.contentType = e
if v == "":
v = e.toLowerAscii
else:
v.add ", "
v.add e.toLowerAscii
exch.req.headers[cast[Symbol](k)] = v
advanceLine()
result = off
proc len(chunk: Chunk): int =
case chunk.orKind
of ChunkKind.string: chunk.string.len
of ChunkKind.bytes: chunk.bytes.len
proc lenLine(chunk: Chunk): string =
result = chunk.len.toHex.strip(true, false, {'0'})
result.add CRLF
proc send[T: byte|char](ses: Session; data: openarray[T]) =
ses.conn.send(addr data[0], data.len, endOfMessage = false)
proc send(ses: Session; chunk: Chunk) =
case chunk.orKind
of ChunkKind.string:
ses.send(chunk.string)
of ChunkKind.bytes:
ses.send(chunk.bytes)
func isTrue(v: Value): bool = v.kind == pkBoolean and v.bool
proc dispatch(exch: Exchange; turn: Turn; res: HttpResponse) =
case res.orKind
of HttpResponseKind.status:
if exch.mode == res.orKind:
exch.active = true
exch.ses.conn.startBatch()
exch.stream.write(
SupportedVersion, " ", res.status.code, " ", res.status.message, CRLF &
# "connection: close" & CRLF &
# RFC9112 says we SHOULD support persistent connections.
"date: ", now().format(IMF), CRLF
# add Date header automatically - RFC 9110 Section 6.6.1.
)
exch.mode = HttpResponseKind.header
of HttpResponseKind.header:
if exch.mode == res.orKind:
exch.stream.write(res.header.name, ": ", res.header.value, CRLF)
of HttpResponseKind.chunk:
if res.chunk.chunk.len > 0:
if exch.mode == HttpResponseKind.header:
exch.stream.write("transfer-encoding: chunked" & CRLF & CRLF)
exch.ses.send(move exch.stream.data)
exch.mode = res.orKind
if exch.mode == res.orKind:
exch.ses.send(res.chunk.chunk.lenLine)
exch.ses.send(res.chunk.chunk)
exch.ses.send(CRLF)
of HttpResponseKind.done:
if exch.mode in {HttpResponseKind.header, HttpResponseKind.chunk}:
if exch.mode == HttpResponseKind.header:
exch.stream.write("content-length: ", $res.done.chunk.len & CRLF & CRLF)
exch.ses.send(move exch.stream.data)
if res.done.chunk.len > 0:
exch.ses.send(res.done.chunk)
elif exch.mode == HttpResponseKind.chunk:
exch.ses.send(res.done.chunk.lenLine)
if res.done.chunk.len > 0:
exch.ses.send(res.done.chunk)
exch.ses.send(CRLF & "0" & CRLF & CRLF)
exch.mode = res.orKind
exch.ses.conn.endBatch()
if exch.req.headers.getOrDefault(Symbol"connection") == "close":
exch.ses.conn.close()
stop(turn)
# stop the facet scoped to the exchange
# so that the response capability is withdrawn
proc scheduleTimeout(exch: Exchange; turn: Turn) =
const timeout = initDuration(seconds = 4)
after(turn, exch.ses.driver.timers, timeout) do (turn: Turn):
if not exch.active:
var res = HttpResponse(orKind: HttpResponseKind.status)
res.status.code = 504
res.status.message = "Binding timeout"
exch.dispatch(turn, res)
res = HttpResponse(orKind: HttpResponseKind.done)
exch.dispatch(turn, res)
# Use a HttpResponse to reuse logic.
method message(exch: Exchange; turn: Turn; a: AssertionRef) =
# Send responses back into a connection.
if a.value.isTrue:
# Sync message from dataspace.
exch.scheduleTimeout(turn)
if exch.binding.isSome:
# The best binding in the dataspace has been selected.
let handler = exch.binding.get.handler.unembed(Cap)
if handler.isSome:
# Send the handler the exchange capability.
if exch.contentType.startsWith "application/json":
if exch.req.body.isByteString:
var bodyBytes = exch.req.body.bytes.move
exch.req.body = cast[string](bodyBytes).parsePreserves
discard publish(turn, handler.get, HttpContext(
req: exch.req,
res: exch.cap.embed,
))
else:
exch.binding.reset()
else:
var res: HttpResponse
if exch.mode != HttpResponseKind.done and res.fromPreserves a.value:
exch.dispatch(turn, res)
func `==`(s: string; rh: RequestHost): bool =
rh.orKind == RequestHostKind.present and rh.present == s
proc match(b: HttpBinding, r: HttpRequest): bool =
## Check if `HttpBinding` `b` matches `HttpRequest` `r`.
result =
(b.host.orKind == HostPatternKind.any or
b.host.host == r.host) and
(b.port == r.port) and
(b.method.orKind == MethodPatternKind.any or
b.method.specific == r.method)
if result:
for i, p in b.path:
if i > r.path.high: return false
case p.orKind
of PathPatternElementKind.wildcard: discard
of PathPatternElementKind.label:
if p.label != r.path[i]: return false
of PathPatternElementKind.rest:
return i == b.path.high
# return false if "..." isn't the last element
proc strongerThan(a, b: HttpBinding): bool =
## Check if `a` is a stronger `HttpBinding` than `b`.
result =
(a.host.orKind != b.host.orKind and
a.host.orKind == HostPatternKind.host) or
(a.method.orKind != b.method.orKind and
a.method.orKind == MethodPatternKind.specific)
if not result:
if a.path.len > b.path.len: return true
for i in b.path.low..a.path.high:
if a.path[i].orKind != b.path[i].orKind and
a.path[i].orKind == PathPatternElementKind.label:
return true
proc service(turn: Turn; exch: Exchange) =
## Service an HTTP message exchange.
let pat = grab(HttpBinding?:{
0: drop(), # match all hosts
1: ?exch.req.port,
2: drop(), # match all methods
3: drop(), # match all paths
4: drop(),
})
onPublish(turn, exch.ses.driver.ds, pat) do (b: HttpBinding):
if b.match exch.req:
if exch.binding.isNone or b.strongerThan exch.binding.get:
exch.binding = some b
# found a better binding
exch.cap = turn.newCap(exch)
sync(turn, exch.ses.driver.ds, exch.cap)
# When the sync returns we have seen all the bindings.
proc exchange(ses: Session) =
## Detach the current Exchange from the session
## and pass it into Syndicate.
ses.pendingLen = 0
var exch = move ses.exch
exch.ses = ses
ses.facet.run do (turn: Turn):
inFacet(turn) do (turn: Turn):
# start a new facet for this message exchange
preventInertCheck(turn)
exch.facet = turn.facet
exch.stream = newStringStream()
exch.mode = HttpResponseKind.status
turn.service(exch)
proc service(ses: Session) =
## Service a connection to an HTTP client.
const oneMiB = 1 shl 20
ses.facet.onStop do (turn: Turn):
close ses.conn
ses.conn.onClosed do ():
stop ses.facet
ses.conn.onReceivedPartial do (data: seq[byte]; ctx: MessageContext; eom: bool):
if ses.pendingLen == 0:
if ses.exch.isNil:
new ses.exch
let off = parseRequest(ses.conn, ses.exch, cast[string](data))
if off > 0:
assert not ses.exch.isNil
inc(ses.driver.sequenceNumber)
ses.exch.req.sequenceNumber = ses.driver.sequenceNumber
ses.exch.req.port = BiggestInt ses.port
ses.pendingLen = ses.exch.contentLen
if off < data.len:
let n = min(data.len - off, ses.pendingLen)
ses.exch.req.body.bytes.add data[off .. off+n.pred]
ses.pendingLen.dec n
else:
let n = min(data.len, ses.pendingLen)
ses.exch.req.body.bytes.add data[0..n.pred]
ses.pendingLen.dec n
assert ses.pendingLen >= 0, $ses.pendingLen
if ses.pendingLen == 0:
ses.exchange()
ses.conn.receive(maxLength = oneMiB)
else:
ses.conn.receive(maxLength=ses.pendingLen)
ses.conn.receive(maxLength =oneMiB)
proc newListener(port: Port): Listener =
var lp = newLocalEndpoint()
lp.with port
listen newPreconnection(local=[lp])
proc httpListen(turn: Turn; driver: Driver; port: Port): Listener =
let facet = turn.facet
var listener = newListener(port)
preventInertCheck(turn)
listener.onListenError do (err: ref Exception):
terminateFacet(facet, err)
facet.onStop do (turn: Turn):
stop listener
echo "listening for HTTP on port ", port
listener.onConnectionReceived do (conn: Connection):
driver.facet.run do (turn: Turn):
# start a new turn
linkActor(turn, "http-conn") do (turn: Turn):
preventInertCheck(turn)
let facet = turn.facet
conn.onConnectionError do (err: ref Exception):
terminateFacet(facet, err)
# terminate this actor on exception
# facet is scoped to the lifetime of the connection
service Session(
facet: turn.facet,
driver: driver,
conn: conn,
port: port,
)
listener
proc httpDriver(turn: Turn; ds: Cap) =
let driver = Driver(facet: turn.facet, ds: ds, timers: turn.newDataspace)
spawnTimerDriver(turn, driver.timers)
during(turn, driver.ds, HttpBinding?:{ 1: grab() }) do (port: BiggestInt):
publish(turn, ds, HttpListener(port: port))
# TODO: publish <http-service …>.
during(turn, driver.ds, ?:HttpListener) do (port: uint16):
let l = httpListen(turn, driver, Port port)
do:
stop(l)
proc spawnHttpDriver*(turn: Turn; ds: Cap): Actor {.discardable.} =
spawnActor(turn, "http-driver") do (turn: Turn):
httpDriver(turn, ds)

View File

@ -1,177 +0,0 @@
# SPDX-FileCopyrightText: ☭ Emery Hemingway
# SPDX-License-Identifier: Unlicense
import
std/[options, tables, times],
pkg/preserves,
../../syndicate, ../protocols/timer
when defined(solo5):
import solo5_dispatcher
else:
import pkg/sys/[handles, ioqueue]
export timer
when defined(solo5):
import solo5, solo5_dispatcher
proc wallFloat: float =
solo5_clock_wall().float / 1_000_000_000.0
type
TimerDriver = ref object
facet: Facet
## Owning facet of driver.
target: Cap
## Destination for LaterThan assertions.
deadlines: Table[float, Facet]
## Deadlines that other actors are observing.
proc spawnTimerDriver(facet: Facet; cap: Cap): TimerDriver =
TimerDriver(facet: facet, target: cap)
proc await(driver: TimerDriver; deadline: float) {.solo5dispatch.} =
yieldUntil(deadline)
let facet = driver.deadlines.getOrDefault(deadline)
if not facet.isNil:
# check if the deadline is still observed
proc turnWork(turn: Turn) =
discard publish(turn, driver.target, LaterThan(seconds: deadline))
run(facet, turnWork)
else:
import std/[oserrors, posix, sets]
type Time = posix.Time
{.pragma: timerfd, importc, header: "<sys/timerfd.h>".}
proc timerfd_create(clock_id: ClockId, flags: cint): cint {.timerfd.}
proc timerfd_settime(ufd: cint, flags: cint,
utmr: var Itimerspec, otmr: var Itimerspec): cint {.timerfd.}
var
TFD_NONBLOCK {.timerfd.}: cint
TFD_CLOEXEC {.timerfd.}: cint
TFD_TIMER_ABSTIME {.timerfd.}: cint
func toFloat(ts: Timespec): float =
ts.tv_sec.float + ts.tv_nsec.float / 1_000_000_000
func toTimespec(f: float): Timespec =
result.tv_sec = Time(f)
result.tv_nsec = clong(uint64(f * 1_000_000_000) mod 1_000_000_000)
proc wallFloat: float =
var ts: Timespec
if clock_gettime(CLOCK_REALTIME, ts) < 0:
raiseOSError(osLastError(), "clock_gettime")
ts.toFloat
type
TimerDriver = ref object
facet: Facet
## Owning facet of driver.
target: Cap
## Destination for LaterThan assertions.
deadlines: Table[float, Facet]
## Deadlines that other actors are observing.
timers: HashSet[cint]
# TODO: use a single timer descriptor
proc spawnTimerDriver(facet: Facet; cap: Cap): TimerDriver =
let driver = TimerDriver(facet: facet, target: cap)
facet.onStop do (turn: Turn):
for fd in driver.timers:
unregister(FD fd)
discard close(fd)
driver
proc await(driver: TimerDriver; deadline: float) {.asyncio.} =
## Run timer driver concurrently with actor.
let fd = timerfd_create(CLOCK_REALTIME, TFD_NONBLOCK or TFD_CLOEXEC)
if fd < 0:
raiseOSError(osLastError(), "failed to acquire timer descriptor")
var
old: Itimerspec
its = Itimerspec(it_value: deadline.toTimespec)
if timerfd_settime(fd, TFD_TIMER_ABSTIME, its, old) < 0:
raiseOSError(osLastError(), "failed to set timeout")
driver.timers.incl(fd)
while wallFloat() < deadline:
# Check if the timer is expired which
# could happen before waiting.
wait(FD fd, Read)
let facet = driver.deadlines.getOrDefault(deadline)
if not facet.isNil:
# Check if the deadline is still observed.
proc turnWork(turn: Turn) =
discard publish(turn, driver.target, LaterThan(seconds: deadline))
run(facet, turnWork)
discard close(fd)
driver.timers.excl(fd)
proc runTimer(driver: TimerDriver; peer: Cap; label: Value; start, deadline: float) {.asyncio.} =
## Run timer driver concurrently with actor.
assert start < deadline
let fd = timerfd_create(CLOCK_REALTIME, TFD_NONBLOCK or TFD_CLOEXEC)
if fd < 0:
raiseOSError(osLastError(), "failed to acquire timer descriptor")
var
old: Itimerspec
its = Itimerspec(it_value: deadline.toTimespec)
if timerfd_settime(fd, TFD_TIMER_ABSTIME, its, old) < 0:
raiseOSError(osLastError(), "failed to set timeout")
driver.timers.incl(fd)
var now = wallFloat()
while now < deadline:
# Check if the timer is expired which
# could happen before waiting.
wait(FD fd, Read)
now = wallFloat()
let facet = driver.deadlines.getOrDefault(deadline)
if not facet.isNil:
# Check if the deadline is still observed.
proc turnWork(turn: Turn) =
message(turn, peer, TimerExpired(label: label, seconds: now - start))
run(facet, turnWork)
discard close(fd)
driver.timers.excl(fd)
driver.deadlines.del deadline
proc spawnTimerDriver*(turn: Turn; ds: Cap): Actor {.discardable.} =
## Spawn a timer actor that responds to
## dataspace observations of timeouts on `ds`.
linkActor(turn, "timers") do (turn: Turn):
let driver = spawnTimerDriver(turn.facet, ds)
let laterThanPat = observePattern(!LaterThan, {@[0.toPreserves]: grabLit()})
during(turn, ds, laterThanPat) do (deadline: float):
driver.deadlines[deadline] = turn.facet
discard trampoline:
whelp await(driver, deadline)
do:
driver.deadlines.del deadline
during(turn, ds, SetTimer.grabType) do (req: SetTimer):
var
now = wallFloat()
deadline = req.seconds
let peer = req.peer.unembed Cap
if peer.isSome:
if req.kind == TimerKind.relative:
deadline = deadline + now
if deadline <= now:
message(turn, peer.get, TimerExpired(label: req.label))
else:
driver.deadlines[deadline] = turn.facet
discard trampoline:
whelp driver.runTimer(peer.get, req.label, now, deadline)
do:
driver.deadlines.del deadline
proc after*(turn: Turn; ds: Cap; dur: Duration; act: TurnAction) =
## Execute `act` after some duration of time.
var later = wallFloat() + dur.inMilliseconds.float / 1_000.0
onPublish(turn, ds, ?LaterThan(seconds: later)):
act(turn)

View File

@ -1,51 +0,0 @@
# SPDX-FileCopyrightText: ☭ Emery Hemingway
# SPDX-License-Identifier: Unlicense
import
std/[hashes, tables],
pkg/preserves,
./actors, ./patterns, ./protocols/dataspace
type
DuringProc* = proc (turn: Turn; a: Value; h: Handle): TurnAction
DuringActionKind = enum null, dead, act
DuringAction = object
case kind: DuringActionKind
of null, dead: discard
of act:
action: TurnAction
DuringEntity {.final.}= ref object of Entity
cb: DuringProc
assertionMap: Table[Handle, DuringAction]
method publish(de: DuringEntity; turn: Turn; a: AssertionRef; h: Handle) =
discard inFacet(turn) do (turn: Turn):
let action = de.cb(turn, a.value, h)
# assert(not action.isNil "should have put in a no-op action")
let g = de.assertionMap.getOrDefault h
case g.kind
of null:
de.assertionMap[h] = DuringAction(kind: act, action: action)
of dead:
de.assertionMap.del h
if not action.isNil:
action(turn)
of act:
raiseAssert("during: duplicate handle in publish: " & $h)
method retract(de: DuringEntity; turn: Turn; h: Handle) =
let g = de.assertionMap.getOrDefault h
case g.kind
of null:
de.assertionMap[h] = DuringAction(kind: dead)
of dead:
raiseAssert("during: duplicate handle in retract: " & $h)
of act:
de.assertionMap.del h
if not g.action.isNil:
g.action(turn)
proc during*(cb: DuringProc): DuringEntity = DuringEntity(cb: cb)
proc observe*(turn: Turn; ds: Cap; pat: Pattern; e: Entity): Handle =
publish(turn, ds, Observe(pattern: pat, observer: newCap(turn, e)))

View File

@ -1,505 +0,0 @@
# SPDX-FileCopyrightText: ☭ Emery Hemingway
# SPDX-License-Identifier: Unlicense
import
std/[assertions, options, sequtils, tables, typetraits],
pkg/preserves,
./protocols/[dataspacePatterns, dataspace]
from ./actors import Cap
export dataspacePatterns.`$`, AnyAtomKind, GroupTypeKind, PatternKind
type
Pattern* = dataspacePatterns.Pattern
proc toPattern(b: sink PatternBind): Pattern =
Pattern(orKind: PatternKind.`bind`, `bind`: b)
proc toPattern(l: sink PatternLit): Pattern =
Pattern(orKind: PatternKind.`lit`, lit: l)
proc toPattern(g: sink PatternGroup): Pattern =
Pattern(orKind: PatternKind.`group`, group: g)
proc toPattern(a: sink AnyAtom): Pattern =
PatternLit(value: a).toPattern
proc grab*(p: sink Pattern): Pattern =
PatternBind(pattern: p).toPattern
proc drop*(): Pattern = Pattern(orKind: PatternKind.`discard`)
## Create a pattern to match any value without capture.
proc grab*(): Pattern = drop().grab()
## Create a pattern to capture any value.
proc drop*(pr: Value): Pattern =
## Convert a `Preserve` value to a `Pattern`.
runnableExamples:
from std/unittest import check
import pkg/preserves
check:
$("""<foo "bar" #"00" [0 1 2.0] {maybe: #t} <_>>""".parsePreserves.drop) ==
"""<group <rec foo> {0: <lit "bar"> 1: <lit #"00"> 2: <group <arr> {0: <lit 0> 1: <lit 1> 2: <lit 2.0>}> 3: <group <dict> {maybe: <lit #t>}> 4: <_>}>"""
case pr.kind
of pkBoolean:
AnyAtom(orKind: AnyAtomKind.`bool`, bool: pr.bool).toPattern
of pkFloat:
AnyAtom(orKind: AnyAtomKind.`double`, double: pr.float).toPattern
of pkRegister:
AnyAtom(orKind: AnyAtomKind.`int`, int: pr.register).toPattern
of pkBigInt:
raiseAssert "cannot make a pattern over a big integer"
of pkString:
AnyAtom(orKind: AnyAtomKind.`string`, string: pr.string).toPattern
of pkByteString:
AnyAtom(orKind: AnyAtomKind.`bytes`, bytes: pr.bytes).toPattern
of pkSymbol:
AnyAtom(orKind: AnyAtomKind.`symbol`, symbol: pr.symbol).toPattern
of pkRecord:
if pr.isRecord("_", 0):
drop()
elif pr.isRecord("bind", 1):
pr.fields[0].drop
else:
var group = PatternGroup(`type`: GroupType(orKind: GroupTypeKind.rec))
group.`type`.rec.label = pr.label
var i: int
for v in pr.fields:
group.entries[toPreserves i] = drop v
inc i
group.toPattern
of pkSequence:
var group = PatternGroup(`type`: GroupType(orKind: GroupTypeKind.arr))
for i, v in pr.sequence:
group.entries[toPreserves i] = drop v
group.toPattern
of pkSet:
raiseAssert "cannot construct a pattern over a set literal"
of pkDictionary:
var group = PatternGroup(`type`: GroupType(orKind: GroupTypeKind.dict))
for key, val in pr.pairs:
group.entries[key] = drop val
group.toPattern
of pkEmbedded:
if pr.embeddedRef.isNil: drop()
else:
AnyAtom(orKind: AnyAtomKind.`embedded`, embedded: pr.embeddedRef).toPattern
#else:
# raise newException(ValueError, "cannot generate a pattern for unhandled Value type")
proc drop*[T](x: T): Pattern =
## Construct a `Pattern` from value of type `T`.
## This proc is called `drop` because the value `x` is matched but discarded.
runnableExamples:
from std/unittest import check
check:
$drop(true) == "<lit #t>"
$drop(3.14) == "<lit 3.14>"
$drop([0, 1, 2, 3]) == "<group <arr> {0: <lit 0> 1: <lit 1> 2: <lit 2> 3: <lit 3>}>"
drop(x.toPreserves)
proc grab*[T](x: T): Pattern {.
deprecated: "use drop unless you wish to capture the provided value".} =
PatternBind(pattern: drop x).toPattern
proc grabTypeFlat*(typ: static typedesc): Pattern =
## Derive a `Pattern` from type `typ`.
## This works for `tuple` and `object` types but in the
## general case will return a wildcard binding.
runnableExamples:
import pkg/preserves
from std/unittest import check
check:
$grabTypeFlat(array[3, int]) ==
"""<group <arr> {0: <bind <_>> 1: <bind <_>> 2: <bind <_>> 3: <bind <_>>}>"""
type
Point = tuple[x: int; y: int]
Rect {.preservesRecord: "rect".} = tuple[a: Point; B: Point]
ColoredRect {.preservesDictionary.} = tuple[color: string; rect: Rect]
check:
$(grabTypeFlat Point) ==
"<group <arr> {0: <bind <_>> 1: <bind <_>>}>"
$(grabTypeFlat Rect) ==
"<group <rec rect> {0: <group <arr> {0: <bind <_>> 1: <bind <_>>}> 1: <group <arr> {0: <bind <_>> 1: <bind <_>>}>}>"
$(grabTypeFlat ColoredRect) ==
"<group <dict> {color: <bind <_>> rect: <group <rec rect> {0: <group <arr> {0: <bind <_>> 1: <bind <_>>}> 1: <group <arr> {0: <bind <_>> 1: <bind <_>>}>}>}>"
when typ is ref:
grabTypeFlat(pointerBase(typ))
elif typ.hasPreservesRecordPragma:
var group = PatternGroup(`type`: GroupType(orKind: GroupTypeKind.`rec`))
group.`type`.rec.label = typ.recordLabel.toSymbol
for _, f in fieldPairs(default typ):
group.entries[group.entries.len.toPreserves] = grabTypeFlat(typeof f)
group.toPattern
elif typ.hasPreservesDictionaryPragma:
var group = PatternGroup(`type`: GroupType(orKind: GroupTypeKind.`dict`))
for key, val in fieldPairs(default typ):
group.entries[key.toSymbol] = grabTypeFlat(typeof val)
group.toPattern
elif typ is tuple:
var group = PatternGroup(`type`: GroupType(orKind: GroupTypeKind.`arr`))
for _, f in fieldPairs(default typ):
group.entries[group.entries.len.toPreserves] = grabTypeFlat(typeof f)
group.toPattern
elif typ is array:
var group = PatternGroup(`type`: GroupType(orKind: GroupTypeKind.`arr`))
for i in 0..len(typ):
group.entries[toPreserves i] = grab()
group.toPattern
else:
grab()
proc fieldCount(T: typedesc): int =
for _, _ in fieldPairs(default T):
inc result
proc matchType*(typ: static typedesc): Pattern =
## Derive a `Pattern` from type `typ` without any bindings.
when typ is ref:
matchType(pointerBase(typ))
elif typ.hasPreservesRecordPragma:
var group = PatternGroup(`type`: GroupType(orKind: GroupTypeKind.`rec`))
group.`type`.rec.label = typ.recordLabel.toSymbol
for _, f in fieldPairs(default typ):
group.entries[group.entries.len.toPreserves] = matchType(typeof f)
group.toPattern
elif typ.hasPreservesDictionaryPragma:
var group = PatternGroup(`type`: GroupType(orKind: GroupTypeKind.`dict`))
for key, val in fieldPairs(default typ):
group.entries[key.toSymbol] = matchType(typeof val)
group.toPattern
elif typ is tuple:
var group = PatternGroup(`type`: GroupType(orKind: GroupTypeKind.`arr`))
for _, f in fieldPairs(default typ):
group.entries[group.entries.len.toPreserves] = matchType(typeof f)
group.toPattern
elif typ is array:
var group = PatternGroup(`type`: GroupType(orKind: GroupTypeKind.`arr`))
let elemPat = typ.default.elementType
for i in 0..typ.high:
group.entries[i.toPreserves] = elemPat
group.toPattern
else:
drop()
proc grabType*(typ: static typedesc): Pattern =
PatternBind(pattern: typ.matchType).toPattern
proc grabWithin*(T: static typedesc): Pattern =
## Construct a `Pattern` that binds the fields within type `T`.
result = matchType(T)
for entry in result.group.entries.mvalues:
case entry.orKind
of `discard`: entry = grab()
of `bind`, `lit`: discard
of `group`: entry = grab(entry)
proc grabWithinType*(T: static typedesc): Pattern {.
deprecated: "use grabWithin".} = grabWithin(T)
proc bindEntries(group: var PatternGroup; bindings: openArray[(int, Pattern)]) =
## Set `bindings` for a `group`.
for (i, pat) in bindings: group.entries[toPreserves i] = pat
proc grab*(typ: static typedesc; bindings: sink openArray[(int, Pattern)]): Pattern =
## Construct a `Pattern` from type `typ` with pattern `bindings` by integer offset.
when typ is ptr | ref:
grab(pointerBase(typ), bindings)
elif typ.hasPreservesRecordPragma:
var group = PatternGroup(`type`: GroupType(orKind: GroupTypeKind.`rec`))
group.`type`.rec.label = typ.recordLabel.toSymbol
bindEntries(group, bindings)
group.toPattern
elif typ is tuple:
var group = PatternGroup(`type`: GroupType(orKind: GroupTypeKind.`arr`))
bindEntries(group, bindings)
group.toPattern
else:
{.error: "grab with indexed bindings not implemented for " & $typ.}
proc grab*(typ: static typedesc; bindings: sink openArray[(Value, Pattern)]): Pattern =
## Construct a `Pattern` from type `typ` with dictionary field `bindings`.
when typ.hasPreservesDictionaryPragma:
var group = PatternGroup(`type`: GroupType(orKind: GroupTypeKind.`dict`))
for key, val in bindinds: group.entries[key] = val
group.toPattern
else:
{.error: "grab with dictionary bindings not implemented for " & $typ.}
proc grabLit*(): Pattern =
runnableExamples:
from std/unittest import check
check:
$grabLit() == """<group <rec lit> {0: <bind <_>>}>"""
grabTypeFlat(dataspacePatterns.PatternLit)
proc grabDict*(): Pattern =
grabTypeFlat(dataspacePatterns.GroupTypeDict)
proc unpackLiterals*(pr: Value): Value =
result = pr
apply(result) do (pr: var Value):
if pr.isRecord("lit", 1) or pr.isRecord("dict", 1) or pr.isRecord("arr", 1) or pr.isRecord("set", 1):
pr = pr.record[0]
proc inject*(pattern: sink Pattern; p: Pattern; path: varargs[Value, toPreserves]): Pattern =
## Inject `p` inside `pattern` at `path`.
## Injects are made at offsets indexed by the discard (`<_>`) patterns in `pat`.
proc inject(pat: var Pattern; path: openarray[Value]) =
if len(path) == 0:
pat = p
elif pat.orKind != PatternKind.`group`:
raise newException(ValueError, "cannot inject along specified path")
else:
inject(pat.group.entries[path[0]], path[1..path.high])
result = pattern
inject(result, path)
proc matchRecord*(label: Value, fields: varargs[Pattern]): Pattern =
runnableExamples:
from std/unittest import check
import pkg/preserves
check:
$matchRecord("Says".toSymbol, grab(), grab()) ==
"""<group <rec Says> {0: <bind <_>> 1: <bind <_>>}>"""
var group = PatternGroup(`type`: GroupType(orKind: GroupTypeKind.`rec`))
group.`type`.rec.label = label
for i, f in fields: group.entries[toPreserves i] = f
group.toPattern
proc matchRecord*(label: string; fields: varargs[Pattern]): Pattern =
matchRecord(label.toSymbol, fields)
proc grabRecord*(label: Value, fields: varargs[Pattern]): Pattern =
runnableExamples:
from std/unittest import check
import pkg/preserves
check:
$grabRecord("Says".toSymbol, grab(), grab()) ==
"""<bind <group <rec Says> {0: <bind <_>> 1: <bind <_>>}>>"""
grab(matchRecord(label, fields))
proc grabRecord*(label: Value, fields: sink openArray[(int, Pattern)]): Pattern =
runnableExamples:
from std/unittest import check
import pkg/preserves
check:
$grabRecord("Says".toSymbol, {3: grab(), 4: grab()}) ==
"""<group <rec Says> {3: <bind <_>> 4: <bind <_>>}>"""
var group = PatternGroup(`type`: GroupType(orKind: GroupTypeKind.`rec`))
group.`type`.rec.label = label
for (i, p) in fields: group.entries[toPreserves i] = p
group.toPattern
proc grabRecord*(label: string, fields: varargs[Pattern]): Pattern =
## Sugar for creating record patterns.
## `label` is converted to a symbol value.
grabRecord(label.toSymbol, fields)
proc matchDictionary*(bindings: sink openArray[(Value, Pattern)]): Pattern =
## Construct a pattern that grabs some dictionary pairs.
var group = PatternGroup(`type`: GroupType(orKind: GroupTypeKind.`dict`))
for (key, val) in bindings: group.entries[key] = val
group.toPattern
proc matchDictionary*(bindings: sink openArray[(string, Pattern)]): Pattern =
## Construct a pattern that grabs some dictionary pairs.
## Keys are converted from strings to symbols.
var group = PatternGroup(`type`: GroupType(orKind: GroupTypeKind.`dict`))
for (key, val) in bindings: group.entries[toSymbol key] = val
group.toPattern
proc depattern(group: PatternGroup; values: var seq[Value]; index: var int): Value
proc depattern(pat: Pattern; values: var seq[Value]; index: var int): Value =
case pat.orKind
of PatternKind.`discard`:
discard
of PatternKind.`bind`:
if index < values.len:
result = move values[index]
inc index
of PatternKind.`lit`:
result = pat.`lit`.value.toPreserves
of PatternKind.`group`:
result = depattern(pat.group, values, index)
proc depattern(group: PatternGroup; values: var seq[Value]; index: var int): Value =
case group.`type`.orKind
of GroupTypeKind.rec:
result = initRecord(group.`type`.rec.label, group.entries.len)
var i: int
for key, val in group.entries:
if i.fromPreserves key:
result[i] = depattern(val, values, index)
of GroupTypeKind.arr:
result = initSequence(group.entries.len)
var i: int
for key, val in group.entries:
if i.fromPreserves key:
result[i] = depattern(val, values, index)
of GroupTypeKind.dict:
result = initDictionary(group.entries.len)
for key, val in group.entries:
result[key] = depattern(val, values, index)
proc depattern*(pat: Pattern; values: sink seq[Value]): Value =
## Convert a `Pattern` to a `Value` while replacing binds with `values`.
runnableExamples:
from std/unittest import check
import pkg/preserves
type Foo {.preservesRecord: "foo".} = object
a, b: int
let pat = grabTypeFlat Foo
let val = depattern(pat, @[1.toPreserves, 5.toPreserves])
check $val == "<foo 1 5>"
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
func isGroup(pat: Pattern): bool =
pat.orKind == PatternKind.`group`
func isMetaDict(pat: Pattern): bool =
pat.orKind == PatternKind.`group` and
pat.group.type.orKind == GroupTypeKind.dict
proc metaApply(result: var Pattern; pat: Pattern; path: openarray[Value], offset: int) =
if offset == path.len:
result = pat
elif result.isGroup and result.group.entries[1.toPreserves].isMetaDict:
if offset == path.high:
result.group.entries[1.toPreserves].group.entries[path[offset]] = pat
else:
metaApply(result.group.entries[1.toPreserves].group.entries[path[offset]], pat, path, succ offset)
else:
assert result.isGroup, "non-group: " & $result
assert result.group.entries[1.toPreserves].isMetaDict, "non-meta-dict: " & $result.group.entries[1.toPreserves]
raise newException(ValueError, "cannot inject into non-group pattern " & $result)
proc observePattern*(pat: Pattern): Pattern =
## Construct a pattern that matches an `Observe` assertion for `pat`.
runnableExamples:
import pkg/preserves
let
sample = parsePreserves"<sample>"
observation = sample.drop.observePattern
assert $observation ==
"<group <rec Observe> {0: <group <rec group> {0: <group <rec rec> {0: <lit sample>}> 1: <group <dict> {}>}> 1: <_>}>"
result = matchType Observe
result.group.entries[0.toPreserves] = pat.toPreserves.drop
proc observePattern*(pat: Pattern; injects: openarray[(seq[Value], Pattern)]): Pattern =
## Construct a pattern that matches an `Observe` assertion for `pat`
## and inject patterns along paths corresponding to the source pattern `pat`.
runnableExamples:
import pkg/preserves
let
v = parsePreserves"[ {} {a: #f b: #t } ]"
observation = v.drop.observePattern { @[1.toPreserves, "b".toSymbol]: grab() }
assert $observation ==
"<group <rec Observe> {0: <group <rec group> {0: <group <rec arr> {}> 1: <group <dict> {0: <group <rec group> {0: <group <rec dict> {}> 1: <group <dict> {}>}> 1: <group <rec group> {0: <group <rec dict> {}> 1: <group <dict> {a: <group <rec lit> {0: <lit #f>}> b: <bind <_>>}>}>}>}> 1: <_>}>"
result = matchType Observe
var meta = pat.toPreserves.drop
for (path, pat) in injects:
metaApply(meta, pat, path, 0)
result.group.entries[0.toPreserves] = meta
proc observePattern*(pat: Pattern; injects: openarray[(int, Pattern)]): Pattern =
## Sugar for observing patterns using record field offsets.
pat.observePattern injects.map do (pair: (int, Pattern)) -> (seq[Value], Pattern):
(@[pair[0].toPreserves], pair[1])
type
Path* = seq[Value]
Paths* = seq[Path]
Captures* = seq[Value]
Analysis* = tuple
presentPaths: Paths
constPaths: Paths
constValues: seq[Value]
capturePaths: Paths
func walk(result: var Analysis; path: var Path; p: Pattern)
func walk(result: var Analysis; path: var Path; key: Value; pat: Pattern) =
path.add(key)
walk(result, path, pat)
discard path.pop
func walk(result: var Analysis; path: var Path; p: Pattern) =
case p.orKind
of PatternKind.group:
for k, v in p.group.entries: walk(result, path, k, v)
of PatternKind.`bind`:
result.capturePaths.add(path)
walk(result, path, p.`bind`.pattern)
of PatternKind.`discard`:
result.presentPaths.add(path)
of PatternKind.`lit`:
result.constPaths.add(path)
result.constValues.add(p.`lit`.value.toPreserves)
func analyse*(p: Pattern): Analysis =
var path: Path
walk(result, path, p)
func checkPresence*(v: Value; present: Paths): bool =
result = true
for path in present:
if not result: break
result = step(v, path).isSome
func projectPaths*(v: Value; paths: Paths): Option[Captures] =
var res = newSeq[Value](paths.len)
for i, path in paths:
var vv = step(v, path)
if vv.isSome: res[i] = get(vv)
else: return
some res
proc matches*(pat: Pattern; pr: Value): bool =
let analysis = analyse(pat)
assert analysis.constPaths.len == analysis.constValues.len
result = checkPresence(pr, analysis.presentPaths)
if result:
for i, path in analysis.constPaths:
let v = step(pr, path)
if v.isNone: return false
if analysis.constValues[i] != v.get: return false
for path in analysis.capturePaths:
if step(pr, path).isNone: return false
proc capture*(pat: Pattern; pr: Value): seq[Value] =
let analysis = analyse(pat)
assert analysis.constPaths.len == analysis.constValues.len
if checkPresence(pr, analysis.presentPaths):
for i, path in analysis.constPaths:
let v = step(pr, path)
if v.isNone : return @[]
if analysis.constValues[i] != v.get: return @[]
for path in analysis.capturePaths:
let v = step(pr, path)
if v.isNone: return @[]
result.add(get v)
when isMainModule:
stdout.writeLine stdin.readAll.parsePreserves.grab

View File

@ -1,86 +0,0 @@
import
preserves, std/tables
type
AnyAtomKind* {.pure.} = enum
`bool`, `double`, `int`, `string`, `bytes`, `symbol`, `embedded`
`AnyAtom`* {.preservesOr.} = object
case orKind*: AnyAtomKind
of AnyAtomKind.`bool`:
`bool`*: bool
of AnyAtomKind.`double`:
`double`*: float
of AnyAtomKind.`int`:
`int`*: BiggestInt
of AnyAtomKind.`string`:
`string`*: string
of AnyAtomKind.`bytes`:
`bytes`*: seq[byte]
of AnyAtomKind.`symbol`:
`symbol`*: Symbol
of AnyAtomKind.`embedded`:
`embedded`* {.preservesEmbedded.}: EmbeddedRef
GroupTypeKind* {.pure.} = enum
`rec`, `arr`, `dict`
GroupTypeRec* {.preservesRecord: "rec".} = object
`label`*: Value
GroupTypeArr* {.preservesRecord: "arr".} = object
GroupTypeDict* {.preservesRecord: "dict".} = object
`GroupType`* {.preservesOr.} = object
case orKind*: GroupTypeKind
of GroupTypeKind.`rec`:
`rec`*: GroupTypeRec
of GroupTypeKind.`arr`:
`arr`*: GroupTypeArr
of GroupTypeKind.`dict`:
`dict`*: GroupTypeDict
PatternKind* {.pure.} = enum
`discard`, `bind`, `lit`, `group`
PatternDiscard* {.preservesRecord: "_".} = object
PatternBind* {.preservesRecord: "bind".} = object
`pattern`*: Pattern
PatternLit* {.preservesRecord: "lit".} = object
`value`*: AnyAtom
PatternGroup* {.preservesRecord: "group".} = object
`type`*: GroupType
`entries`*: Table[Value, Pattern]
`Pattern`* {.acyclic, preservesOr.} = ref object
case orKind*: PatternKind
of PatternKind.`discard`:
`discard`*: PatternDiscard
of PatternKind.`bind`:
`bind`* {.preservesEmbedded.}: PatternBind
of PatternKind.`lit`:
`lit`* {.preservesEmbedded.}: PatternLit
of PatternKind.`group`:
`group`* {.preservesEmbedded.}: PatternGroup
proc `$`*(x: AnyAtom | GroupType | Pattern): string =
`$`(toPreserves(x))
proc encode*(x: AnyAtom | GroupType | Pattern): seq[byte] =
encode(toPreserves(x))

View File

@ -1,34 +0,0 @@
import
preserves
type
Question* {.preservesRecord: "q".} = object
`request`*: Value
Answer* {.preservesRecord: "a".} = object
`request`*: Value
`response`*: Value
ResultKind* {.pure.} = enum
`ok`, `error`
ResultOk* {.preservesRecord: "ok".} = object
`value`*: Value
ResultError* {.preservesRecord: "error".} = object
`error`*: Value
`Result`* {.preservesOr.} = object
case orKind*: ResultKind
of ResultKind.`ok`:
`ok`*: ResultOk
of ResultKind.`error`:
`error`*: ResultError
proc `$`*(x: Question | Answer | Result): string =
`$`(toPreserves(x))
proc encode*(x: Question | Answer | Result): seq[byte] =
encode(toPreserves(x))

View File

@ -1,677 +0,0 @@
# SPDX-FileCopyrightText: ☭ Emery Hemingway
# SPDX-License-Identifier: Unlicense
import
std/[options, sets, tables],
pkg/preserves,
../syndicate, ./durings, ./membranes, ./protocols/[gatekeeper, protocol, sturdy, transportAddress]
when defined(posix):
import ./capabilities
from std/os import getEnv, `/`
when defined(traceSyndicate):
when defined(posix):
template trace(args: varargs[untyped]): untyped = stderr.writeLine(args)
else:
template trace(args: varargs[untyped]): untyped = echo(args)
else:
template trace(args: varargs[untyped]): untyped = discard
export `$`
export Route, Stdio, Tcp, WebSocket, Unix
type
Assertion = Value
Event = protocol.Event
Handle = actors.Handle
Oid = sturdy.Oid
Turn = syndicate.Turn
WireRef = sturdy.WireRef
PacketWriter = proc (turn: Turn; buf: seq[byte]) {.closure.}
RelaySetup = proc (turn: Turn; relay: Relay) {.closure.}
Relay* = ref object
facet: Facet
inboundAssertions: Table[Handle,
tuple[localHandle: Handle, imported: seq[WireSymbol]]]
outboundAssertions: Table[Handle, seq[WireSymbol]]
pendingTurn: protocol.Turn
exported: Membrane
imported: Membrane
nextLocalOid: Oid
wireBuf: BufferedDecoder
packetWriter: PacketWriter
peer: Cap
SyncPeerEntity = ref object of Entity
relay: Relay
peer: Cap
handleMap: Table[Handle, Handle]
e: WireSymbol
RelayEntity = ref object of Entity
## https://synit.org/book/protocol.html#relay-entities
label: string
relay: Relay
proc releaseCapOut(r: Relay; e: WireSymbol) =
r.exported.drop e
method publish(spe: SyncPeerEntity; t: Turn; a: AssertionRef; h: Handle) =
spe.handleMap[h] = publish(t, spe.peer, a.value)
method retract(se: SyncPeerEntity; t: Turn; h: Handle) =
var other: Handle
if se.handleMap.pop(h, other):
retract(t, other)
method message(se: SyncPeerEntity; t: Turn; a: AssertionRef) =
if not se.e.isNil:
se.relay.releaseCapOut(se.e)
message(t, se.peer, a.value)
method sync(se: SyncPeerEntity; t: Turn; peer: Cap) =
sync(t, se.peer, peer)
proc newSyncPeerEntity(r: Relay; p: Cap): SyncPeerEntity =
SyncPeerEntity(relay: r, peer: p)
proc rewriteCapOut(relay: Relay; cap: Cap; exported: var seq[WireSymbol]): WireRef =
if cap.target of RelayEntity and cap.target.RelayEntity.relay == relay and cap.caveats.len == 0:
result = WireRef(orKind: WireRefKind.yours, yours: WireRefYours(oid: cap.target.oid))
else:
var ws = grab(relay.exported, cap)
if ws.isNil:
ws = newWireSymbol(relay.exported, relay.nextLocalOid, cap)
inc relay.nextLocalOid
exported.add ws
result = WireRef(
orKind: WireRefKind.mine,
mine: WireRefMine(oid: ws.oid))
proc rewriteOut(relay: Relay; v: Assertion):
tuple[rewritten: Value, exported: seq[WireSymbol]] =
var exported: seq[WireSymbol]
result.rewritten = mapEmbeds(v) do (pr: Value) -> Value:
let o = pr.unembed(Cap); if o.isSome:
result = rewriteCapOut(relay, o.get, exported).toPreserves
result.embedded = true
else: result = pr
result.exported = exported
proc register(relay: Relay; v: Assertion; h: Handle): tuple[rewritten: Value, exported: seq[WireSymbol]] =
result = rewriteOut(relay, v)
relay.outboundAssertions[h] = result.exported
proc deregister(relay: Relay; h: Handle) =
var outbound: seq[WireSymbol]
if relay.outboundAssertions.pop(h, outbound):
for e in outbound: releaseCapOut(relay, e)
proc send(relay: Relay; turn: Turn; rOid: protocol.Oid; m: Event) =
# TODO: don't send right away.
relay.pendingTurn.add TurnEvent(oid: rOid, event: m)
queueEffect(turn, relay.facet) do (turn: Turn):
if relay.pendingTurn.len > 0:
var pkt = Packet(
orKind: PacketKind.Turn,
turn: move relay.pendingTurn)
trace "C: ", pkt
relay.packetWriter(turn, encode pkt)
proc send(re: RelayEntity; turn: Turn; ev: Event) =
send(re.relay, turn, protocol.Oid re.oid, ev)
method publish(re: RelayEntity; t: Turn; a: AssertionRef; h: Handle) =
re.send(t, Event(
orKind: EventKind.Assert,
`assert`: protocol.Assert(
assertion: re.relay.register(a.value, h).rewritten,
handle: h)))
method retract(re: RelayEntity; t: Turn; h: Handle) =
re.relay.deregister h
re.send(t, Event(
orKind: EventKind.Retract,
retract: Retract(handle: h)))
method message(re: RelayEntity; turn: Turn; msg: AssertionRef) =
var (value, exported) = rewriteOut(re.relay, msg.value)
assert(len(exported) == 0, "cannot send a reference in a message")
if len(exported) == 0:
re.send(turn, Event(orKind: EventKind.Message, message: Message(body: value)))
method sync(re: RelayEntity; turn: Turn; peer: Cap) =
var
peerEntity = newSyncPeerEntity(re.relay, peer)
exported: seq[WireSymbol]
wr = rewriteCapOut(re.relay, turn.newCap(peerEntity), exported)
peerEntity.e = exported[0]
var ev = Event(orKind: EventKind.Sync)
ev.sync.peer = wr.toPreserves.embed
re.send(turn, ev)
proc newRelayEntity(label: string; r: Relay; o: Oid): RelayEntity =
RelayEntity(label: label, relay: r, oid: o)
using
relay: Relay
facet: Facet
proc lookupLocal(relay; oid: Oid): Cap =
let sym = relay.exported.grab oid
if not sym.isNil:
result = sym.cap
proc rewriteCapIn(relay; facet; n: WireRef, imported: var seq[WireSymbol]): Cap =
case n.orKind
of WireRefKind.mine:
var e = relay.imported.grab(n.mine.oid)
if e.isNil:
e = newWireSymbol(
relay.imported,
n.mine.oid,
newCap(facet, newRelayEntity("rewriteCapIn", relay, n.mine.oid)),
)
imported.add e
result = e.cap
of WireRefKind.yours:
result = relay.lookupLocal(n.yours.oid)
if result.isNil:
result = newInertCap()
elif n.yours.attenuation.len > 0:
result = attenuate(result, n.yours.attenuation)
proc rewriteIn(relay; facet; v: Value):
tuple[rewritten: Assertion; imported: seq[WireSymbol]] =
var imported: seq[WireSymbol]
result.rewritten = mapEmbeds(v) do (pr: Value) -> Value:
let wr = pr.preservesTo WireRef; if wr.isSome:
result = rewriteCapIn(relay, facet, wr.get, imported).embed
else:
result = pr
result.imported = imported
proc close(r: Relay) = discard
proc dispatch(relay: Relay; turn: Turn; cap: Cap; event: Event) =
case event.orKind
of EventKind.Assert:
let (a, imported) = rewriteIn(relay, turn.facet, event.assert.assertion)
relay.inboundAssertions[event.assert.handle] = (publish(turn, cap, a), imported,)
of EventKind.Retract:
let remoteHandle = event.retract.handle
var outbound: tuple[localHandle: Handle, imported: seq[WireSymbol]]
if relay.inboundAssertions.pop(remoteHandle, outbound):
for e in outbound.imported: relay.imported.drop e
turn.retract(outbound.localHandle)
of EventKind.Message:
let (a, imported) = rewriteIn(relay, turn.facet, event.message.body)
assert imported.len == 0, "Cannot receive transient reference"
turn.message(cap, a)
of EventKind.Sync:
turn.sync(cap) do (turn: Turn):
var
(v, imported) = rewriteIn(relay, turn.facet, event.sync.peer)
peer = unembed(v, Cap)
if peer.isSome:
turn.message(get peer, true)
for e in imported: relay.imported.drop e
proc dispatch(relay: Relay; v: Value) =
trace "S: ", v
run(relay.facet) do (t: Turn):
var pkt: Packet
if pkt.fromPreserves(v):
case pkt.orKind
of PacketKind.Turn:
# https://synit.org/book/protocol.html#turn-packets
for te in pkt.turn:
let r = lookupLocal(relay, te.oid.Oid)
if not r.isNil:
dispatch(relay, t, r, te.event)
of PacketKind.Error:
# https://synit.org/book/protocol.html#error-packets
when defined(posix):
stderr.writeLine("Error from server: ", pkt.error.message, " (detail: ", pkt.error.detail, ")")
close relay
of PacketKind.Extension:
# https://synit.org/book/protocol.html#extension-packets
discard
of PacketKind.Nop:
discard
else:
when defined(posix):
stderr.writeLine("discarding undecoded packet ", v)
proc recv(relay: Relay; buf: openarray[byte]; slice: Slice[int]) =
feed(relay.wireBuf, buf, slice)
var pr = decode(relay.wireBuf)
if pr.isSome: dispatch(relay, pr.get)
proc recv(relay: Relay; buf: openarray[byte]) {.used.} =
feed(relay.wireBuf, buf)
var pr = decode(relay.wireBuf)
if pr.isSome: dispatch(relay, pr.get)
type
RelayOptions* = object of RootObj
packetWriter*: PacketWriter
RelayActorOptions* = object of RelayOptions
initialOid*: Option[Oid]
initialCap*: Cap
nextLocalOid*: Option[Oid]
proc spawnRelay(name: string; turn: Turn; opts: RelayActorOptions; setup: RelaySetup) =
linkActor(turn, name) do (turn: Turn):
turn.preventInertCheck()
let relay = Relay(
facet: turn.facet,
packetWriter: opts.packetWriter,
wireBuf: newBufferedDecoder(0),
)
if not opts.initialCap.isNil:
var exported: seq[WireSymbol]
discard rewriteCapOut(relay, opts.initialCap, exported)
opts.nextLocalOid.map do (oid: Oid):
relay.nextLocalOid =
if oid == 0.Oid: 1.Oid
else: oid
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
type ShutdownEntity = ref object of Entity
method retract(e: ShutdownEntity; turn: Turn; h: Handle) =
stopActor(e.facet)
when defined(posix) and not defined(nimdoc):
import std/[oserrors, posix]
import pkg/sys/[files, handles, ioqueue, sockets]
export transportAddress.Unix
type StdioEntity = ref object of Entity
relay: Relay
stdin: AsyncFile
alive: bool
method message(entity: StdioEntity; turn: Turn; ass: AssertionRef) =
if ass.value.preservesTo(ForceDisconnect).isSome:
entity.alive = false
proc loop(entity: StdioEntity) {.asyncio.} =
let buf = new seq[byte]
entity.alive = true
while entity.alive:
buf[].setLen(0x1000)
let n = read(entity.stdin, buf)
if n > 0:
entity.relay.recv(buf[], 0..<n)
else:
entity.alive = false
if n < 0: raiseOSError(osLastError())
stopActor(entity.facet)
proc connectTransport(turn: Turn; ds: Cap; ta: transportAddress.Stdio) =
## Connect to an external dataspace over stdio.
let localDataspace = newDataspace(turn)
proc stdoutWriter(turn: Turn; buf: seq[byte]) =
## Blocking write to stdout.
let n = writeBytes(stdout, buf, 0, buf.len)
flushFile(stdout)
if n != buf.len:
stopActor(turn)
var opts = RelayActorOptions(
packetWriter: stdoutWriter,
initialCap: localDataspace,
initialOid: 0.Oid.some,
)
spawnRelay("stdio", turn, opts) do (turn: Turn; relay: Relay):
let
facet = turn.facet
fd = stdin.getOsFileHandle()
flags = fcntl(fd.cint, F_GETFL, 0)
if flags < 0: raiseOSError(osLastError())
if fcntl(fd.cint, F_SETFL, flags or O_NONBLOCK) < 0:
raiseOSError(osLastError())
let entity = StdioEntity(
facet: turn.facet, relay: relay, stdin: newAsyncFile(FD fd))
onStop(entity.facet) do (turn: Turn):
entity.alive = false
close(entity.stdin)
# Close stdin to remove it from the ioqueue
discard trampoline:
whelp loop(entity)
publish(turn, ds, TransportConnection(
`addr`: ta.toPreserves,
control: newCap(entity, turn),
resolved: localDataspace.accepted,
))
proc connectStdio*(turn: Turn; ds: Cap) =
## Connect to an external dataspace over stdin and stdout.
connectTransport(turn, ds, transportAddress.Stdio())
type
TcpEntity = ref object of Entity
relay: Relay
sock: AsyncConn[sockets.Protocol.TCP]
alive: bool
UnixEntity = ref object of Entity
relay: Relay
sock: AsyncConn[sockets.Protocol.Unix]
alive: bool
SocketEntity = TcpEntity | UnixEntity
method message(entity: SocketEntity; turn: Turn; ass: AssertionRef) =
if ass.value.preservesTo(ForceDisconnect).isSome:
entity.alive = false
template bootSocketEntity() {.dirty.} =
proc setup(turn: Turn) {.closure.} =
proc kill(turn: Turn) =
if entity.alive:
entity.alive = false
close(entity.sock)
onStop(turn, kill)
var ass = TransportConnection(
`addr`: ta.toPreserves,
control: newCap(entity, turn),
resolved: entity.relay.peer.accepted,
)
publish(turn, ds, ass)
run(entity.relay.facet, setup)
let buf = new seq[byte]
entity.alive = true
while entity.alive:
buf[].setLen(0x1000)
let n = read(entity.sock, buf)
if n > 0:
entity.relay.recv(buf[], 0..<n)
else:
entity.alive = false
if n < 0: raiseOSError(osLastError())
stopActor(entity.facet)
# the socket closes when the actor is stopped
proc boot(entity: TcpEntity; ta: transportAddress.Tcp; ds: Cap) {.asyncio.} =
entity.sock = connectTcpAsync(ta.host, Port ta.port)
bootSocketEntity()
proc boot(entity: UnixEntity; ta: transportAddress.Unix; ds: Cap) {.asyncio.} =
entity.sock = connectUnixAsync(ta.path)
bootSocketEntity()
template spawnSocketRelay() {.dirty.} =
proc writeConn(turn: Turn; buf: seq[byte]) =
if entity.alive:
discard trampoline:
whelp write(entity.sock, buf)
var ops = RelayActorOptions(
packetWriter: writeConn,
initialOid: 0.Oid.some,
)
spawnRelay("socket", turn, ops) do (turn: Turn; relay: Relay):
entity.facet = turn.facet
entity.relay = relay
discard trampoline:
whelp boot(entity, ta, ds)
proc connectTransport(turn: Turn; ds: Cap; ta: transportAddress.Tcp) =
let entity = TcpEntity()
spawnSocketRelay()
proc connectTransport(turn: Turn; ds: Cap; ta: transportAddress.Unix) =
let entity = UnixEntity()
spawnSocketRelay()
elif defined(solo5):
import solo5_dispatcher
import taps
type
TcpEntity = ref object of Entity
relay: Relay
conn: Connection
decoder: BufferedDecoder
method message(entity: TcpEntity; turn: Turn; ass: AssertionRef) =
if ass.value.preservesTo(ForceDisconnect).isSome:
entity.conn.abort()
proc connectTransport(turn: Turn; ds: Cap; ta: transportAddress.Tcp) =
let entity = TcpEntity(facet: turn.facet)
proc writeConn(turn: Turn; buf: seq[byte]) =
assert not entity.conn.isNil
entity.conn.batch:
entity.conn.send(buf)
var ops = RelayActorOptions(
packetWriter: writeConn,
initialOid: 0.Oid.some,
)
spawnRelay("socket", turn, ops) do (turn: Turn; relay: Relay):
entity.facet = turn.facet
entity.relay = relay
var ep = newRemoteEndpoint()
if ta.host.isIpAddress:
ep.with ta.host.parseIpAddress
else:
ep.withHostname ta.host
ep.with ta.port.Port
var tp = newTransportProperties()
tp.require "reliability"
tp.ignore "congestion-control"
tp.ignore "preserve-order"
var preconn = newPreconnection(
remote=[ep], transport=tp.some)
entity.conn = preconn.initiate()
entity.facet.onStop do (turn: Turn):
entity.conn.close()
entity.conn.onConnectionError do (err: ref Exception):
run(entity.facet) do (turn: Turn):
terminate(turn, err)
entity.conn.onClosed():
stop(entity.facet)
entity.conn.onReceivedPartial do (data: seq[byte]; ctx: MessageContext; eom: bool):
entity.relay.recv(data)
if eom:
stop(entity.facet)
else:
entity.conn.receive()
entity.conn.onReady do ():
entity.facet.run do (turn: Turn):
publish(turn, ds, TransportConnection(
`addr`: ta.toPreserves,
control: newCap(entity, turn),
resolved: entity.relay.peer.accepted,
))
entity.conn.receive()
proc walk(turn: Turn; ds, origin: Cap; route: Route; transOff, stepOff: int) =
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: 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):
origin.relay.run do (turn: Turn):
# walk using the facet that manages the transport connection
walk(turn, ds, origin, route, transOff, 0)
type StepCallback = proc (turn: Turn; step: Value; origin: Cap; res: Resolved) {.closure.}
proc spawnStepResolver(turn: Turn; ds: Cap; stepType: Value; cb: StepCallback) =
let pat = observePattern(
ResolvedPathStep?:{1: grabRecord(stepType)},
{ @[0.toPreserves]: grabLit(), @[1.toPreserves]: grab() },
)
during(turn, ds, pat) do (origin: Cap; stepDetail: Literal[Value]):
proc duringCallback(turn: Turn; ass: Value; h: Handle): TurnAction =
var res: Resolved
if res.fromPreserves ass:
cb(turn, stepDetail.value, origin, res)
proc action(turn: Turn) =
stop(turn)
result = action
publish(turn, origin, Resolve(
step: stepDetail.value, observer: newCap(turn, during(duringCallback))))
proc spawnRelays*(turn: Turn; ds: Cap) =
## Spawn actors that manage routes and appease gatekeepers.
let transPat = observePattern(!TransportConnection, { @[0.toPreserves]: grab() })
# Use a generic pattern and type matching
# in the during handler because it is easy.
when defined(posix) and not defined(nimdoc):
let stdioPat = ?Observe(pattern: TransportConnection?:{0: ?:Stdio})
during(turn, ds, stdioPat) do:
connectTransport(turn, ds, Stdio())
# TODO: unix pattern
during(turn, ds, transPat) do (ta: Literal[transportAddress.Unix]):
try: connectTransport(turn, ds, ta.value)
except exceptions.IOError as e:
publish(turn, ds, TransportConnection(
`addr`: ta.toPreserve,
resolved: rejected(embed e),
))
# TODO: tcp pattern
during(turn, ds, transPat) do (ta: Literal[transportAddress.Tcp]):
try: connectTransport(turn, ds, ta.value)
except exceptions.IOError as e:
publish(turn, ds, TransportConnection(
`addr`: ta.toPreserves,
resolved: rejected(embed e),
))
let resolvePat = observePattern(!ResolvePath, {@[0.toPreserves]: grab()})
during(turn, ds, resolvePat) do (route: Literal[Route]):
for i, transAddr in route.value.transports:
connectRoute(turn, ds, route.value, i)
spawnStepResolver(turn, ds, "ref".toSymbol) do (
turn: Turn, step: Value, origin: Cap, res: Resolved):
publish(turn, ds, ResolvedPathStep(
origin: origin, pathStep: step, resolved: res))
type
BootProc* = proc (turn: Turn; ds: Cap) {.closure.}
proc resolve*(turn: Turn; ds: Cap; route: Route; bootProc: BootProc) =
## Resolve `route` within `ds` and call `bootProc` with resolved capabilities.
## If a resolved route is retracted then `bootProc` will be called again with
## the next available route.
var
destinations: OrderedSet[Cap]
activeDest: Cap
activeActor: Actor
proc bootActor(turn: Turn) =
assert activeActor.isNil
assert activeDest.isNil
for d in destinations:
activeDest = d
break
if not activeDest.isNil:
activeActor = linkActor(turn, "resolver") do (turn: Turn):
onStop(turn) do (turn: Turn):
activeActor = nil
activeDest = nil
bootActor(turn)
bootProc(turn, activeDest)
during(turn, ds, ResolvePath ?: {0: ?route, 3: ?:ResolvedAccepted}) do (dst: Cap):
destinations.incl dst
if activeDest.isNil:
bootActor(turn)
do:
destinations.excl dst
proc resolve*(turn: Turn; route: Route; bootProc: BootProc) =
## Resolve `route` and call `bootProc` with resolved capability.
let ds = turn.newDataspace()
resolve(turn, ds, route, bootProc)
spawnRelays(turn, ds)
when defined(posix):
const defaultRoute* = "<route [<stdio>]>"
proc envRoute*: Route =
## Get an route to a Syndicate capability from the calling environment.
## On UNIX this is the SYNDICATE_ROUTE environmental variable with a
## fallack to a defaultRoute_.
## See https://git.syndicate-lang.org/syndicate-lang/syndicate-protocols/raw/branch/main/schemas/gatekeeper.prs.
var text = getEnv("SYNDICATE_ROUTE", defaultRoute)
try:
if result.fromPreserves text.parsePreserves: return
except ValueError: discard
raise newException(ValueError, "failed to parse $SYNDICATE_ROUTE " & $text)
proc resolveEnvironment*(turn: Turn; bootProc: BootProc) =
## Resolve a capability from the calling environment
## and call `bootProc`. See envRoute_.
resolve(turn, envRoute(), bootProc)
# TODO: define a runActor that comes preloaded with relaying

View File

@ -1,61 +1,12 @@
# Emulate Nimble from CycloneDX data at sbom.json.
# Package
import std/json
version = "20240301"
author = "Emery Hemingway"
description = "Syndicated actors for conversational concurrency"
license = "Unlicense"
srcDir = "src"
proc lookupComponent(sbom: JsonNode; bomRef: string): JsonNode =
for c in sbom{"components"}.getElems.items:
if c{"bom-ref"}.getStr == bomRef:
return c
result = newJNull()
let
sbom = (getPkgDir() & "/sbom.json").readFile.parseJson
comp = sbom{"metadata", "component"}
bomRef = comp{"bom-ref"}.getStr
# Dependencies
version = comp{"version"}.getStr
author = comp{"authors"}[0]{"name"}.getStr
description = comp{"description"}.getStr
license = comp{"licenses"}[0]{"license", "id"}.getStr
for prop in comp{"properties"}.getElems.items:
let (key, val) = (prop{"name"}.getStr, prop{"value"}.getStr)
case key
of "nim:skipDirs:":
add(skipDirs, val)
of "nim:skipFiles:":
add(skipFiles, val)
of "nim:skipExt":
add(skipExt, val)
of "nim:installDirs":
add(installDirs, val)
of "nim:installFiles":
add(installFiles, val)
of "nim:installExt":
add(installExt, val)
of "nim:binDir":
add(binDir, val)
of "nim:srcDir":
add(srcDir, val)
of "nim:backend":
add(backend, val)
else:
if key.startsWith "nim:bin:":
namedBin[key[8..key.high]] = val
for depend in sbom{"dependencies"}.items:
if depend{"ref"}.getStr == bomRef:
for depRef in depend{"dependsOn"}.items:
let dep = sbom.lookupComponent(depRef.getStr)
var spec = dep{"name"}.getStr
for extRef in dep{"externalReferences"}.elems:
if extRef{"type"}.getStr == "vcs":
spec = extRef{"url"}.getStr
break
let ver = dep{"version"}.getStr
if ver != "":
if ver.allCharsInSet {'0'..'9', '.'}: spec.add " == "
else: spec.add '#'
spec.add ver
requires spec
break
requires "https://github.com/ehmry/hashlib.git >= 20231130", "nim >= 2.0.0", "https://git.syndicate-lang.org/ehmry/preserves-nim.git >= 20240208", "https://github.com/alaviss/nim-sys.git", "https://github.com/nim-works/cps"

View File

@ -1,6 +1,7 @@
include_rules
: foreach *.prs |> !preserves-schema-nim |> | {schema}
: foreach t*.nim | ../../preserves-nim/<tests> {schema} $(SYNDICATE_PROTOCOL) |> !nim_run |> | ../<test>
# : foreach solo5*.nim | ../../taps/<sources> ../../preserves-nim/<tests> {schema} $(SYNDICATE_PROTOCOL) |> !nim_solo5_spt |> | ../<test>
NIM_FLAGS += --define:traceSyndicate
: foreach *.prs |> !preserves_schema_nim |> | {schema}
: foreach t*.nim | ../../preserves-nim/<tests> {schema} $(SYNDICATE_PROTOCOL) |> !nim |> | ../<test> {test}
: | $(NIM_GROUPS) |> $(NIM) $(NIM_FLAGS) dump > %o |> nim.dump
: foreach {test} |> SYNDICATE_TRACE_FILE=%o ./%f |> ./%B.trace.bin {bintrace}
: foreach {bintrace} |> preserves-tool convert <%f >%o |> %B

View File

@ -1,40 +0,0 @@
# SPDX-FileCopyrightText: ☭ Emery Hemingway
# SPDX-License-Identifier: Unlicense
import
std/solo5,
pkg/taps,
pkg/preserves,
syndicate, syndicate/relays
acquireDevices([("relay", netBasic)], netAcquireHook)
type Netif {.preservesRecord: "netif"} = object
device, ipAddr: string
proc spawnNetifActor(turn: Turn; ds: Cap) =
spawnActor(turn, "netif") do (turn: Turn):
let facet = turn.facet
onInterfaceUp do (device: string; ip: IpAddress):
run(facet) do (turn: Turn):
if not ip.isLinkLocal:
discard publish(turn, ds, Netif(device: device, ipAddr: $ip))
runActor("relay-test") do (turn: Turn):
let root = turn.facet
onStop(turn) do (turn: Turn):
quit()
let ds = newDataspace(turn)
spawnNetifActor(turn, ds)
spawnRelays(turn, ds)
var
route: Route
pr = parsePreserves $solo5_start_info.cmdline
if route.fromPreserves pr:
echo "parsed route ", route.toPreserves
during(turn, ds, Netif?:{1: grab()}) do (ip: string):
echo "Acquired address ", ip
resolve(turn, ds, route) do (turn: Turn; ds: Cap):
echo "route resolved!"
echo "stopping root facet"
stop(turn, root)

View File

@ -1,3 +0,0 @@
define:ipv6Enabled
define:traceSyndicate
import:"std/assertions"

View File

@ -1,25 +0,0 @@
# SPDX-FileCopyrightText: ☭ Emery Hemingway
# SPDX-License-Identifier: Unlicense
import std/times
import solo5
import syndicate, syndicate/drivers/timers
acquireDevices()
runActor("timer-test") do (turn: Turn):
let timers = newDataspace(turn)
spawnTimerDriver(turn, timers)
onPublish(turn, timers, ?LaterThan(seconds: 1356100000)):
echo "now in 13th bʼakʼtun"
after(turn, timers, initDuration(seconds = 3)) do (turn: Turn):
echo "third timer expired"
stopActor(turn)
after(turn, timers, initDuration(seconds = 1)) do (turn: Turn):
echo "first timer expired"
after(turn, timers, initDuration(seconds = 2)) do (turn: Turn):
echo "second timer expired"

View File

@ -1,2 +0,0 @@
define:ipv6Enabled
import:"std/assertions"

View File

@ -1,11 +1,8 @@
# SPDX-FileCopyrightText: ☭ Emery Hemingway
# SPDX-License-Identifier: Unlicense
import std/[oserrors, parseopt, posix, strutils]
import
pkg/sys/[files, handles, ioqueue],
pkg/preserves,
syndicate, syndicate/relays
import std/[asyncdispatch, asyncfile, parseopt]
import preserves, syndicate, syndicate/relays
type
Present {.preservesRecord: "Present".} = object
@ -13,51 +10,33 @@ type
Says {.preservesRecord: "Says".} = object
who, what: string
proc syncAndStop(facet: Facet; cap: Cap) =
## Stop the actor responsible for `facet` after
## synchronizing with `cap`.
run(facet) do (turn: Turn):
sync(turn, cap, stopActor)
proc readStdin(facet: Facet; ds: Cap; username: string) =
let file = openAsync("/dev/stdin")
onStop(facet) do (turn: var Turn): close(file)
close(stdin)
proc readLine() {.gcsafe.} =
let future = readLine(file)
addCallback(future, facet) do (turn: var Turn):
var msg = read(future)
if msg == "": quit()
message(turn, ds, Says(who: username, what: msg))
readLine()
readLine()
proc readStdin(facet: Facet; ds: Cap; username: string) {.asyncio.} =
let
fd = stdin.getOsFileHandle()
flags = fcntl(fd.cint, F_GETFL, 0)
if flags < 0:
raiseOSError(osLastError())
if fcntl(fd.cint, F_SETFL, flags or O_NONBLOCK) < 0:
raiseOSError(osLastError())
let
file = newAsyncFile(FD fd)
buf = new string
buf[].setLen(0x1000)
while true:
let n = read(file, buf)
if n < 1:
stderr.writeLine "test_chat calls stopsActor ", facet.actor
syncAndStop(facet, ds)
return
else:
var msg = buf[][0..<n].strip
proc send(turn: Turn) =
message(turn, ds, Says(who: username, what: msg))
run(facet, send)
proc chat(facet: Facet; ds: Cap; username: string) =
during(facet, ds, ?:Present) do (who: string):
echo who, " joined"
do:
echo who, " left"
proc chat(turn: Turn; ds: Cap; username: string) =
during(turn, ds, ?:Present) do (who: string):
echo who, " joined"
do:
echo who, " left"
onMessage(facet, ds, ?:Says) do (who: string, what: string):
echo who, ": ", what
onMessage(turn, ds, ?:Says) do (who: string, what: string):
echo who, ": ", what
discard publish(turn, ds, Present(username: username))
discard trampoline:
whelp readStdin(turn.facet, ds, username)
discard publish(facet, ds, Present(username: username))
readStdin(facet, ds, username)
proc main =
let route = envRoute()
var username = ""
for kind, key, val in getopt():
@ -69,8 +48,10 @@ proc main =
if username == "":
stderr.writeLine "--user: unspecified"
else:
runActor("chat") do (turn: Turn):
resolveEnvironment(turn) do (turn: Turn; ds: Cap):
chat(turn, ds, username)
runActor("chat") do (root: Facet):
let ds = facet.newDataspace()
facet.spawnRelays(ds)
resolve(facet, ds, route) do (remote: Cap):
chat(facet, remote, username)
main()

View File

@ -1,85 +1,103 @@
# SPDX-FileCopyrightText: ☭ Emery Hemingway
# SPDX-License-Identifier: Unlicense
import
std/[options, tables, unittest],
pkg/preserves, syndicate, syndicate/protocols/timer
import std/[options, tables, unittest]
suite "example":
var pat: Pattern
check pat.fromPreserves parsePreserves"""
<group <arr> {
0: <lit 1>
1: <bind <group <arr> {
0: <bind <_>>
1: <_>
}>>
2: <_>
}>
"""
import preserves, syndicate, syndicate/protocols/gatekeeper
const A = "[1 2 3]"
test A:
let v = parsePreserves A
check:
not pat.matches(v)
import ./test_schema
const B = "[1 [2 3] 4]"
test B:
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
v = parsePreserves B
c = parsePreserves "[[2 3] 2]"
check pat.matches(v)
check pat.capture(v).toPreserves == c
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
const C = "[1 [2] 5]"
test C:
let v = parsePreserves C
check:
not pat.matches(v)
patC = grab obsA
const D = "[1 [2 3 4] 5]"
test D:
test $patC:
check patC.matches obsA
test $patB:
checkpoint $obsA
check patB.matches obsA
test "TransportConnection":
let
v = parsePreserves D
c = parsePreserves "[[2 3 4] 2]"
check pat.matches(v)
check pat.capture(v).toPreserves == c
const E = "[1 [<x> <y>] []]"
test E:
let
v = parsePreserves E
c = parsePreserves "[[<x> <y>] <x>]"
check pat.matches(v)
check pat.capture(v).toPreserves == c
suite "meta":
test "pattern-of-pattern":
let
pat = matchRecord("foo".toSymbol, matchDictionary({666.toPreserves: drop()}))
meta = pat.toPreserves.drop()
check $meta == "<group <rec group> {0: <group <rec rec> {0: <lit foo>}> 1: <group <dict> {0: <group <rec group> {0: <group <rec dict> {}> 1: <group <dict> {666: <_>}>}>}>}>"
test "observe":
let
val = Observe(pattern: LaterThan ?: {0: drop 12.24}).toPreserves
pat = grab(val)
check pat.matches(val)
check pat.capture(val) == @[val]
let
meta = observePattern(!LaterThan, {@[0.toPreserves]: grabLit()})
res = parsePreserves "[12.24]"
check meta.matches(val)
check meta.capture(val).toPreserves == res
test "connect-transport":
let pat = parsePreserves"""
<group <rec connect-transport> {0: <group <rec unix> {0: <lit "/run/user/1000/dataspace">}> 2: <group <rec accepted> {0: <bind <_>>}>}>
""".preservesTo(Pattern).get
let val = parsePreserves"""
<connect-transport <unix "/run/user/1000/dataspace"> #:#f <accepted #:#f>>
"""
check pat.matches(val)
check pat.capture(val).toPreserves == parsePreserves "[#:#f]"
pat = TransportConnection ?: { 2: ?:Rejected}
text = """<rec connect-transport [<_> <_> <rec rejected [<bind <_>>]>]>"""
check $pat == text

View File

@ -1,8 +1,8 @@
import
std/[streams, strutils, unittest],
pkg/preserves,
syndicate/relays,
syndicate/protocols/sturdy
import std/[streams, strutils, unittest]
import preserves
import syndicate/relays
import syndicate/protocols/sturdy
type WireRef = sturdy.WireRef

View File

@ -1,34 +1,32 @@
# SPDX-FileCopyrightText: ☭ Emery Hemingway
# SPDX-License-Identifier: Unlicense
import
std/times,
pkg/preserves,
syndicate, syndicate/drivers/timers
import std/[os, times]
import pkg/cps
import pkg/sys/ioqueue
import sam/syndicate
import sam/actors/timers
var passCount = 0
let actor = bootActor("timer-test") do (facet: Facet):
let timers = facet.newDataspace()
spawnTimerActor(timers)
runActor("timer-test") do (turn: Turn):
let timers = newDataspace(turn)
spawnTimerDriver(turn, timers)
onPublish(turn, timers, ?LaterThan(seconds: 1356100000)):
onPublish(timers, ?LaterThan(seconds: 1356091200)):
echo "now in 13th bʼakʼtun"
inc passCount
after(turn, timers, initDuration(seconds = 3)) do (turn: Turn):
timers.after(initDuration(seconds = 3)) do ():
echo "third timer expired"
assert passCount == 3
inc passCount
stopActor(facet)
after(turn, timers, initDuration(seconds = 1)) do (turn: Turn):
timers.after(initDuration(seconds = 1)) do ():
echo "first timer expired"
assert passCount == 1
inc passCount
after(turn, timers, initDuration(seconds = 2)) do (turn: Turn):
timers.after(initDuration(seconds = 2)) do ():
echo "second timer expired"
assert passCount == 2
inc passCount
doAssert passCount == 4, $passCount
while not actor.stopped:
echo "running actor"
if not run(actor):
echo "run(actor) did not progress"
ioqueue.run()
echo "ioqueue.run finished"