Pass only a single preserves value to assert

This commit is contained in:
Emery Hemingway 2021-06-30 13:30:56 +02:00
parent 3de97c5cf1
commit 86b2dfbdab
6 changed files with 71 additions and 70 deletions

View File

@ -1,11 +1,11 @@
# SPDX-License-Identifier: ISC
import std/[asyncdispatch, macros, options]
import preserves, preserves/records
import preserves
import syndicate/[assertions, dataspaces, events, skeletons]
export assertions.Capture
export assertions.Discard
export assertions.`?_`
export assertions.`?*`
export assertions.Observe
export dataspaces.Facet
export dataspaces.FieldId
@ -29,9 +29,8 @@ proc `==`*(x, y: FieldId): bool {.borrow.}
proc newLit(p: pointer): NimNode = ident"nil"
## Hack to make `newLit` work on `Presevere`.
proc getCurrentFacet*(): Facet =
proc getCurrentFacet*(): Facet {.error.}
## Return the current `Facet` for this context.
raiseAssert "getCurrentFacet called outside of a Syndicate context"
template stopIf*(cond, body: untyped): untyped =
## Stop the current facet if `cond` is true and
@ -43,41 +42,23 @@ template stopIf*(cond, body: untyped): untyped =
proc getCurrentFacet(): Facet {.inject, used.} = facet
body
template send*(class: RecordClass; fields: varargs[Preserve,
toPreserve]): untyped =
template sendMessage*(msg: untyped): untyped =
mixin getCurrentFacet
send(getCurrentFacet(), init(class, fields))
send(getCurrentFacet(), toPreserve(msg))
proc assertionForRecord(class: RecordClass; doHandler: NimNode): NimNode =
## Generate an assertion that captures or discards the items of record `class`
## according to the parameters of `doHandler`. `_` parameters are discarded.
let formalArgs = doHandler[3]
if formalArgs.len.pred != class.arity:
error($formalArgs.repr & " does not match record class " & $class, doHandler)
result = newCall("init", newLit(class))
for i, arg in formalArgs:
if i > 0:
arg.expectKind nnkIdentDefs
if arg[0] == ident"_":
result.add newCall("init", ident"Discard")
else:
result.add newCall("init", ident"Capture", newCall("init",
ident"Discard"))
proc callbackForEvent(event: EventKind; class: RecordClass; doHandler: NimNode): NimNode =
## Generate a procedure that checks an event kind, unpacks the fields of `class` to match the
## parameters of `doHandler`, and calls the body of `doHandler`.
let formalArgs = doHandler[3]
if formalArgs.len.pred != class.arity:
error($formalArgs.repr & " does not match record class " & $class, doHandler)
doHandler.expectKind nnkDo
proc callbackForEvent(event: EventKind; pattern, handler: NimNode): NimNode =
## Generate a procedure that checks an event kind, unpacks `pattern` match to fit the
## parameters of `handler`, and calls the body of `handler`.
# TODO: compile time analysis of pattern to count number of captures
handler.expectKind nnkDo
let
formalArgs = handler[3]
cbFacetSym = genSym(nskParam, "facet")
scriptFacetSym = genSym(nskParam, "facet")
eventSym = genSym(nskParam, "event")
recSym = genSym(nskParam, "record")
var
letSection = newNimNode(nnkLetSection, doHandler)
letSection = newNimNode(nnkLetSection, handler)
captureCount: int
for i, arg in formalArgs:
if i > 0:
@ -111,7 +92,7 @@ proc callbackForEvent(event: EventKind; class: RecordClass; doHandler: NimNode):
body = scriptFacetSym,
pragmas = newNimNode(nnkPragma).add(ident"inject").add(ident"used")),
letSection,
doHandler[6]
handler[6]
)
)
newProc(
@ -133,28 +114,27 @@ proc callbackForEvent(event: EventKind; class: RecordClass; doHandler: NimNode):
# TODO: this proc just checks the event type and then schedules a script,
# should the event check be done in skeletons instead?
proc onEvent(event: EventKind; class: RecordClass; doHandler: NimNode): NimNode =
proc onEvent(event: EventKind; pattern, handler: NimNode): NimNode =
let
assertion = assertionForRecord(class, doHandler)
handler = callbackForEvent(event, class, doHandler)
handler = callbackForEvent(event, pattern, handler)
handlerSym = handler[0]
result = quote do:
`handler`
mixin getCurrentFacet
discard getCurrentFacet().addEndpoint do (facet: Facet) -> EndpointSpec:
let a = `assertion`
result.assertion = some(init(Observe, a))
let a = `pattern`
result.assertion = some(toPreserve(observe(a)))
result.analysis = some(analyzeAssertion(a))
result.analysis.get.callback = wrap(facet, `handlerSym`)
macro onAsserted*(class: static[RecordClass]; doHandler: untyped) =
onEvent(addedEvent, class, doHandler)
macro onAsserted*(pattern: Preserve; handler: untyped) =
onEvent(addedEvent, pattern, handler)
macro onRetracted*(class: static[RecordClass]; doHandler: untyped) =
onEvent(removedEvent, class, doHandler)
macro onRetracted*(pattern: Preserve; handler: untyped) =
onEvent(removedEvent, pattern, handler)
macro onMessage*(class: static[RecordClass]; doHandler: untyped) =
onEvent(messageEvent, class, doHandler)
macro onMessage*(pattern: Preserve; doHandler: untyped) =
onEvent(messageEvent, pattern, doHandler)
template onStart*(body: untyped): untyped =
mixin getCurrentFacet
@ -168,11 +148,10 @@ template onStop*(body: untyped): untyped =
proc getCurrentFacet(): Facet {.inject, used.} = facet
body
template assert*(class: RecordClass; fields: varargs[Preserve, toPreserve]): untyped =
template assert*(a: Preserve): untyped =
mixin getCurrentFacet
let facet = getCurrentFacet()
discard facet.addEndpoint do (_: Facet) -> EndpointSpec:
let a = init(class, fields)
result.assertion = some(a)
template field*(F: untyped; T: typedesc; initial: T): untyped =

View File

@ -2,10 +2,30 @@
import preserves, preserves/records
type
Discard* {.record: "discard".} = tuple[]
Capture* {.record: "capture".} = tuple[pattern: Preserve]
Observe* {.record: "observe".} = tuple[pattern: Preserve]
#Inbound* = RecordClass(label: symbol"inbound", arity: 1)
#Outbound* = RecordClass(label: symbol"outbound", arity: 1)
#Instance* = RecordClass(label: symbol"instance", arity: 1)
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)
`?_`* = initRecord("discard")
`?*`* = initRecord("capture", `?_`)
proc capture*(pattern: Preserve): Capture {.inline.} = (pattern,)
proc capture*[T](pattern: T): Capture {.inline.} = (pattern.toPreserve,)
proc pattern*(class: RecordClass; fields: varargs[Preserve, toPreserve]): Preserve {.inline.} =
## Build a pattern for capturing some fields of a tuple type.
assert(class.arity == fields.len)
init(class, fields)
proc pattern*(T: typedesc[tuple]; fields: varargs[Preserve, toPreserve]): Preserve {.inline.} =
## Build a pattern for capturing some fields of a tuple type.
# TODO: pattern type?
pattern(classOf(T), fields)
proc observe*(pattern: Preserve): Observe {.inline.} = (pattern,)
proc observe*[T](pattern: T): Observe {.inline.} = (pattern.toPreserve,)

View File

@ -555,7 +555,7 @@ proc bootModule*(name: string; bootProc: ActivationScript): Future[void] =
template declareField*(facet: Facet; F: untyped; T: typedesc; initial: T): untyped =
## Declare getter and setter procs for field `F` of type `T` initalized with `initial`.
type DistinctField = object of Field
type DistinctField {.final, unpreservable.} = object of Field
discard
let `F` {.inject.} = DistinctField(id: facet.actor.dataspace.generateId.FieldId)
facet.actor.dataspace.dataflow.defineObservableProperty(`F`.id)

View File

@ -48,11 +48,14 @@ proc `$`(a: Analysis): string =
proc analyzeAssertion*(a: Value): Analysis =
var path: Path
proc walk(analysis: var Analysis; a: Value): Skeleton[Shape] =
if Capture.isClassOf a:
const
discardClass = classOf Discard
captureClass = classOf Capture
if discardClass.isClassOf a:
discard
elif captureClass.isClassOf a:
analysis.capturePaths.add(path)
result = walk(analysis, a.fields[0])
elif Discard.isClassOf a:
discard
else:
if a.kind == pkRecord:
let class = classOf(a)
@ -274,6 +277,7 @@ proc adjustAssertion*(index: var Index; outerValue: Value; delta: int): ChangeDe
(proc (l: Leaf; v: Value) = l.cachedAssertions.incl(v)),
(proc (h: Handler; vs: seq[Value]) =
if h.cachedCaptures.change(vs, +1) == cdAbsentToPresent:
#debugEcho " assertion of ", outerValue
for cb in h.callbacks: cb(addedEvent, vs)))
of cdPresentToAbsent:
index.root.modify(
@ -283,6 +287,7 @@ proc adjustAssertion*(index: var Index; outerValue: Value; delta: int): ChangeDe
(proc (l: Leaf; v: Value) = l.cachedAssertions.excl(v)),
(proc (h: Handler; vs: seq[Value]) =
if h.cachedCaptures.change(vs, -1) == cdPresentToAbsent:
#debugEcho "retraction of ", outerValue
for cb in h.callbacks: cb(removedEvent, vs)))
else:
discard

View File

@ -7,8 +7,6 @@ import asyncdispatch, tables, options
const N = 100000
const
`?_` = init(Discard)
`?$` = init(Capture, `?_`)
BoxState = RecordClass(label: symbol"BoxState", arity: 1)
SetBox = RecordClass(label: symbol"SetBox", arity: 1)
@ -30,21 +28,21 @@ proc boot(facet: Facet) =
echo "terminated box root facet"
discard facet.addEndpoint do (facet: Facet) -> EndpointSpec:
const a = SetBox.init(`?$`)
const a = SetBox.init(`?*`)
result.analysis = some analyzeAssertion(a)
proc cb(facet: Facet; evt: EventKind; vs: seq[Value]) =
if evt == messageEvent:
facet.scheduleScript do (facet: Facet):
value.set(vs[0])
# echo "box updated value ", vs[0]
echo "box updated value ", vs[0]
result.analysis.get.callback = facet.wrap cb
const o = Observe.init(SetBox.init(`?$`))
const o = observe(SetBox.init(`?*`)).toPreserve
result.assertion = some o
facet.spawn("client") do (facet: Facet):
discard facet.addEndpoint do (facet: Facet) -> EndpointSpec:
const a = BoxState.init(`?$`)
const a = BoxState.init(`?*`)
result.analysis = some analyzeAssertion(a)
proc cb(facet: Facet; evt: EventKind; vs: seq[Value]) =
if evt == addedEvent:
@ -53,7 +51,7 @@ proc boot(facet: Facet) =
# echo "client sending ", v
facet.send(v)
result.analysis.get.callback = facet.wrap cb
const o = Observe.init(BoxState.init(`?$`))
const o = observe(BoxState.init(`?*`)).toPreserve
result.assertion = some o
discard facet.addEndpoint do (facet: Facet) -> EndpointSpec:
@ -64,7 +62,7 @@ proc boot(facet: Facet) =
facet.scheduleScript do (facet: Facet):
echo "box gone"
result.analysis.get.callback = facet.wrap cb
const o = Observe.init(BoxState.init(`?_`))
const o = observe(BoxState.init(`?_`)).toPreserve
result.assertion = some o
facet.actor.dataspace.ground.addStopHandler do (_: Dataspace):

View File

@ -7,16 +7,15 @@ import syndicate
const
BoxState = RecordClass(label: symbol"box-state", arity: 1)
SetBox = RecordClass(label: symbol"set-box", arity: 1)
# TODO: hide RecordClass behind Assertion and Message types
syndicate "test_dsl":
spawn "box":
field(currentValue, int, 0)
assert(BoxState, currentValue.get)
assert(BoxState.init currentValue.get)
stopIf currentValue.get == 10:
echo "box: terminating"
onMessage(SetBox) do (newValue: int):
onMessage(SetBox % `?*`) do (newValue: int):
# The SetBox message is unpacked to `newValue: int`
echo "box: taking on new value ", newValue
currentValue.set(newValue)
@ -24,10 +23,10 @@ syndicate "test_dsl":
spawn "client":
#stopIf retracted(observe(SetBox, _)):
# echo "client: box has gone"
onAsserted(BoxState) do (v: int):
onAsserted(BoxState % `?*`) do (v: int):
echo "client: learned that box's value is now ", v
send(SetBox, v+1)
onRetracted(BoxState) do (_):
sendMessage(SetBox % v.succ)
onRetracted(BoxState % `?_`) do (_):
echo "client: box state disappeared"
onStop:
quit(0) # Quit explicitly rather than let the dispatcher run empty.