diff --git a/src/syndicate.nim b/src/syndicate.nim index d9e00f7..63092d6 100644 --- a/src/syndicate.nim +++ b/src/syndicate.nim @@ -1,14 +1,11 @@ -# SPDX-License-Identifier: ISC +# SPDX-FileCopyrightText: ☭ 2021 Emery Hemingway +# SPDX-License-Identifier: Unlicense import std/[asyncdispatch, macros, options] import preserves, preserves/records import syndicate/[assertions, dataspaces, events, skeletons] -export preserves.`%` export preserves.fromPreserve -export records.init -export assertions.`?_` -export assertions.`?*` export assertions.Observe export dataspaces.Facet export dataspaces.FieldId @@ -128,7 +125,7 @@ proc onEvent(event: EventKind, pattern, handler: NimNode): NimNode = proc getCurrentFacet(): Facet {.inject, used.} = facet `handler` let a = `pattern` - result.assertion = Observe.init(a) + result.assertion = observe(a) result.analysis = some(analyzeAssertion(a)) result.callback = wrap(facet, EventKind(`event`), `handlerSym`) @@ -197,13 +194,13 @@ template withFacet*(f: Facet; body: untyped): untyped = ## Execute a Syndicate ``body`` using the ``Facet`` at ``f``. runnableExamples: import preserves, preserves/records - type Foo = ref object + type Foo {.record: "foo".} = ref object facet: Facet i: int proc incAndAssert(foo: Foo) = inc(foo.i) withFacet foo.facet: - react: assert: initRecord("Foo", %foo.i) + react: assert: foo proc getCurrentFacet(): Facet {.inject, used.} = f body @@ -219,3 +216,13 @@ type BootProc* = proc (facet: Facet) {.gcsafe.} template boot*(module: BootProc) = mixin getCurrentFacet module(getCurrentFacet()) + +macro `?`*(x: untyped): untyped = + ## Sugar for generating Syndicate patterns. + ## `?_` is a pattern that matches but discards arbitrary + ## values and `?` combined with any other identifier is + ## a match and capture. + if eqIdent(x, "_"): + quote: toPreserve(Discard()) + else: + quote: toPreserve(Capture()) diff --git a/src/syndicate/assertions.nim b/src/syndicate/assertions.nim index 673d1a9..d363cac 100644 --- a/src/syndicate/assertions.nim +++ b/src/syndicate/assertions.nim @@ -1,21 +1,29 @@ -# SPDX-License-Identifier: ISC +# SPDX-FileCopyrightText: ☭ 2021 Emery Hemingway +# SPDX-License-Identifier: Unlicense -import preserves, preserves/records +import std/options +import preserves -const - Discard* = RecordClass(label: symbol"discard", arity: 0) - Capture* = RecordClass(label: symbol"capture", arity: 1) - Observe* = RecordClass(label: symbol"observe", arity: 1) - #Inbound* = RecordClass(label: symbol"inbound", arity: 1) - #Outbound* = RecordClass(label: symbol"outbound", arity: 1) - #Instance* = RecordClass(label: symbol"instance", arity: 1) +type + Discard* {.record: "discard", pure.} = object + discard - `?_`* = initRecord("discard") - `?*`* = Capture % `?_` + Capture* {.record: "capture", pure.} = object + _: Discard + + Observe* {.record: "observe", pure.} = object + pattern: Preserve + +proc observe*[T](x: T): Preserve = + Observe(pattern: x.toPreserve).toPreserve proc captureCount*(pattern: Preserve): int = - if Capture.isClassOf pattern: + if pattern.preserveTo(Capture).isSome: result = 1 else: for e in pattern.items: result.inc captureCount(e) + +when isMainModule: + let a = observe(`?*`) + assert($toPreserve(a) == ">") diff --git a/src/syndicate/dataspaces.nim b/src/syndicate/dataspaces.nim index d0d5de3..3f8faab 100644 --- a/src/syndicate/dataspaces.nim +++ b/src/syndicate/dataspaces.nim @@ -1,4 +1,5 @@ -# SPDX-License-Identifier: ISC +# SPDX-FileCopyrightText: ☭ 2021 Emery Hemingway +# SPDX-License-Identifier: Unlicense import ./bags, ./dataflow, ./events, ./skeletons import preserves @@ -70,7 +71,7 @@ type Field* = object of RootObj id*: FieldId - Fields* = seq[Preserve] + Fields* = seq[Value] # TODO: compile-time tuples Turn = object @@ -403,7 +404,7 @@ proc initActivationAction(script: ActivationScript; name: string): Action = ds.activations.add(script) proc boot(root: Facet) = root.addStartScript(script) - ds.addActor(name, boot, Preserve(kind: pkSet), actor) + ds.addActor(name, boot, Value(kind: pkSet), actor) Action(impl: impl, kind: activationAction) proc activate(facet; name: string; script: ActivationScript) = @@ -547,15 +548,15 @@ template declareField*(facet: Facet; F: untyped; T: typedesc; initial: T): untyp let fieldOff = facet.fields.high proc set(f: DistinctField; x: T) {.used.} = facet.actor.dataspace.dataflow.recordDamage(f.id) - facet.fields[fieldOff] = toPreserve[T](x) - proc set(f: DistinctField; x: Preserve) {.used.} = + facet.fields[fieldOff] = toPreserve(x) + proc set(f: DistinctField; x: Value) {.used.} = facet.actor.dataspace.dataflow.recordDamage(f.id) facet.fields[fieldOff] = x proc get(f: DistinctField): T {.used.} = facet.actor.dataspace.dataflow.recordObservation(f.id) - if not fromPreserve[T](result, facet.fields[fieldOff]): + if not fromPreserve(result, facet.fields[fieldOff]): raise newException(ValueError, "cannot convert field " & $F & " to " & $T) - proc getPreserve(f: DistinctField): Preserve {.used.} = + proc getPreserve(f: DistinctField): Value {.used.} = facet.actor.dataspace.dataflow.recordObservation(f.id) facet.fields[fieldOff] diff --git a/src/syndicate/drivers/timers.nim b/src/syndicate/drivers/timers.nim index 8e76974..2f3db9d 100644 --- a/src/syndicate/drivers/timers.nim +++ b/src/syndicate/drivers/timers.nim @@ -2,7 +2,7 @@ import std/[asyncdispatch, monotimes, times] import preserves, preserves/records -import syndicate +import syndicate, syndicate/assertions type TimeLaterThan* {.record: "TimeLaterThan".} = object `deadline`*: Monotime @@ -21,7 +21,7 @@ proc fromPreserveHook*(mt: var Monotime; p: Preserve): bool = syndicate timerDriver: spawn "timer": - during(Observe % (prsTimeLaterThan(`?*`))) do (deadline: MonoTime): + during(observe(prsTimeLaterThan(?deadline))) do (deadline: MonoTime): let now = getMonoTime() period = inMilliseconds(deadline - now) diff --git a/src/syndicate/skeletons.nim b/src/syndicate/skeletons.nim index 05eb738..70852d1 100644 --- a/src/syndicate/skeletons.nim +++ b/src/syndicate/skeletons.nim @@ -1,4 +1,5 @@ -# SPDX-License-Identifier: ISC +# SPDX-FileCopyrightText: ☭ 2021 Emery Hemingway +# SPDX-License-Identifier: Unlicense import ./assertions, ./bags, ./events import preserves, preserves/records @@ -36,9 +37,9 @@ proc projectPaths(v: Value; paths: seq[Path]): seq[Value] = proc analyzeAssertion*(a: Value): Analysis = var path: Path proc walk(analysis: var Analysis; a: Value): Skeleton[Shape] = - if Discard.isClassOf a: + if a.preserveTo(Discard).isSome: discard - elif Capture.isClassOf a: + elif a.preserveTo(Capture).isSome: analysis.capturePaths.add(path) result = walk(analysis, a.fields[0]) else: diff --git a/syndicate.nimble b/syndicate.nimble index d382af3..2a44e86 100644 --- a/syndicate.nimble +++ b/syndicate.nimble @@ -9,4 +9,4 @@ srcDir = "src" # Dependencies -requires "nim >= 1.4.8", "preserves >= 0.3.0" +requires "nim >= 1.4.8", "preserves >= 1.0.0" diff --git a/tests/box_and_client.nim b/tests/box_and_client.nim index 05cfd01..a101954 100644 --- a/tests/box_and_client.nim +++ b/tests/box_and_client.nim @@ -1,9 +1,10 @@ -## Date of generation: 2021-08-28 10:14 +## Date of generation: 2021-09-01 13:32 import std/typetraits, preserves type + EmbeddedType = void BoxState* {.record: "box-state".} = object ## ```` `value`*: BiggestInt @@ -11,7 +12,9 @@ type `value`*: BiggestInt proc prsBoxState*(value: Preserve | BiggestInt): Preserve = - initRecord(symbol("box-state"), value) + initRecord[EmbeddedType](symbol("box-state", EmbeddedType), + toPreserve(value, EmbeddedType)) proc prsSetBox*(value: Preserve | BiggestInt): Preserve = - initRecord(symbol("set-box"), value) + initRecord[EmbeddedType](symbol("set-box", EmbeddedType), + toPreserve(value, EmbeddedType)) diff --git a/tests/test_box_and_client.nim b/tests/test_box_and_client.nim index f1ab191..c76b853 100644 --- a/tests/test_box_and_client.nim +++ b/tests/test_box_and_client.nim @@ -8,9 +8,9 @@ import ./box_and_client const N = 100000 -const - `?_` = init(Discard) - `?$` = init(Capture, `?_`) +let + `?_` = Discard().toPreserve + `?$` = Capture().toPreserve proc boot(facet: Facet) = @@ -28,19 +28,19 @@ proc boot(facet: Facet) = echo "terminated box root facet" facet.addEndpoint do (facet: Facet) -> EndpointSpec: - const a = prsSetBox(`?$`) + let a = prsSetBox(`?$`) result.analysis = some analyzeAssertion(a) proc cb(facet: Facet; vs: seq[Value]) = facet.scheduleScript do (facet: Facet): value.set(vs[0]) # echo "box updated value ", vs[0] result.callback = facet.wrap(messageEvent, cb) - result.assertion = Observe.init(prsSetBox(`?$`)) + result.assertion = observe(prsSetBox(`?$`)) facet.spawn("client") do (facet: Facet): facet.addEndpoint do (facet: Facet) -> EndpointSpec: - const a = prsBoxState(`?$`) + let a = prsBoxState(`?$`) result.analysis = some analyzeAssertion(a) proc cb(facet: Facet; vs: seq[Value]) = facet.scheduleScript do (facet: Facet): @@ -48,16 +48,16 @@ proc boot(facet: Facet) = # echo "client sending ", v facet.send(v) result.callback = facet.wrap(addedEvent, cb) - result.assertion = Observe.init(prsBoxState(`?$`)) + result.assertion = observe(prsBoxState(`?$`)) facet.addEndpoint do (facet: Facet) -> EndpointSpec: - const a = prsBoxState(`?_`) + let a = prsBoxState(`?_`) result.analysis = some analyzeAssertion(a) proc cb(facet: Facet; vs: seq[Value]) = facet.scheduleScript do (facet: Facet): echo "box gone" result.callback = facet.wrap(removedEvent, cb) - result.assertion = Observe.init(prsBoxState(`?_`)) + result.assertion = observe(prsBoxState(`?_`)) facet.actor.dataspace.ground.addStopHandler do (_: Dataspace): echo "stopping box-and-client" diff --git a/tests/test_dsl.nim b/tests/test_dsl.nim index 7d4d771..14945e8 100644 --- a/tests/test_dsl.nim +++ b/tests/test_dsl.nim @@ -1,7 +1,8 @@ -# SPDX-License-Identifier: ISC +# SPDX-FileCopyrightText: ☭ 2021 Emery Hemingway +# SPDX-License-Identifier: Unlicense -import asyncdispatch -import preserves, preserves/records +import std/asyncdispatch +import preserves import syndicate import ./box_and_client @@ -13,7 +14,7 @@ syndicate testDsl: asserting prsBoxState(currentValue.get) stopIf currentValue.get == 10: echo "box: terminating" - onMessage(prsSetBox(`?*`)) do (newValue: int): + onMessage(prsSetBox(?newValue)) do (newValue: int): # The SetBox message is unpacked to `newValue: int` echo "box: taking on new value ", newValue currentValue.set(newValue) @@ -21,10 +22,10 @@ syndicate testDsl: spawn "client": #stopIf retracted(observe(SetBox, _)): # echo "client: box has gone" - onAsserted(prsBoxState(`?*`)) do (v: BiggestInt): + onAsserted(prsBoxState(?v)) do (v: BiggestInt): echo "client: learned that box's value is now ", v send(prsSetBox(v.succ)) - onRetracted(prsBoxState(`?_`)) do (_): + onRetracted(prsBoxState(?_)) do (_): echo "client: box state disappeared" onStop: quit(0) # Quit explicitly rather than let the dispatcher run empty.