+ (read-port-map (make-hash-table))
+ (write-port-map (make-hash-table))
+ (except-port-map (make-hash-table))
+ (schedule (make-schedule))
+ (time (gettimeofday)))
+ ;; TODO: document arguments
+ "Make a fresh agenda."
+ (make-agenda-intern queue prompt
+ read-port-map write-port-map except-port-map
+ schedule time))
+
+(define (current-agenda-prompt)
+ "Get the prompt for the current agenda; signal an error if there isn't one."
+ (let ((current-agenda (%current-agenda)))
+ (if (not current-agenda)
+ (throw
+ 'no-current-agenda
+ "Can't get current agenda prompt if there's no current agenda!")
+ (agenda-prompt-tag current-agenda))))
+
+
+\f
+;;; Schedule
+;;; ========
+
+;;; This is where we handle timed events for the future
+
+;; This section totally borrows from the ideas in SICP
+;; <3 <3 <3
+
+;; NOTE: time is a cons of (seconds . microseconds)
+
+(define-record-type <time-segment>
+ (make-time-segment-intern time queue)
+ time-segment?
+ (time time-segment-time)
+ (queue time-segment-queue))
+
+(define (time-segment-right-format time)
+ "Ensure TIME is in the right format.
+
+The right format means (second . microsecond).
+If an integer, will convert appropriately."
+ ;; TODO: add floating point / rational number support.
+ (match time
+ ;; time is already a cons of second and microsecnd
+ (((? integer? s) . (? integer? u)) time)
+ ;; time was just an integer (just the second)
+ ((? integer? _) (cons time 0))
+ (_ (throw 'invalid-time "Invalid time" time))))
+
+(define* (make-time-segment time #:optional (queue (make-q)))
+ "Make a time segment of TIME and QUEUE
+
+No automatic conversion is done, so you might have to
+run (time-segment-right-format) first."
+ (make-time-segment-intern time queue))
+
+(define (time< time1 time2)
+ "Check if TIME1 is less than TIME2"
+ (cond ((< (car time1)
+ (car time2))
+ #t)
+ ((and (= (car time1)
+ (car time2))
+ (< (cdr time1)
+ (cdr time2)))
+ #t)
+ (else #f)))
+
+(define (time= time1 time2)
+ "Check whether TIME1 and TIME2 are equivalent"
+ (and (= (car time1) (car time2))
+ (= (cdr time1) (cdr time2))))
+
+(define (time<= time1 time2)
+ "Check if TIME1 is less than or equal to TIME2"
+ (or (time< time1 time2)
+ (time= time1 time2)))
+
+
+(define-record-type <time-delta>
+ (make-time-delta-intern sec usec)
+ time-delta?
+ (sec time-delta-sec)
+ (usec time-delta-usec))
+
+(define* (make-time-delta sec #:optional (usec 0))
+ "Make a <time-delta> of SEC seconds and USEC microseconds.
+
+This is used primarily so the agenda can recognize RUN-REQUEST objects
+which are meant "
+ (make-time-delta-intern sec usec))
+
+(define tdelta make-time-delta)
+
+(define (time-carry-correct time)
+ "Corrects/handles time microsecond carry.
+Will produce (0 . 0) instead of a negative number, if needed."
+ (cond ((>= (cdr time) 1000000)
+ (cons
+ (+ (car time) 1)
+ (- (cdr time) 1000000)))
+ ((< (cdr time) 0)
+ (if (= (car time) 0)
+ '(0 0)
+ (cons
+ (- (car time) 1)
+ (+ (cdr time) 1000000))))
+ (else time)))
+
+(define (time-delta+ time time-delta)
+ "Increment a TIME by the value of TIME-DELTA"
+ (time-carry-correct
+ (cons (+ (car time) (time-delta-sec time-delta))
+ (+ (cdr time) (time-delta-usec time-delta)))))
+
+(define (time-minus time1 time2)
+ "Subtract TIME2 from TIME1"
+ (time-carry-correct
+ (cons (- (car time1) (car time2))
+ (- (cdr time2) (cdr time2)))))
+
+(define (time-plus time1 time2)
+ "Add TIME1 and TIME2"
+ (time-carry-correct
+ (cons (+ (car time1) (car time2))
+ (+ (cdr time2) (cdr time2)))))
+
+
+(define-record-type <schedule>
+ (make-schedule-intern segments)
+ schedule?
+ (segments schedule-segments set-schedule-segments!))
+
+(define* (make-schedule #:optional segments)
+ "Make a schedule, optionally pre-composed of SEGMENTS"
+ (make-schedule-intern (or segments '())))
+
+(define (schedule-soonest-time schedule)
+ "Return a cons of (sec . usec) for next time segement, or #f if none"
+ (let ((segments (schedule-segments schedule)))
+ (if (eq? segments '())
+ #f
+ (time-segment-time (car segments)))))
+
+;; TODO: This code is reasonably easy to read but it
+;; mutates AND is worst case of O(n) in both space and time :(
+;; but at least it'll be reasonably easy to refactor to
+;; a more functional setup?
+(define (schedule-add! schedule time proc)
+ "Mutate SCHEDULE, adding PROC at an appropriate time segment for TIME"
+ (let ((time (time-segment-right-format time)))
+ (define (new-time-segment)
+ (let ((new-segment
+ (make-time-segment time)))
+ (enq! (time-segment-queue new-segment) proc)
+ new-segment))
+ (define (loop segments)
+ (define (segment-equals-time? segment)
+ (time= time (time-segment-time segment)))
+
+ (define (segment-more-than-time? segment)
+ (time< time (time-segment-time segment)))
+
+ ;; We could switch this out to be more mutate'y
+ ;; and avoid the O(n) of space... is that over-optimizing?
+ (match segments
+ ;; If we're at the end of the list, time to make a new
+ ;; segment...
+ ('() (cons (new-time-segment) '()))
+ ;; If the segment's time is exactly our time, good news
+ ;; everyone! Let's append our stuff to its queue
+ (((? segment-equals-time? first) rest ...)
+ (enq! (time-segment-queue first) proc)
+ segments)
+ ;; If the first segment is more than our time,
+ ;; ours belongs before this one, so add it and
+ ;; start consing our way back
+ (((? segment-more-than-time? first) rest ...)
+ (cons (new-time-segment) segments))
+ ;; Otherwise, build up recursive result
+ ((first rest ... )
+ (cons first (loop rest)))))
+ (set-schedule-segments!
+ schedule
+ (loop (schedule-segments schedule)))))
+
+(define (schedule-empty? schedule)
+ "Check if the SCHEDULE is currently empty"
+ (eq? (schedule-segments schedule) '()))
+
+(define (schedule-segments-split schedule time)
+ "Does a multiple value return of time segments before/at and after TIME"
+ (let ((time (time-segment-right-format time)))
+ (define (segment-is-now? segment)
+ (time= (time-segment-time segment) time))
+ (define (segment-is-before-now? segment)
+ (time< (time-segment-time segment) time))