Documentation in the code

This commit is contained in:
Tony Garnock-Jones 2014-06-10 13:54:10 -04:00
parent 2eb8822c56
commit 289a7351df
3 changed files with 460 additions and 85 deletions

View File

@ -1,4 +1,5 @@
#lang racket/base
;; Core implementation of network actors and Network Calculus (NC) communication API.
(require racket/set)
(require racket/match)
@ -39,51 +40,121 @@
log-events-and-actions?
routing-implementation)
;; A PID is a number uniquely identifying a Process within a World.
;; Note that PIDs are only meaningful within the context of their
;; World: they are not global Process identifiers.
;; (Parameterof (Listof PID))
;; Path to the active leaf in the process tree. The car end is the
;; leaf; the cdr end, the root. Used for debugging purposes.
(define pid-stack (make-parameter '()))
;; (Parameterof Boolean)
;; True when Worlds should log their internal actions for use in
;; debugging.
(define log-events-and-actions? (make-parameter #f))
;; TODO: support +Inf.0 as a level number
;; Events
;; An Event is a communication from a World to a Process contained
;; within it. One of
;; - (routing-update Gestalt), description of change in the sender's interests/subscriptions
;; - (message Any Nat Boolean), a (multicast, in general) message sent by an actor
;; A message's (feedback?) field is #f when it is a message
;; originating from an advertiser/publisher and terminating with a
;; subscriber, and #t in the opposite case.
(struct routing-update (gestalt) #:prefab)
(struct message (body meta-level feedback?) #:prefab)
;; Actions (in addition to Events)
;; (spawn is just process)
;; An Action is a communication from a Process to its containing
;; World, instructing the World to take some action on the Process's
;; behalf. One of
;; - an Event: change in the Process's interests, or message from the Process
;; - a Process: instruction to spawn a new process as described
;; - (quit): instruction to terminate the sending process
(struct quit () #:prefab)
;; Intra-world signalling
;; A PendingEvent is a description of a set of Events to be
;; communicated to a World's Processes. In naïve implementations of
;; NC, there is no distinction between Events and PendingEvents; here,
;; we must ensure that the buffering delay doesn't affect the Gestalts
;; communicated in routing-update Events, so a special record is used
;; to capture the appropriate Gestalt environment.
;; - (pending-routing-update Gestalt Gestalt (Option PID))
(struct pending-routing-update (aggregate affected-subgestalt known-target) #:prefab)
;; Actors and Configurations
;; A Process (a.k.a. Actor) describes a single actor in a World.
;; - (process Gestalt Behavior Any)
;; The Gestalt describes the current interests of the Process: either
;; those it was spawned with, or the most recent interests from a
;; routing-update Action.
(struct process (gestalt behavior state) #:transparent)
(struct world (next-pid ;; Natural, PID for next-spawned process
event-queue ;; Queue of Event
runnable-pids ;; Set of PIDs
partial-gestalt ;; Gestalt from local processes only; maps to PID
full-gestalt ;; Union of partial-gestalt and downward-gestalt
process-table ;; Hash from PID to Process
downward-gestalt ;; Gestalt representing interests of outside world
process-actions ;; Queue of (cons PID Action)
;; A World (a.k.a Configuration) is the state of an actor representing
;; a group of communicating Processes. The term is also used from time
;; to time to denote the actor having a World as its state and
;; world-handle-event as its Behavior.
(struct world (next-pid ;; PID, for next-spawned process
pending-event-queue ;; (Queueof PendingEvent)
runnable-pids ;; (Setof PID), non-inert processes
partial-gestalt ;; Gestalt, from local processes only; maps to PID
full-gestalt ;; Gestalt, union of partial- and downward-gestalts
process-table ;; (HashTable PID Process)
downward-gestalt ;; Gestalt, representing interests of outside world
process-actions ;; (Queueof (Pairof PID Action))
) #:transparent)
;; Behavior : maybe event * state -> transition
;; A Behavior is a ((Option Event) Any -> Transition): a function
;; mapping an Event (or, in the #f case, a poll signal) and a
;; Process's current state to a Transition.
;;
;; A Transition is either
;; - #f, a signal from a Process that it is inert and need not be
;; scheduled until some Event relevant to it arrives; or,
;; - a (transition Any (Constreeof Action)), a new Process state to
;; be held by its World and a sequence of Actions for the World
;; to take on the transitioning Process's behalf.
(struct transition (state actions) #:transparent)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Protocol and utilities
;; sub : Pattern [#:meta-level Nat] [#:level Nat] -> Gestalt
;; pub : Pattern [#:meta-level Nat] [#:level Nat] -> Gestalt
;;
;; Construct atomic Gestalts representing subscriptions/advertisements
;; matching the given pattern, at the given meta-level and level.
;; These are frequently used in combination with gestalt-union when
;; building spawn and routing-update Actions.
(define (sub p #:meta-level [ml 0] #:level [l 0]) (simple-gestalt #f p l ml))
(define (pub p #:meta-level [ml 0] #:level [l 0]) (simple-gestalt #t p l ml))
;; Gestalt Any -> Boolean
;; True iff m falls within the set of messages represented by the Gestalt.
(define (gestalt-accepts? g m)
(match-define (message b ml f?) m)
(not (set-empty? (gestalt-match-value g b ml f?))))
;; Behavior Any [Gestalt] -> Action
;; Constructs a spawn Action for a new process with the given behavior
;; and state. If a Gestalt is supplied, the new process will begin its
;; existence with the corresponding subscriptions/advertisements/
;; conversational-responsibilities.
(define (spawn behavior state [gestalt (gestalt-empty)]) (process gestalt behavior state))
;; send : Any [#:meta-level Nat] -> Action
;; feedback : Any [#:meta-level Nat] -> Action
;;
;; Each constructs an Action that will deliver a body to peers at the
;; given meta-level. (send) constructs messages that will be delivered
;; to subscribers; (feedback), to advertisers.
(define (send body #:meta-level [ml 0]) (message body ml #f))
(define (feedback body #:meta-level [ml 0]) (message body ml #t))
;; Action* -> Action
;; Constructs an action which causes the creation of a new World
;; Process. The given actions will be taken by a primordial process
;; running in the context of the new World.
(define (spawn-world . boot-actions)
(spawn world-handle-event
(enqueue-actions (world 0
@ -97,25 +168,48 @@
-1
boot-actions)))
;; Any -> Boolean; type predicates for Event and Action respectively.
(define (event? x) (or (routing-update? x) (message? x)))
(define (action? x) (or (event? x) (process? x) (quit? x)))
;; (Any -> Transition) Transition -> Transition
;; A kind of monad-ish bind operator: threads the state in t0 through
;; k, appending the action sequence from t0 with that from the result
;; of calling k.
;; TODO: sort out exactly how #f should propagate here
(define (transition-bind k t0)
(match-define (transition state0 actions0) t0)
(match (k state0)
[(transition state1 actions1) (transition state1 (cons actions0 actions1))]
[#f t0]))
;; Transition (Any -> Transition)* -> Transition
;; Each step is a function from state to Transition. The state in t0
;; is threaded through the steps; the action sequences are appended.
(define (sequence-transitions t0 . steps)
(foldl transition-bind t0 steps))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Trigger guards
;; Trigger-guards only pass through routing updates if there has been
;; a change.
;; TriggerGuards wrap process Behavior and state, only passing through
;; routing-update Events to their contained process behavior/state if
;; there has been a change. All other Events go straight through.
;;
;; - (trigger-guard Gestalt Behavior Any)
;;
;; The structural similarity to Processes is meaningful: a Process
;; describes the current interests of the Process, as well as its
;; behavior. A TriggerGuard describes the current interests of the
;; Process's *environment*, and doesn't bother passing on a
;; routing-update unless the change is non-zero.
(struct trigger-guard (gestalt handler state) #:transparent)
;; Behavior :> (Option Event) TriggerGuard -> Transition
;; Inspects the given event: if it is a routing update, the contained
;; Gestalt is compared to the TriggerGuard's record of the previous
;; Gestalt from the environment, and only if it is different is it
;; passed on.
(define (trigger-guard-handle e s0)
(match-define (trigger-guard old-gestalt handler old-state) s0)
(define (deliver s)
@ -131,6 +225,8 @@
(deliver (struct-copy trigger-guard s0 [gestalt new-gestalt])))]
[_ (deliver s0)]))
;; Process -> Process
;; Wraps a Process in a TriggerGuard.
(define (trigger-guard-process p)
(match-define (process _ b s) p)
(struct-copy process p [behavior trigger-guard-handle] [state (trigger-guard #f b s)]))
@ -139,42 +235,50 @@
;; World implementation
;; Each time a world is handed an event from its environment, it:
;; 1. dispatches events
;; 1. dispatches PendingEvents
;; a. removing them one-at-a-time from the queue
;; b. dispatching them to processes
;; c. updating process states and accumulating actions in the queue
;; b. converting them to Events and dispatching them to processes
;; c. updating process states and accumulating Actions in the queue
;; d. any process that returned non-#f is considered "non-idle" for step 3.
;; 2. performs actions
;; 2. performs Actions
;; a. removing them one-at-a-time from the queue
;; b. interpreting them
;; c. updating world state and accumulating events in the queue
;; c. updating World state and accumulating PendingEvents in the queue
;; 3. steps non-idle processes
;; a. runs through the set of processes accumulated in 1d. above
;; a. runs through the runnable-pids set of processes accumulated in 1d. above
;; b. any process that returned non-#f is put in the "non-idle" set for next time
;; 4. yields updated world state and world actions to environment.
;; 4. yields updated World state and world Actions to the environment.
;;
;; Note that routing-update actions are queued internally as
;; pending-routing-update structures, in order to preserve and
;; communicate transient gestalt states to processes. In addition, the
;; Note that routing-update Actions are queued as
;; pending-routing-update structures in order to preserve and
;; communicate transient Gestalt states to Processes. In addition, the
;; known-target field of a pending-routing-update structure is used to
;; provide NC's initial gestalt signal to a newly-spawned process.
;; provide NC's initial Gestalt signal to a newly-spawned process.
;;
;; TODO: should step 3 occur before step 1?
;; World PID (Constreeof Action) -> World
;; Stores actions taken by PID for later interpretation.
(define (enqueue-actions w pid actions)
(struct-copy world w
[process-actions (queue-append-list (world-process-actions w)
(filter-map (lambda (a) (and (action? a) (cons pid a)))
(flatten actions)))]))
;; World -> Boolean
;; True if the World has no further reductions it can take.
;;
;; The code is written to maintain the runnable-pids set carefully, to
;; ensure we can locally decide whether we're inert or not without
;; having to search the whole deep process tree.
(define (inert? w)
(and (queue-empty? (world-event-queue w))
(and (queue-empty? (world-pending-event-queue w))
(queue-empty? (world-process-actions w))
(set-empty? (world-runnable-pids w))))
;; Event PID Process -> Transition
;; Delivers the event to the process, catching any exceptions it
;; throws and converting them to quit Actions.
(define (deliver-event e pid p)
(parameterize ((pid-stack (cons pid (pid-stack))))
(when (and (log-events-and-actions?) e)
@ -200,9 +304,16 @@
(log-error "Process ~a returned non-#f, non-transition: ~v" (pid-stack) x)
(transition (process-state p) (list (quit)))]))))
;; World PID -> World
;; Marks the given PID as not-provably-inert.
(define (mark-pid-runnable w pid)
(struct-copy world w [runnable-pids (set-add (world-runnable-pids w) pid)]))
;; PID Transition World -> World
;; Examines the given Transition, updating PID's Process's state and
;; enqueueing Actions for later interpretation. When the Transition is
;; non-#f, PID's Process may wish to take further internal reductions,
;; so we mark it as runnable.
(define (apply-transition pid t w)
(match t
[#f w]
@ -221,41 +332,64 @@
(struct-copy process p [state new-state])))))
(enqueue-actions (mark-pid-runnable w pid) pid new-actions))]))
(define (enqueue-event e w)
(struct-copy world w [event-queue (enqueue (world-event-queue w) e)]))
;; PendingEvent World -> World
;; Enqueue a PendingEvent for later interpretation and dispatch.
(define (enqueue-pending-event e w)
(struct-copy world w [pending-event-queue (enqueue (world-pending-event-queue w) e)]))
;; World -> Transition
;; Examines all queued actions, interpreting them, updating World
;; state, and possibly causing the World to send Actions for
;; interpretation to its own containing World in turn.
(define (perform-actions w)
(for/fold ([t (transition (struct-copy world w [process-actions (make-queue)]) '())])
((entry (in-list (queue->list (world-process-actions w)))))
(match-define (cons pid a) entry)
(transition-bind (perform-action pid a) t)))
(define (dispatch-events w)
(transition (for/fold ([w (struct-copy world w [event-queue (make-queue)])])
((e (in-list (queue->list (world-event-queue w)))))
(dispatch-event e w))
;; World -> Transition
;; Interprets queued PendingEvents, delivering resulting Events to Processes.
(define (dispatch-pending-events w)
(transition (for/fold ([w (struct-copy world w [pending-event-queue (make-queue)])])
((e (in-list (queue->list (world-pending-event-queue w)))))
(dispatch-pending-event e w))
'()))
;; PID World (Process -> Process) -> World
;; Extracts a Process by PID, maps fp over it, and stores the result back into the table.
(define (transform-process pid w fp)
(define pt (world-process-table w))
(match (hash-ref pt pid)
[#f w]
[p (struct-copy world w [process-table (hash-set pt pid (fp p))])]))
;; World -> World
;; Updates the World's cached copy of the union of its partial- and downward-gestalts.
(define (update-full-gestalt w)
(struct-copy world w [full-gestalt
(gestalt-union (world-partial-gestalt w) (world-downward-gestalt w))]))
;; World Gestalt (Option PID) -> World
;; Constructs and enqueues a PendingEvent describing a change to the
;; World's gestalt falling within the relevant-gestalt *subset* of it.
(define (issue-local-routing-update w relevant-gestalt known-target)
(enqueue-event (pending-routing-update (world-full-gestalt w)
relevant-gestalt
known-target)
w))
(enqueue-pending-event (pending-routing-update (world-full-gestalt w)
relevant-gestalt
known-target)
w))
;; World Gestalt (Option PID) -> Transition
;; Communicates a change in World's gestalt falling within the
;; relevant-gestalt *subset* of it both to local Processes and to the
;; World's own containing World.
(define (issue-routing-update w relevant-gestalt known-target)
(transition (issue-local-routing-update w relevant-gestalt known-target)
(routing-update (drop-gestalt (world-partial-gestalt w)))))
;; World Gestalt Gestalt (Option PID) -> Transition
;; Communicates a change in the World gestalt corresponding to a
;; change in a single Process's gestalt. The old-gestalt is what the
;; Process used to be interested in; new-gestalt is its new interests.
(define (apply-and-issue-routing-update w old-gestalt new-gestalt known-target)
(define new-partial
(gestalt-union (gestalt-erase-path (world-partial-gestalt w) old-gestalt) new-gestalt))
@ -263,6 +397,10 @@
(gestalt-union old-gestalt new-gestalt)
known-target))
;; PID Action -> World -> Transition
;; Interprets a single Action performed by PID, updating World state
;; and possibly causing the World to take externally-visible Actions
;; as a result.
(define ((perform-action pid a) w)
(match a
[(? process? new-p)
@ -300,10 +438,12 @@
(transition w '()))]
[(message body meta-level feedback?)
(if (zero? meta-level)
(transition (enqueue-event a w) '())
(transition (enqueue-pending-event a w) '())
(transition w (message body (- meta-level 1) feedback?)))]))
(define (dispatch-event e w)
;; PendingEvent World -> World
;; Interprets a PendingEvent, delivering the resulting Event(s) to Processes.
(define (dispatch-pending-event e w)
(match e
[(message body meta-level feedback?)
(define pids (gestalt-match-value (world-partial-gestalt w) body meta-level feedback?))
@ -320,7 +460,14 @@
[p (define g1 (gestalt-filter g (process-gestalt p)))
(apply-transition pid (deliver-event (routing-update g1) pid p) w)]))]))
;; This is roughly the "schedule" rule of the calculus.
;; World -> Transition
;; Polls the non-provably-inert processes identified by the
;; runnable-pids set (by sending them #f instead of an Event).
;;
;; N.B.: We also effectively compute whether this entire World is
;; inert here.
;;
;; This is roughly the "schedule" rule of the Network Calculus.
(define (step-children w)
(define runnable-pids (world-runnable-pids w))
(if (set-empty? runnable-pids)
@ -331,14 +478,20 @@
(if (not p) w (apply-transition pid (deliver-event #f pid p) w)))
'()))) ;; world needs another check to see if more can happen.
;; Behavior :> (Option Event) World -> Transition
;; World's behavior function. Lifts and dispatches an incoming event
;; to contained Processes.
(define (world-handle-event e w)
(if (or e (not (inert? w)))
(sequence-transitions (transition (inject-event e w) '())
dispatch-events
dispatch-pending-events
perform-actions
(lambda (w) (or (step-children w) (transition w '()))))
(step-children w)))
;; Event World -> World
;; Translates an event from the World's container into PendingEvents
;; suitable for its own contained Processes.
(define (inject-event e w)
(match e
[#f w]
@ -350,6 +503,8 @@
(gestalt-union old-downward new-downward)
#f)]
[(message body meta-level feedback?)
(enqueue-event (message body (+ meta-level 1) feedback?) w)]))
(enqueue-pending-event (message body (+ meta-level 1) feedback?) w)]))
;; Symbol
;; Describes the routing implementation, for use in profiling, debugging etc.
(define routing-implementation 'fastrouting)

View File

@ -29,12 +29,21 @@
gestalt->jsexpr
jsexpr->gestalt)
;; A Gestalt is a (gestalt (Listof (Listof (Pairof Matcher Matcher)))),
;; representing the total interests of a process or group of
;; processes.
;; A Gestalt is a (gestalt (Listof Metalevel)), representing the total
;; interests of a process or group of processes at all metalevels and
;; levels.
;;
;; The outer list has an entry for each active metalevel, starting
;; with metalevel 0 in the car.
;; A Level is a (Pairof Matcher Matcher), representing active
;; subscriptions and advertisements at a particular level and
;; metalevel.
;;
;; A Metalevel is a (Listof Level), representing all Levels (ordered
;; by level number) at a given metalevel.
;;
;; --
;;
;; The outer list of a Gestalt has an entry for each active metalevel,
;; starting with metalevel 0 in the car.
;;
;; The middle list has an entry for each active level within its
;; metalevel, starting with level 0 in the car.
@ -44,6 +53,7 @@
;;
;; Each of the Matchers maps to (NonemptySetof PID).
;;
;; --
;;
;; "... a few standardised subsystems, identical from citizen to
;; citizen. Two of these were channels for incoming data — one for
@ -59,44 +69,69 @@
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; (Listof X) Nat [-> X] -> X
(define (safe-list-ref xs n [fail-thunk (lambda () (error 'safe-list-ref "No such index ~v" n))])
(let loop ((xs xs) (n n))
(match xs
['() (fail-thunk)]
[(cons x xs) (if (zero? n) x (loop xs (- n 1)))])))
;; (Listof X) -> (Listof X)
;; ->> HISTORICAL IRONY <<-
(define (safe-cdr xs)
(if (null? xs)
'()
(cdr xs)))
;; X -> X (Listof X) -> (Listof X)
;; Conses a onto d, unless d is '() and a is the special unit value.
(define ((guarded-cons unit) a d)
(if (and (null? d) (equal? a unit))
'()
(cons a d)))
;; Level
;; The empty level, matching no messages.
(define empty-level '(#f . #f))
;; The empty metalevel, matching no messages at any level.
(define empty-metalevel '())
;; Level Metalevel -> Metalevel
;; Only adds to its second argument if its first is nonempty.
(define cons-level (guarded-cons empty-level))
;; Metalevel (Listof Metalevel) -> (Listof Metalevel).
;; Only adds to its second argument if its first is nonempty.
(define cons-metalevel (guarded-cons empty-metalevel))
;; Gestalt × Value × Natural × Boolean → (Setof PID)
;; Retrieves those PIDs that have active subscriptions/advertisements
;; covering the given message at the given metalevel.
(define (gestalt-match-value g body metalevel is-feedback?)
(define levels (safe-list-ref (gestalt-metalevels g) metalevel (lambda () empty-metalevel)))
(for/fold [(acc (set))] [(level (in-list levels))]
(define matcher ((if is-feedback? cdr car) level)) ;; feedback targets advertisers/publishers
(set-union (matcher-match-value matcher body) acc)))
;; (Listof Projection) -> CompiledProjection
;; For use with gestalt-project.
(define (compile-gestalt-projection* specs)
(compile-projection* specs))
;; Projection* -> CompiledProjection
;; For use with gestalt-project.
(define (compile-gestalt-projection . specs)
(compile-gestalt-projection* specs))
;; CompiledProjection
;; Represents a projection that simply captures the entirety of the
;; projected matcher; useful as an identity projection.
(define capture-everything-projection (compile-gestalt-projection (?!)))
;; Gestalt × Natural × Natural × Boolean × CompiledSpec → Matcher
;; Gestalt × Natural × Natural × Boolean × CompiledProjection → Matcher
;; Retrieves the Matcher within g at the given metalevel and level,
;; representing subscriptions or advertisements, projected by capture-spec.
(define (gestalt-project g metalevel level get-advertisements? capture-spec)
(define levels (safe-list-ref (gestalt-metalevels g) metalevel (lambda () empty-metalevel)))
(define matcher ((if get-advertisements? cdr car)
@ -105,30 +140,57 @@
matcher
(matcher-project matcher capture-spec)))
;; Gestalt -> Gestalt
;; Discards the 0th metalevel, renumbering others appropriately.
;; Used to map a Gestalt from a World to Gestalts of its containing World.
(define (drop-gestalt g)
(gestalt (safe-cdr (gestalt-metalevels g))))
;; Gestalt -> Gestalt
;; Adds a fresh empty 0th metalevel, renumbering others appropriately.
;; Used to map Gestalt from a World's container to the World's own Gestalt.
(define (lift-gestalt g)
(gestalt (cons-metalevel empty-metalevel (gestalt-metalevels g))))
;; Nat X (Listof X) -> (Listof X)
;; Prepends n references to x to xs.
(define (prepend n x xs)
(if (zero? n)
xs
(cons x (prepend (- n 1) x xs))))
;; Boolean Pattern Nat Nat -> Gestalt
;; Compiles p and embeds it at the appropriate level and metalevel
;; within a Gestalt. Used by (pub) and (sub) to construct "atomic"
;; Gestalts.
(define (simple-gestalt is-adv? p level metalevel)
(define m (pattern->matcher #t p))
(gestalt (prepend metalevel empty-metalevel
(list (prepend level empty-level
(list (if is-adv? (cons #f m) (cons m #f))))))))
;; -> Gestalt
;; The empty gestalt.
(define (gestalt-empty) (gestalt '()))
;; Gestalt -> Boolean
;; True iff the gestalt matches no messages.
;; TODO: our invariants should ensure that (gestalt-empty? g) iff (equal? g (gestalt '())).
;; Make sure this actually is true.
(define (gestalt-empty? g)
(andmap (lambda (ml)
(andmap (lambda (l) (and (matcher-empty? (car l)) (matcher-empty? (cdr l)))) ml))
(gestalt-metalevels g)))
;; map-zip: ((U 'right-longer 'left-longer) (Listof X) -> (Listof Y))
;; (X X -> Y)
;; (Y (Listof Y) -> (Listof Y))
;; (Listof X)
;; (Listof X)
;; -> (Listof Y)
;; Horrific map-like function that isn't quite as picky as map about
;; ragged input lists. The imbalance-handler is used to handle ragged
;; inputs.
(define (map-zip imbalance-handler item-handler gcons ls1 ls2)
(let walk ((ls1 ls1) (ls2 ls2))
(match* (ls1 ls2)
@ -138,14 +200,20 @@
[((cons l1 ls1) (cons l2 ls2))
(gcons (item-handler l1 l2) (walk ls1 ls2))])))
(define (gestalt-combine g1 g2 imbalance-handler matcher-pair-combiner)
;; Gestalt Gestalt (...->...) (Level Level -> Level) -> Gestalt
;; Combine two gestalts with the given level-combiner.
;; The type of imbalance-handler is awkward because of the punning.
(define (gestalt-combine g1 g2 imbalance-handler level-combiner)
(gestalt (map-zip imbalance-handler
(lambda (ls1 ls2)
(map-zip imbalance-handler matcher-pair-combiner cons-level ls1 ls2))
(map-zip imbalance-handler level-combiner cons-level ls1 ls2))
cons-metalevel
(gestalt-metalevels g1)
(gestalt-metalevels g2))))
;; Gestalt Gestalt (...->...) (Matcher Matcher -> Matcher) -> Gestalt
;; Combines g1 and g2, giving subs/subs and advs/advs from g1 and g2
;; to the matcher-combiner.
(define (gestalt-combine-straight g1 g2 imbalance-handler matcher-combiner)
(gestalt-combine g1 g2
imbalance-handler
@ -153,6 +221,8 @@
(cons (matcher-combiner (car sa1) (car sa2))
(matcher-combiner (cdr sa1) (cdr sa2))))))
;; Gestalt* -> Gestalt
;; Computes the union of its arguments.
(define (gestalt-union . gs)
(if (null? gs)
(gestalt-empty)
@ -161,8 +231,11 @@
[(list g) g]
[(cons g rest) (gestalt-union1 g (walk rest))]))))
;; Gestalt Gestalt -> Gestalt
;; Computes the union of its arguments.
(define (gestalt-union1 g1 g2) (gestalt-combine-straight g1 g2 (lambda (side x) x) matcher-union))
;; Gestalt Gestalt -> Gestalt
;; View on g1 from g2's perspective.
(define gestalt-filter
(let ()
@ -196,6 +269,7 @@
(parameterize ((matcher-intersect-successes (lambda (v1 v2) v1)))
(gestalt (filter-metalevels (gestalt-metalevels g1) (gestalt-metalevels g2)))))))
;; Gestalt Gestalt -> (Setof PID)
;; Much like gestalt-filter, takes a view on gestalt g1 from g2's
;; perspective. However, instead of returning the filtered g1, returns
;; just the set of values in the g2-map that were overlapped by some
@ -231,16 +305,23 @@
(matcher-match-matcher-unit (set)))
(match-metalevels (gestalt-metalevels g1) (gestalt-metalevels g2) (set))))))
;; Gestalt Gestalt -> Gestalt
;; Erases the g2-subset of g1 from g1, yielding the result.
(define (gestalt-erase-path g1 g2)
(gestalt-combine-straight g1 g2
erase-imbalance-handler
matcher-erase-path))
;; (U 'right-longer 'left-longer) (Listof X) -> (Listof X)
;; Asymmetric imbalance handler suitable for use in subtraction operations.
(define (erase-imbalance-handler side x)
(case side
[(left-longer) x]
[(right-longer) '()]))
;; Gestalt (Nat Nat Level -> Level) -> Gestalt
;; Maps f over all levels in g, passing f the metalevel number, the
;; level number, and the level itself, in that order.
(define (gestalt-transform g f)
(gestalt (let loop-outer ((mls (gestalt-metalevels g)) (i 0))
(cond [(null? mls) '()]
@ -251,16 +332,24 @@
(loop-inner (cdr ls) (+ j 1)))]))
(loop-outer (cdr mls) (+ i 1)))]))))
;; Gestalt (Matcher -> Matcher) -> Gestalt
;; Maps f over all matchers in g.
(define (gestalt-matcher-transform g f)
(gestalt-transform g (lambda (i j p) (cons (f (car p)) (f (cdr p))))))
;; Gestalt -> GestaltSet
;; Blurs the distinctions between mapped-to processes in g.
(define (strip-gestalt-label g)
(gestalt-matcher-transform g (lambda (m) (matcher-relabel m (lambda (v) #t)))))
;; GestaltSet -> Gestalt
;; Relabels g so that all matched keys map to (set pid).
(define (label-gestalt g pid)
(define pidset (set pid))
(gestalt-matcher-transform g (lambda (m) (matcher-relabel m (lambda (v) pidset)))))
;; Gestalt [OutputPort] -> Void
;; Pretty-prints g on port.
(define (pretty-print-gestalt g [port (current-output-port)])
(if (gestalt-empty? g)
(fprintf port "EMPTY GESTALT\n")
@ -272,9 +361,13 @@
(when subs (fprintf port " - subs:") (pretty-print-matcher subs port #:indent 9))
(when advs (fprintf port " - advs:") (pretty-print-matcher advs port #:indent 9)))))))
;; Gestalt -> String
;; Returns a string containing the pretty-printing of g.
(define (gestalt->pretty-string g)
(with-output-to-string (lambda () (pretty-print-gestalt g))))
;; Gestalt [(Value -> JSExpr)] -> JSExpr
;; Serializes a gestalt to a JSON expression.
(define (gestalt->jsexpr g [success->jsexpr (lambda (v) #t)])
(list "gestalt" (for/list [(ls (in-list (gestalt-metalevels g)))]
(for/list [(l (in-list ls))]
@ -282,6 +375,8 @@
(list (matcher->jsexpr subs success->jsexpr)
(matcher->jsexpr advs success->jsexpr))))))
;; JSExpr [(JSExpr -> Value)] -> Gestalt
;; Deserializes a gestalt from a JSON expression.
(define (jsexpr->gestalt j [jsexpr->success (lambda (v) #t)])
(match j
[(list "gestalt" mlsj)

View File

@ -1,4 +1,6 @@
#lang racket/base
;; Implements a nested-word-like automaton mapping sets of messages to sets of other values.
;; A kind of "regular-expression"-keyed multimap.
(require racket/set)
(require racket/match)
@ -40,7 +42,10 @@
matcher-match-matcher-unit
matcher-project-success)
;; TODO: perhaps avoid the parameters on the fast-path, if they are causing measurable slowdown.
;; TODO: perhaps avoid the parameters on the fast-path, if they are
;; causing measurable slowdown.
;; TODO: should these even be parameterizable?
(define matcher-union-successes (make-parameter (lambda (v1 v2)
(match* (v1 v2)
[(#t v) v]
@ -56,6 +61,7 @@
(define matcher-match-matcher-unit (make-parameter (cons (set) (set))))
(define matcher-project-success (make-parameter values))
;; Constructs a structure type and a singleton instance of it.
(define-syntax-rule (define-singleton-struct singleton-name struct-name print-representation)
(begin
(struct struct-name ()
@ -64,41 +70,47 @@
(lambda (v port mode) (display print-representation port)))
(define singleton-name (struct-name))))
;; Unicode angle brackets: 〈, 〉
;; A Sigma is, roughly, a token in a value being matched. It is one of:
;; - a struct-type, signifying the start of a struct.
;; - start-of-list, signifying the start of a list.
;; - start-of-vector, signifying the start of a vector.
;; - improper-list-marker, signifying the transition into the cdr position of a pair
;; - end-of-sequence, signifying the notional close-paren at the end of a compound.
;; - SOL, signifying the start of a list.
;; - SOV, signifying the start of a vector.
;; - ILM, signifying the transition into the cdr position of a pair
;; - EOS, signifying the notional close-paren at the end of a compound.
;; - any other value, representing itself.
;; N.B. hash-tables cannot be Sigmas at present.
(define-singleton-struct SOL start-of-list "<")
(define-singleton-struct SOV start-of-vector "<vector")
(define-singleton-struct ILM improper-list-marker "|")
(define-singleton-struct EOS end-of-sequence ">")
;; A Pattern is an atom, the special wildcard value, an
;; embedded-matcher, or a Racket compound (struct, pair, or vector)
;; containing Patterns.
;; A Pattern is an atom, the special wildcard value (?), an
;; (embedded-matcher Matcher), or a Racket compound (struct, pair, or
;; vector) containing Patterns.
(define-singleton-struct ? wildcard "") ;; alternative printing: ¿
(struct embedded-matcher (matcher) #:transparent)
;; A Projection is an atom, the special wildcard value (?), a (capture
;; Pattern), or a Racket compound (struct, pair, or vector) containing
;; Projections. A Projection is much like a Pattern, but may include
;; captures, and may not include embedded matchers.
;;
;; When projecting a matcher, the capturing wildcard can be used.
(struct capture (pattern) #:transparent)
;; Capture with default of wildcard.
;; [Pattern] -> Projection
;; Construct a capture with default pattern of wildcard.
(define (?! [pattern ?]) (capture pattern))
;; Compiled projections include start-of-capture and end-of-capture
;; elements.
;; A CompiledProjection is a (Listof (U Sigma ? SOC EOC)). Compiled
;; projections include start-of-capture and end-of-capture elements.
(define-singleton-struct SOC start-of-capture "{{")
(define-singleton-struct EOC end-of-capture "}}")
;; A Matcher is either
;; - #f, indicating no further matches possible
;; - a (success Any), representing a successful match (if the end of the input has been reached)
;; - a Hashtable mapping (Sigma or wildcard) to Matcher
;; - a (success Any), representing a successful match (if the end of
;; the input has been reached)
;; - a Hashtable mapping (U Sigma ?) to Matcher
;; - a (wildcard-sequence Matcher)
;; If, in a hashtable matcher, a wild key is present, it is intended
;; to catch all and ONLY those keys not otherwise present in the
@ -106,6 +118,8 @@
(struct success (value) #:transparent)
(struct wildcard-sequence (matcher) #:transparent)
;; Any -> Boolean
;; Predicate recognising Matchers. Expensive!
(define (matcher? x)
(or (eq? x #f)
(success? x)
@ -114,15 +128,60 @@
(for/and ([v (in-hash-values x)])
(matcher? v)))))
;; -> Matcher
;; The empty Matcher
(define (matcher-empty) #f)
;; Matcher -> Boolean
;; True iff the argument is the empty matcher
(define (matcher-empty? r) (not r))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Smart constructors & accessors
;;
;; Maintain this INVARIANT: A Matcher is non-empty iff it contains
;; some keys that map to some Values. Essentially, don't bother
;; prepending tokens to a Matcher unless there's some possibility it
;; can map to one or more Values.
;; (Option Any) -> Matcher
;; If the argument is #f, returns the empty matcher; otherwise, a success Matcher.
(define (rsuccess v) (and v (success v)))
;; (U Sigma ?) Matcher -> Matcher
;; Prepends e to r, if r is non-empty.
(define (rseq e r) (if (matcher-empty? r) r (hash e r)))
;; Matcher -> Matcher
;; Prepends the wildcard pseudo-Sigma to r, if r is non-empty.
(define (rwild r) (rseq ? r))
;; Matcher -> Matcher
;; If r is non-empty, returns a matcher that consumes input up to and
;; including EOS, then continuing with r.
(define (rwildseq r) (if (matcher-empty? r) r (wildcard-sequence r)))
;; Matcher (U Sigma ?) -> Matcher
;; r must be a hashtable matcher. Retrieves the continuation after
;; accepting key. If key is absent, returns the failing/empty matcher.
(define (rlookup r key)
(hash-ref r key (lambda () #f)))
;; Matcher (U Sigma ?) Matcher -> Matcher
;; Updates (installs or removes) a continuation in a Matcher. r must
;; be either #f or a hashtable matcher.
(define (rupdate r key k)
(if (matcher-empty? k)
(and r
(let ((r1 (hash-remove r key)))
(if (zero? (hash-count r1))
#f
r1)))
(hash-set (or r (hash)) key k)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Pattern compilation
;; Any -> Boolean
;; Racket objects are structures, so we reject them explicitly for
;; now, leaving them opaque to unification.
@ -130,10 +189,14 @@
(and (struct? x)
(not (object? x))))
;; (A B -> B) B (Vectorof A) -> B
(define (vector-foldr kons knil v)
(for/fold [(acc knil)] [(elem (in-vector v (- (vector-length v) 1) -1 -1))]
(kons elem acc)))
;; Value (Listof Pattern) -> Matcher
;; Compiles a sequence of patterns into a matcher that accepts input
;; matching that sequence, yielding v.
(define (pattern->matcher* v ps)
(define (walk-list ps acc)
(match ps
@ -152,43 +215,42 @@
(when skipped? (error 'pattern->matcher "Cannot reflect on struct instance ~v" p))
(define fs (cdr (vector->list (struct->vector p))))
(rseq t (foldr walk (rseq EOS acc) fs))]
;; TODO: consider options for treating hash tables as compounds rather than (useless) atoms
;; TODO: consider options for treating hash tables as compounds
;; rather than (useless) atoms
[(? hash?) (error 'pattern->matcher "Cannot match on hash tables at present")]
[other (rseq other acc)]))
(walk-list ps (rsuccess v)))
;; Value Pattern* -> Matcher
;; Convenience form of pattern->matcher*.
(define (pattern->matcher v . ps)
(pattern->matcher* v ps))
(define (rlookup r key)
(hash-ref r key (lambda () #f)))
(define (rupdate r key k)
(if (matcher-empty? k)
(and r
(let ((r1 (hash-remove r key)))
(if (zero? (hash-count r1))
#f
r1)))
(hash-set (or r (hash)) key k)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Matcher combinators
;; Sigma -> Boolean
;; True iff k represents the start of a compound datum.
(define (key-open? k)
(or (eq? k SOL)
(eq? k SOV)
(struct-type? k)))
;; Sigma -> Boolean
;; True iff k represents the end of a compound datum.
(define (key-close? k)
(eq? k EOS))
(define (key-normal? k)
(not (or (key-open? k)
(key-close? k))))
;; Matcher -> Matcher
;; Unrolls the implicit recursion in a wildcard-sequence.
;; Exploits the fact that (rwildseq r) === (matcher-union (rwild (rwildseq r)) (rseq EOS r)).
(define (expand-wildseq r)
(matcher-union (rwild (rwildseq r))
(rseq EOS r)))
;; Matcher Matcher -> Matcher
;; Computes the union of the multimaps passed in.
(define matcher-union
(let ()
(define (merge o1 o2)
@ -227,11 +289,15 @@
(rupdate acc key k)))
merge))
;; Hashtable Hashtable -> Hashtable
;; Returns the smaller of its arguments.
(define (smaller-hash h1 h2)
(if (< (hash-count h1) (hash-count h2))
h1
h2))
;; Matcher Matcher -> Matcher
;; Computes the intersection of the multimaps passed in.
(define matcher-intersect
(let ()
;; INVARIANT: re1 is a part of the original re1, and likewise for
@ -285,6 +351,7 @@
[(r #f) #f]
[(r1 r2) (walk r1 r2)]))))
;; Matcher Matcher -> Matcher
;; Removes re2's mappings from re1. Assumes re2 has previously been union'd into re1.
;; The combine-successes function should return #f to signal "no remaining success values".
(define matcher-erase-path
@ -350,6 +417,12 @@
[(#f r) (cofinite-pattern)]
[(r1 r2) (walk r1 r2)]))))
;; Matcher -> Matcher
;; Checks for redundant branches in its argument: when a matcher
;; contains only entries for (EOS -> (wildcard-sequence m')) and
;; (★ -> (wildcard-sequence m')), it is equivalent to
;; (wildcard-sequence m') itself. This is in a way the inverse of
;; expand-wildseq.
(define (collapse-wildcard-sequences m)
(match m
[(? hash? h)
@ -366,12 +439,23 @@
h)]
[other other]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Matching single keys into a multimap
;; (Listof Sigma) -> (Listof Sigma)
;; Hackish support for improper lists. TODO: revisit
;; Converts an improper list into a proper one with ILM in the penultimate position.
(define (transform-list-value xs)
(match xs
['() '()]
[(cons x xs) (cons x (transform-list-value xs))]
[other (cons ILM (cons other '()))]))
;; Matcher InputValue [Value] -> Value
;; Converts the nested structure v on-the-fly into a sequence of
;; Sigmas and runs them through the Matcher r. If v leads to a success
;; Matcher, returns the values contained in the success Matcher;
;; otherwise, returns failure-result.
(define (matcher-match-value r v [failure-result (set)])
(if (matcher-empty? r)
failure-result
@ -421,6 +505,12 @@
[#f (walk-wild rest stack)]
[k (walk rest stack k)])])]))))
;; Matcher Matcher -> Value
;;
;; Similar to matcher-match-value, but instead of a single key,
;; accepts a Matcher serving as *multiple* simultaneously-examined
;; keys. Returns the union of all successful values reached by the
;; probe.
(define matcher-match-matcher
(let ()
(define (walk re1 re2 acc)
@ -464,6 +554,11 @@
[(r1 r2) (walk r1 r2 (matcher-match-matcher-unit))]))))
;; Matcher × (Value → Matcher) → Matcher
;; Since Matchers accept *sequences* of input values, this appends two
;; matchers into a single matcher that accepts their concatenation.
;; Because matchers map inputs to values, the second matcher is
;; expressed as a function from success-values from the first matcher
;; to a second matcher.
(define (matcher-append m0 m-tail-fn)
(let walk ((m m0))
(match m
@ -475,6 +570,8 @@
(matcher-union acc (m-tail-fn (success-value v)))
(rupdate acc k (walk v))))])))
;; Matcher (Value -> (Option Value)) -> Matcher
;; Maps f over success values in m.
(define (matcher-relabel m f)
(let walk ((m m))
(match m
@ -483,6 +580,12 @@
[(wildcard-sequence m1) (rwildseq (walk m1))]
[(? hash?) (for/fold [(acc #f)] [((k v) (in-hash m))] (rupdate acc k (walk v)))])))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Projection
;; (Listof Projection) -> CompiledProjection
;; Compiles a sequence of projections into a single CompiledProjection
;; for use with matcher-project.
(define (compile-projection* ps)
(define (walk-list ps acc)
(match ps
@ -508,9 +611,13 @@
(walk-list ps '()))
;; Projection* -> CompiledProjection
;; Convenience form of compile-projection*.
(define (compile-projection . ps)
(compile-projection* ps))
;; Projection -> Pattern
;; Strips captures from its argument, returning an equivalent non-capturing pattern.
(define (projection->pattern p)
(let walk ((p p))
(match p
@ -523,12 +630,13 @@
(when skipped? (error 'projection->pattern "Cannot reflect on struct instance ~v" p))
(define fs (cdr (vector->list (struct->vector p))))
(apply (struct-type-make-constructor t) (map walk fs))]
;; TODO: consider options for treating hash tables as compounds rather than (useless) atoms
;; TODO: consider options for treating hash tables as compounds
;; rather than (useless) atoms
[(? hash?) (error 'projection->pattern "Cannot match on hash tables at present")]
[other other])))
;; Matcher × CompiledProjection [× (Value -> (Option Value))] → Matcher
;; The result matches a vector of length equal to the number of captures.
;; The result matches a sequence of inputs of length equal to the number of captures.
;; The project-success function should return #f to signal "no success values".
(define matcher-project
;; TODO: skip-nested, capture-nested, and various cases in walk all
@ -625,6 +733,10 @@
(lambda (m spec)
(walk #f m spec))))
;; (Listof Sigma) -> (Listof Sigma)
;; Hackish support for improper lists. TODO: revisit
;; Undoes the transformation of transform-list-value, converting
;; ILM-marked proper lists back into improper ones.
(define (untransform-list-value vs)
(match vs
['() '()]
@ -633,7 +745,9 @@
[(cons v vs) (cons v (untransform-list-value vs))]))
;; Matcher → (Option (Setof (Listof Value)))
;; Multiplies out unions. Returns #f if any dimension of m is infinite.
;; Extracts the "keys" in its argument multimap m, representing input
;; sequences as lists. Multiplies out unions. Returns #f if any
;; dimension of m is infinite.
(define matcher-key-set
(let ()
;; Matcher (Value Matcher -> (Setof Value)) -> (Option (Setof Value))
@ -697,10 +811,15 @@
(define vss (matcher-key-set m))
(and vss (for/set [(vs (in-set vss))] (car vs))))
;; struct-type -> Symbol
;; Extract just the name of the given struct-type.
(define (struct-type-name st)
(define-values (name x2 x3 x4 x5 x6 x7 x8) (struct-type-info st))
name)
;; Matcher [OutputPort] [#:indent Nat] -> Void
;; Pretty-prints the given matcher on the given port, with
;; second-and-subsequent lines indented by the given amount.
(define (pretty-print-matcher m [port (current-output-port)] #:indent [initial-indent 0])
(define (d x) (display x port))
(define (walk i m)
@ -735,6 +854,8 @@
(newline port)
m)
;; Matcher (Value -> JSExpr) -> JSExpr
;; Serializes a matcher to a JSON expression.
(define (matcher->jsexpr m success->jsexpr)
(let walk ((m m))
(match m
@ -752,11 +873,15 @@
[else k])
(walk v)))])))
;; String -> String
;; Undoes the encoding of struct-type names used in the JSON serialization of Matchers.
(define (deserialize-struct-type-name stn)
(define expected-paren-pos (- (string-length stn) 1))
(and (char=? (string-ref stn expected-paren-pos) #\()
(substring stn 0 expected-paren-pos)))
;; JSExpr (JSExpr -> Value) [String -> (Option struct-type)] -> Matcher
;; Deserializes a matcher from a JSON expression.
(define (jsexpr->matcher j jsexpr->success [struct-type-name->struct-type (lambda () #f)])
(let walk ((j j))
(match j