~ehmry/syndicate-nim

a5830a4a076ffd78127df4211f3d89c6910e70d2 — Emery Hemingway a month ago cfd863f
Parameterize Preserve type
M src/syndicate.nim => src/syndicate.nim +15 -8
@@ 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())

M src/syndicate/assertions.nim => src/syndicate/assertions.nim +20 -12
@@ 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) == "<capture <discard>>")

M src/syndicate/dataspaces.nim => src/syndicate/dataspaces.nim +8 -7
@@ 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]


M src/syndicate/drivers/timers.nim => src/syndicate/drivers/timers.nim +2 -2
@@ 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)

M src/syndicate/skeletons.nim => src/syndicate/skeletons.nim +4 -3
@@ 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:

M syndicate.nimble => syndicate.nimble +1 -1
@@ 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"

M tests/box_and_client.nim => tests/box_and_client.nim +6 -3
@@ 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 ## ``<box-state @value int>``
    `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))

M tests/test_box_and_client.nim => tests/test_box_and_client.nim +9 -9
@@ 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"

M tests/test_dsl.nim => tests/test_dsl.nim +7 -6
@@ 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.