Documentation in the code
This commit is contained in:
parent
2eb8822c56
commit
289a7351df
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue