Skip to content

Commit 1544b70

Browse files
committed
Add implementation of digital-circuit
1 parent a592553 commit 1544b70

File tree

4 files changed

+466
-80
lines changed

4 files changed

+466
-80
lines changed

chapter3/after-delay.rkt

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
#lang racket
2+
(require compatibility/mlist)
3+
(require "queue.rkt")
4+
(require "sicp-compat.rkt")
5+
;;; segment: (time queue)
6+
(define (make-time-segment time queue)
7+
(cons time queue))
8+
9+
(define (segment-time s) (car s))
10+
11+
(define (segment-queue s) (cdr s))
12+
13+
;;; The agenda is a sorted list of time segments:
14+
;;; agenda: [current-time, (time1, queue1), (time2, queue2), (time3, queue3), ...]
15+
;;; agenda: [0, (5, [action-A]), (10, [action-B, action-C]), (15, [action-D])]
16+
(define (make-agenda) (mlist 0))
17+
18+
(define (current-time agenda) (car agenda))
19+
20+
(define (set-current-time! agenda time)
21+
(set-car! agenda time)
22+
)
23+
24+
(define (segments agenda) (cdr agenda))
25+
26+
(define (set-segments! agenda segments)
27+
(set-cdr! agenda segments))
28+
29+
(define (first-segment agenda)
30+
(car (segments agenda)))
31+
32+
(define (rest-segments agenda)
33+
(cdr (segments agenda)))
34+
35+
(define (empty-agenda? agenda)
36+
(null? (segments agenda)))
37+
38+
(define (add-to-agenda! time action agenda)
39+
(define (belongs-before? segments)
40+
(or (null? segments)
41+
(< time (segment-time (car segments)))))
42+
43+
(define (make-new-time-segment time action)
44+
(let ((q (make-queue)))
45+
(insert-queue! q action)
46+
(make-time-segment time q)))
47+
48+
(define (add-to-segments! segments)
49+
;; Time slot already exists
50+
(if (= (segment-time (car segments)) time)
51+
(insert-queue! (segment-queue (car segments))
52+
action)
53+
;; New time slot needed
54+
(let ((rest (cdr segments)))
55+
(if (belongs-before? rest)
56+
(set-cdr!
57+
segments
58+
(cons (make-new-time-segment time action)
59+
(cdr segments)))
60+
(add-to-segments! rest)))))
61+
62+
(let ((segments (segments agenda)))
63+
(if (belongs-before? segments)
64+
(set-segments!
65+
agenda
66+
(cons (make-new-time-segment time action)
67+
segments))
68+
69+
(add-to-segments! segments)))
70+
)
71+
72+
(define (remove-first-agenda-item! agenda)
73+
(let ((q (segment-queue (first-segment agenda))))
74+
(delete-queue! q)
75+
(when (empty-queue? q)
76+
(set-segments! agenda (rest-segments agenda)))))
77+
78+
(define (first-agenda-item agenda)
79+
(if (empty-agenda? agenda)
80+
(error "Agenda is empty -- FIRST-AGENDA-ITEM")
81+
(let ((first-seg (first-segment agenda)))
82+
(set-current-time! agenda (segment-time first-seg))
83+
(front-queue (segment-queue first-seg)))))
84+
85+
(define the-agenda (make-agenda))
86+
87+
;;; The after-delay function implements discrete event simulation for
88+
;;; digital circuits.
89+
;;; It schedules actions to execute at future simulation times without
90+
;;; blocking
91+
;;; - after-delay calculates current-time + delay, adds the action to a time-ordered agenda, and returns immediately.
92+
(define (after-delay delay action)
93+
(add-to-agenda! (+ delay (current-time the-agenda))
94+
action
95+
the-agenda))
96+
97+
;;; The propagate function serves as the event loop, processing
98+
;;; scheduled events chronologically.
99+
;;; Time advances in discrete jumps only when first-agenda-item calls
100+
;;; set-current-time!, jumping directly to the next event time (e.g.,
101+
;;; 0 -> 3 -> 5 -> 8, skipping intermediate times).
102+
;;; This design efficiently models digital circuits where signals change at specific moments, avoiding continuous time checking.
103+
(define (propagate-agenda! agenda)
104+
(if (empty-agenda? agenda)
105+
'done
106+
(let ((first-item (first-agenda-item agenda)))
107+
(first-item)
108+
(remove-first-agenda-item! agenda)
109+
(propagate-agenda! agenda))))
110+
111+
(define (propagate)
112+
(propagate-agenda! the-agenda))
113+
114+
(provide after-delay propagate)
115+
(module+ test
116+
(require rackunit)
117+
(define test-output '())
118+
(define (reset-test-output!) (set! test-output '()))
119+
(define (add-test-output! msg) (set! test-output (cons msg test-output)))
120+
(define (get-test-output) (reverse test-output))
121+
122+
(test-case "Test for time advances correctly"
123+
(define test-agenda (make-agenda))
124+
(check-equal? (current-time test-agenda) 0)
125+
126+
(add-to-agenda! 3 (λ () 'dummy) test-agenda)
127+
(add-to-agenda! 8 (λ () 'dummy) test-agenda)
128+
(add-to-agenda! 5 (λ () 'dummy) test-agenda)
129+
130+
;; 0 -> 3
131+
(first-agenda-item test-agenda)
132+
(check-equal? (current-time test-agenda) 3)
133+
(remove-first-agenda-item! test-agenda)
134+
135+
;; 3 -> 5
136+
(first-agenda-item test-agenda)
137+
(check-equal? (current-time test-agenda) 5)
138+
(remove-first-agenda-item! test-agenda)
139+
140+
;; 5 -> 8
141+
(first-agenda-item test-agenda)
142+
(check-equal? (current-time test-agenda) 8)
143+
(remove-first-agenda-item! test-agenda)
144+
)
145+
146+
(test-case "Single event scheduling and execution"
147+
(reset-test-output!)
148+
(let ((agenda (make-agenda)))
149+
(add-to-agenda! 5 (λ () (add-test-output! "Event at 5")) agenda)
150+
(check-equal? (current-time agenda) 0)
151+
(check-equal? (empty-agenda? agenda) #f)
152+
153+
;; Execute events
154+
(propagate-agenda! agenda)
155+
(check-equal? (current-time agenda) 5)
156+
(check-equal? (get-test-output) (list "Event at 5"))
157+
(check-equal? (empty-agenda? agenda) #t)
158+
))
159+
160+
(test-case "Multiple events in chronological order"
161+
(reset-test-output!)
162+
(let ((agenda (make-agenda)))
163+
(add-to-agenda! 10 (λ () (add-test-output! "Event at 10")) agenda)
164+
(add-to-agenda! 3 (λ () (add-test-output! "Event at 3")) agenda)
165+
(add-to-agenda! 7 (λ () (add-test-output! "Event at 7")) agenda)
166+
167+
(propagate-agenda! agenda)
168+
(check-equal? (get-test-output) (list "Event at 3" "Event at 7" "Event at 10"))
169+
(check-equal? (empty-agenda? agenda) #t)
170+
))
171+
172+
(test-case "Multiple events at same time"
173+
(reset-test-output!)
174+
(let ((agenda (make-agenda)))
175+
;; Schedule multiple events at same time
176+
(add-to-agenda! 5 (lambda () (add-test-output! "First at 5")) agenda)
177+
(add-to-agenda! 5 (lambda () (add-test-output! "Second at 5")) agenda)
178+
(add-to-agenda! 5 (lambda () (add-test-output! "Third at 5")) agenda)
179+
180+
(propagate-agenda! agenda)
181+
(check-equal? (length (get-test-output)) 3)
182+
(check-equal? (get-test-output) (list "First at 5" "Second at 5" "Third at 5"))
183+
(check-equal? (empty-agenda? agenda) #t)
184+
)
185+
)
186+
)

0 commit comments

Comments
 (0)