syndicate-nim/src/syndicate/skeletons.nim

280 lines
11 KiB
Nim
Raw Normal View History

2021-09-01 11:44:28 +00:00
# SPDX-FileCopyrightText: ☭ 2021 Emery Hemingway
# SPDX-License-Identifier: Unlicense
2022-03-10 23:30:07 +00:00
import std/[hashes, lists, options, sets, tables]
import preserves
import ./actors, ./bags, ./patterns
2022-03-10 23:30:07 +00:00
template trace(args: varargs[untyped]) = stderr.writeLine(args)
2021-07-09 10:38:10 +00:00
2022-03-10 23:30:07 +00:00
type
Value = Preserve[Ref]
Path = seq[Value]
2021-07-09 10:38:10 +00:00
proc projectPath(v: Value; path: Path): Value =
result = v
for index in path:
result = result[index]
proc projectPaths(v: Value; paths: seq[Path]): seq[Value] =
result.setLen(paths.len)
for i, path in paths: result[i] = projectPath(v, path)
2022-03-10 23:30:07 +00:00
trace "projected ", v, " by paths ", paths, " to ", result
type Class = distinct string
proc hash(cls: Class): Hash {.borrow.}
proc `==`(x, y: Class): bool {.borrow.}
proc classOf*(v: Value): Class =
if v.isRecord:
result = Class $v.label & "/" & $v.arity
elif v.isSequence:
result = Class $v.len
elif v.isDictionary:
result = Class "{}"
proc classOf*(p: Pattern): Class =
if p.orKind == PatternKind.DCompound:
case p.dcompound.orKind
of DCompoundKind.rec:
result = Class $p.dcompound.rec.label & "/" & $p.dcompound.rec.fields.len
of DCompoundKind.arr:
result = Class $p.dcompound.arr.items.len
of DCompoundKind.dict:
result = Class "{}"
proc step(value, index: Value): Value =
try: result = value[index]
except KeyError, ValueError:
trace "step failed, ", index, " not in ", value
type
2022-03-10 23:30:07 +00:00
EventKind = enum addedEvent, removedEvent, messageEvent
2021-07-09 13:08:02 +00:00
AssertionCache = HashSet[Value]
2022-03-10 23:30:07 +00:00
ObserverGroup = ref object
cachedCaptures: Bag[seq[Value]]
observers: Table[Ref, TableRef[seq[Value], Handle]]
Leaf = ref object
2021-07-09 13:08:02 +00:00
cachedAssertions: AssertionCache
2022-03-10 23:30:07 +00:00
observerGroups: Table[seq[Path], ObserverGroup]
Continuation = ref object
2021-07-09 13:08:02 +00:00
cachedAssertions: AssertionCache
2022-03-10 23:30:07 +00:00
leafMap: Table[seq[Path], TableRef[seq[Value], Leaf]]
2022-03-10 23:30:07 +00:00
Selector = tuple[popCount: int; index: Value]
Node = ref object
2022-03-10 23:30:07 +00:00
edges: Table[Selector, TableRef[Class, Node]]
continuation: Continuation
using
continuation: Continuation
leaf: Leaf
node: Node
proc isEmpty(leaf): bool =
2022-03-10 23:30:07 +00:00
leaf.cachedAssertions.len == 0 and leaf.observerGroups.len == 0
type
ContinuationProc = proc (c: Continuation; v: Value) {.gcsafe.}
LeafProc = proc (l: Leaf; v: Value) {.gcsafe.}
2022-03-10 23:30:07 +00:00
ObserverProc = proc (turn: var Turn; h: ObserverGroup; vs: seq[Value]) {.gcsafe.}
proc modify(node; turn: var Turn; operation: EventKind; outerValue: Value;
mCont: ContinuationProc; mLeaf: LeafProc; mObserverGroup: ObserverProc) =
trace "modify node with outerValue ", outerValue
proc walkNode(turn: var Turn; node; termStack: SinglyLinkedList[Value]) =
mCont(node.continuation, outerValue)
if node.continuation.leafMap.len == 0:
trace "node.continuation leafMap is empty"
for (constPaths, constValMap) in node.continuation.leafMap.pairs:
trace "got entry in node.continuation.leafMap for ", constPaths
let constValues = projectPaths(outerValue, constPaths)
var leaf = constValMap.getOrDefault(constValues)
if leaf.isNil and operation == addedEvent:
new leaf
constValMap[constValues] = leaf
if not leaf.isNil:
mLeaf(leaf, outerValue)
for (capturePaths, observerGroup) in leaf.observerGroups.pairs:
mObserverGroup(turn, observerGroup, projectPaths(outerValue, capturePaths))
if operation == removedEvent and leaf.isEmpty:
constValMap.del(constValues)
if constValues.len == 0:
node.continuation.leafMap.del(constPaths)
for (selector, table) in node.edges.pairs:
var nextStack = termStack
for _ in 1..selector.popCount:
nextStack.head = nextStack.head.next
2022-03-10 23:30:07 +00:00
trace "step ", nextStack.head.value, " with ", selector.index
let
nextValue = step(nextStack.head.value, selector.index)
nextClass = classOf nextValue
if nextClass != Class"":
let nextNode = table.getOrDefault(nextClass)
if not nextNode.isNil:
2022-03-10 23:30:07 +00:00
nextStack.prepend(nextValue)
walkNode(turn, nextNode, nextStack)
var stack: SinglyLinkedList[Value]
stack.prepend(outerValue)
walkNode(turn, node, stack)
2021-07-09 13:08:02 +00:00
2022-03-10 23:30:07 +00:00
proc extend(node: Node; pat: Pattern): Continuation =
trace "extend node with ", pat
var path: Path
proc walkNode(node: Node; popCount: Natural; stepIndex: Value; pat: Pattern): tuple[popCount: Natural, nextNode: Node] =
trace "walkNode step ", stepIndex, " of ", pat
case pat.orKind
of PatternKind.DDiscard, PatternKind.DLit: result = (popCount, node)
of PatternKind.DBind: result = walkNode(node, popCount, stepIndex, pat.dbind.pattern)
of PatternKind.DCompound:
let selector = (popCount, stepIndex,)
var table = node.edges.getOrDefault(selector)
2021-07-09 13:08:02 +00:00
if table.isNil:
2022-03-10 23:30:07 +00:00
trace "allocate new table for selector ", selector, " for ", pat
table = newTable[Class, Node]()
2021-07-09 13:08:02 +00:00
node.edges[selector] = table
2022-03-10 23:30:07 +00:00
else:
trace "got a table for ", pat, " with selector ", selector
let class = classOf pat
result.nextNode = table.getOrDefault(class)
if result.nextNode.isNil:
trace "allocate result.nextNode for ", string class
new result.nextNode
table[class] = result.nextNode
new result.nextNode.continuation
for a in node.continuation.cachedAssertions:
2022-03-10 23:30:07 +00:00
if class == classOf projectPath(a, path):
result.nextNode.continuation.cachedAssertions.incl a
result.popCount = 0
template walkKey(pat: Pattern; stepIndex: Value) =
trace "walkKey ", pat, " with step ", stepIndex
path.add(stepIndex)
result = walkNode(result.nextNode, popCount, stepIndex, pat)
discard path.pop()
2022-03-10 23:30:07 +00:00
case pat.dcompound.orKind
of DCompoundKind.rec:
for k, e in pat.dcompound.rec.fields: walkKey(e, k.toPreserve(Ref))
of DCompoundKind.arr:
for k, e in pat.dcompound.arr.items: walkKey(e, k.toPreserve(Ref))
of DCompoundKind.dict:
for k, e in pat.dcompound.dict.entries: walkKey(e, k)
result.popCount.inc
walkNode(node, 0, toPreserve(0, Ref), pat).nextNode.continuation
type
Index* = object
allAssertions: Bag[Value]
root: Node
proc initIndex*(): Index =
2021-07-08 09:50:13 +00:00
Index(root: Node(continuation: Continuation()))
2022-03-10 23:30:07 +00:00
proc add*(index: var Index; turn: var Turn; pattern: Pattern; observer: Ref) =
trace "add pattern ", pattern, " for ", observer
let
2022-03-10 23:30:07 +00:00
analysis = analyse pattern
continuation = index.root.extend pattern
var constValMap = continuation.leafMap.getOrDefault(analysis.constPaths)
if constValMap.isNil:
2022-03-10 23:30:07 +00:00
trace "allocate constValMap in leafMap for ", analysis.constPaths
new constValMap
for a in continuation.cachedAssertions:
2022-03-10 23:30:07 +00:00
let key = projectPaths(a, analysis.constPaths)
2021-07-09 13:08:02 +00:00
var leaf = constValMap.getOrDefault(key)
if leaf.isNil:
new leaf
2021-07-09 13:08:02 +00:00
constValMap[key] = leaf
leaf.cachedAssertions.incl(a)
2022-03-10 23:30:07 +00:00
trace "update leafMap for ", analysis.constPaths
continuation.leafMap[analysis.constPaths] = constValMap
var leaf = constValMap.getOrDefault(analysis.constValues)
if leaf.isNil:
new leaf
2022-03-10 23:30:07 +00:00
constValMap[analysis.constValues] = leaf
trace "get observerGroup for ", analysis.capturePaths
var observerGroup = leaf.observerGroups.getOrDefault(analysis.capturePaths)
if observerGroup.isNil:
trace "allocate observerGroup for ", analysis.capturePaths
new observerGroup
for a in leaf.cachedAssertions:
2022-03-10 23:30:07 +00:00
discard observerGroup.cachedCaptures.change(projectPaths(a, analysis.capturePaths), +1)
leaf.observerGroups[analysis.capturePaths] = observerGroup
var captureMap = newTable[seq[Value], Handle]()
for (count, captures) in observerGroup.cachedCaptures:
captureMap[captures] = publish(turn, observer, captures)
observerGroup.observers[observer] = captureMap
proc remove*(index: var Index; turn: var Turn; pattern: Pattern; observer: Ref) =
var
analysis = analyse pattern
continuation = index.root.extend pattern
let constValMap = continuation.leafMap.getOrDefault(analysis.constPaths)
if not constValMap.isNil:
let leaf = constValMap.getOrDefault(analysis.constValues)
if not leaf.isNil:
let observerGroup = leaf.observerGroups.getOrDefault(analysis.capturePaths)
if not observerGroup.isNil:
let captureMap = observerGroup.observers.getOrDefault(observer)
if not captureMap.isNil:
for handle in captureMap.values: retract(observer.target, turn, handle)
observerGroup.observers.del(observer)
if observerGroup.observers.len == 0:
leaf.observerGroups.del(analysis.capturePaths)
if leaf.isEmpty:
constValMap.del(analysis.constValues)
if constValMap.len == 0:
continuation.leafMap.del(analysis.constPaths)
proc adjustAssertion*(index: var Index; turn: var Turn; outerValue: Value; delta: int): bool =
case index.allAssertions.change(outerValue, delta)
of cdAbsentToPresent:
2022-03-10 23:30:07 +00:00
result = true
index.root.modify(
2022-03-10 23:30:07 +00:00
turn,
addedEvent,
outerValue,
(proc (c: Continuation; v: Value) = c.cachedAssertions.incl(v)),
(proc (l: Leaf; v: Value) = l.cachedAssertions.incl(v)),
2022-03-10 23:30:07 +00:00
(proc (turn: var Turn; group: ObserverGroup; vs: seq[Value]) =
if group.cachedCaptures.change(vs, +1) == cdAbsentToPresent:
for (observer, captureMap) in group.observers.pairs:
let a = vs.toPreserve(Ref)
trace "publish to dataspace observer ", observer, " ", a
captureMap[vs] = publish(turn, observer, a)))
# TODO: this handle is coming from the facet?
of cdPresentToAbsent:
2022-03-10 23:30:07 +00:00
result = true
index.root.modify(
2022-03-10 23:30:07 +00:00
turn,
removedEvent,
outerValue,
(proc (c: Continuation; v: Value) = c.cachedAssertions.excl(v)),
(proc (l: Leaf; v: Value) = l.cachedAssertions.excl(v)),
2022-03-10 23:30:07 +00:00
(proc (turn: var Turn; group: ObserverGroup; vs: seq[Value]) =
if group.cachedCaptures.change(vs, -1) == cdPresentToAbsent:
for (observer, captureMap) in group.observers.pairs:
retract(observer.target, turn, captureMap[vs])
captureMap.del(vs)))
else: discard
proc continuationNoop(c: Continuation; v: Value) = discard
proc leafNoop(l: Leaf; v: Value) = discard
2022-03-10 23:30:07 +00:00
proc add*(index: var Index; turn: var Turn; v: Assertion): bool =
adjustAssertion(index, turn, v, +1)
proc remove*(index: var Index; turn: var Turn; v: Assertion): bool =
adjustAssertion(index, turn, v, -1)
2022-03-10 23:30:07 +00:00
proc deliverMessage*(index: Index; turn: var Turn; v: Value) =
proc observersCb(turn: var Turn; group: ObserverGroup; vs: seq[Value]) =
for observer in group.observers.keys: message(turn, observer, vs)
index.root.modify(turn, messageEvent, v, continuationNoop, leafNoop, observersCb)