Browse code
Various tests
- Fleshed out the tip calc with utility macros
- modified test for nicer output
- generators sorta implemented via signals
Showing 3 changed files
1 | 1 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,41 @@ |
1 |
+(ql:quickload :alexandria) |
|
2 |
+ |
|
3 |
+(defpackage generator-conditions |
|
4 |
+ (:use :cl)) |
|
5 |
+(in-package generator-conditions) |
|
6 |
+ |
|
7 |
+(define-condition generator-condition () ()) |
|
8 |
+(define-condition yield-value (generator-condition) |
|
9 |
+ ((value :initarg :value :reader value))) |
|
10 |
+(define-condition stop (generator-condition) ()) |
|
11 |
+ |
|
12 |
+(defmacro define-generator (name args &body body) |
|
13 |
+ `(flet ((yield (v) (signal (make-condition 'yield-value :value v))) |
|
14 |
+ (stop () (signal (make-condition 'stop)))) |
|
15 |
+ (defun ,name ,args |
|
16 |
+ ,@body))) |
|
17 |
+ |
|
18 |
+(defmacro with-generator (generator var &body body) |
|
19 |
+ `(handler-case |
|
20 |
+ (handler-bind |
|
21 |
+ ((yield-value (lambda (c) |
|
22 |
+ (let ((,var (value c))) |
|
23 |
+ ,@body)))) |
|
24 |
+ ,generator) |
|
25 |
+ (stop (c) (declare (ignore c))))) |
|
26 |
+ |
|
27 |
+(define-generator to-ten () |
|
28 |
+ (loop for x from 0 to 10 |
|
29 |
+ do (yield x))) |
|
30 |
+ |
|
31 |
+(defparameter outp nil) |
|
32 |
+(with-generator (to-ten) x |
|
33 |
+ (with-generator (to-ten) y |
|
34 |
+ 2)) |
|
35 |
+ |
|
36 |
+(defpackage generator-lambda |
|
37 |
+ (:use :cl)) |
|
38 |
+(in-package generator-lambda) |
|
39 |
+ |
|
40 |
+ |
|
41 |
+ |
... | ... |
@@ -15,6 +15,10 @@ |
15 | 15 |
|
16 | 16 |
(in-package :math-test) |
17 | 17 |
|
18 |
+(defmacro push-many (place &body items) |
|
19 |
+ `(progn ,@(loop for item in items |
|
20 |
+ collect `(push ,item ,place)))) |
|
21 |
+ |
|
18 | 22 |
(defmodel service-rating () |
19 | 23 |
((rating :cell t :accessor rating :initarg :rating :initform (c-in :normal)) |
20 | 24 |
(rate :cell t :accessor rate :initform (c? (case (rating self) |
... | ... |
@@ -27,6 +31,9 @@ |
27 | 31 |
(rate :cell t :accessor rate :initarg :rate :initform (c-in 0.18)) |
28 | 32 |
(tip :cell t :accessor tip :initform (c? (* (cost self) |
29 | 33 |
(rate self)))))) |
34 |
+(defmodel fed-tip-calc (tip-calc) |
|
35 |
+ ((rate-feeder :cell t :accessor rate-feeder :initarg :feeder) |
|
36 |
+ (rate :cell t :accessor rate :initarg :rate :initform (c? (rate (rate-feeder self)))))) |
|
30 | 37 |
|
31 | 38 |
(defmodel item () |
32 | 39 |
((kind :cell t :accessor kind :initarg :kind :initform (c-in :food)) |
... | ... |
@@ -47,38 +54,38 @@ |
47 | 54 |
(subtotal self)) |
48 | 55 |
(tip self)))))) |
49 | 56 |
|
57 |
+ |
|
58 |
+(defobserver rate ((self tip-calc)) |
|
59 |
+ (when old-value-boundp |
|
60 |
+ (format t "The rate is: ~a It changed by: ~a~%" new-value (- new-value |
|
61 |
+ old-value)))) |
|
62 |
+ |
|
50 | 63 |
(defobserver tip ((self tip-calc)) |
51 | 64 |
(when old-value-boundp |
52 |
- (format t "The tip is: ~a~%It changed by: ~a~%" new-value (- new-value |
|
65 |
+ (format t "The tip is: ~a It changed by: ~a~%" new-value (- new-value |
|
53 | 66 |
old-value)))) |
54 | 67 |
|
55 | 68 |
|
56 |
- |
|
57 |
-(defmacro push-many (place &body items) |
|
58 |
- `(progn ,@(loop for item in items |
|
59 |
- collect `(push ,item ,place)))) |
|
60 |
- |
|
61 |
-(defparameter s-r (make-instance 'service-rating)) |
|
62 |
-(defparameter bill (make-instance 'bill)) |
|
63 |
-(defparameter tc (make-instance 'tip-calc :cost (c? (cost bill)) :rate (c? (rate s-r)))) |
|
64 |
-(defparameter meal-calc (make-instance 'meal-expense-calculator |
|
65 |
- :subtotal (c? (cost bill)) |
|
66 |
- :tip (c? (tip tc)))) |
|
67 |
- |
|
68 |
-(push-many (items bill) |
|
69 |
- (make-instance 'item :kind :meatloaf :cost 12.99) |
|
70 |
- (make-instance 'item :kind :salmon :cost 14.99) |
|
71 |
- (make-instance 'item :kind :frenchfries :cost 3.99) |
|
72 |
- (make-instance 'item :kind :tomatosoup :cost 3.99) |
|
73 |
- (make-instance 'item :kind :burgundy :cost 8.99) |
|
74 |
- (make-instance 'item :kind :icedtea :cost 3.99)) |
|
75 |
-(setf (rating s-r) :excellent) |
|
76 |
- |
|
77 |
-(format t "~{~a~20t~a~%~}------------------------------ |
|
78 |
-Subtotal:~20t~a~%Tax:~20t~a~%Tip:~20t~a~%Total:~20t~a~%" |
|
79 |
- (loop for item in (items bill) |
|
80 |
- append (list (kind item) (cost item))) |
|
81 |
- (subtotal meal-calc) |
|
82 |
- (* 0.08 (subtotal meal-calc)) |
|
83 |
- (tip tc) |
|
84 |
- (total meal-calc)) |
|
69 |
+(let* ((s-r (make-instance 'service-rating)) |
|
70 |
+ (bill (make-instance 'bill)) |
|
71 |
+ (tc (make-instance 'fed-tip-calc :cost (c? (cost bill)) :feeder s-r)) |
|
72 |
+ (meal-calc (make-instance 'meal-expense-calculator |
|
73 |
+ :subtotal (c? (cost bill)) |
|
74 |
+ :tip (c? (tip tc))))) |
|
75 |
+ |
|
76 |
+ (push-many (items bill) |
|
77 |
+ (make-instance 'item :kind :meatloaf :cost 12.99) |
|
78 |
+ (make-instance 'item :kind :salmon :cost 14.99) |
|
79 |
+ (make-instance 'item :kind :frenchfries :cost 3.99) |
|
80 |
+ (make-instance 'item :kind :tomatosoup :cost 3.99) |
|
81 |
+ (make-instance 'item :kind :burgundy :cost 8.99) |
|
82 |
+ (make-instance 'item :kind :icedtea :cost 3.99)) |
|
83 |
+ (setf (rating s-r) :excellent) |
|
84 |
+ |
|
85 |
+ (format t "~{~a~20t~a~%~}------------------------------~%Subtotal:~20t~a~%Tax:~20t~a~%Tip:~20t~a~%Total:~20t~a~%" |
|
86 |
+ (loop for item in (items bill) |
|
87 |
+ append (list (kind item) (cost item))) |
|
88 |
+ (subtotal meal-calc) |
|
89 |
+ (* 0.08 (subtotal meal-calc)) |
|
90 |
+ (tip tc) |
|
91 |
+ (total meal-calc))) |
85 | 92 |
old mode 100644 |
86 | 93 |
new mode 100755 |
... | ... |
@@ -1,12 +1,18 @@ |
1 |
+#!/usr/local/bin/sbcl --script |
|
2 |
+(declaim (optimize (speed 0) (safety 3) (debug 3))) |
|
3 |
+(load #p"~/quicklisp/setup.lisp") |
|
1 | 4 |
(ql:quickload :qtools) |
2 | 5 |
(ql:quickload :cells) |
3 | 6 |
(ql:quickload :qtcore) |
4 | 7 |
(ql:quickload :qtgui) |
8 |
+(ql:quickload :parse-number) |
|
9 |
+(ql:quickload :alexandria) |
|
10 |
+(ql:quickload :anaphora) |
|
5 | 11 |
|
6 | 12 |
(cl:defpackage :tipcalc.models |
7 | 13 |
(:use :cl :cells) |
8 | 14 |
(:export |
9 |
- service-rating tip-calc tax-calc item bill meal-expense-calculator |
|
15 |
+ service-rating fed-tip-calc tip-calc tax-calc item bill meal-expense-calculator |
|
10 | 16 |
rating rate cost tip tax kind item subtotal subtotal total)) |
11 | 17 |
|
12 | 18 |
(in-package :tipcalc.models) |
... | ... |
@@ -23,6 +29,10 @@ |
23 | 29 |
(rate :cell t :accessor rate :initarg :rate :initform (c-in 0.18)) |
24 | 30 |
(tip :cell t :accessor tip :initform (c? (* (cost self) |
25 | 31 |
(rate self)))))) |
32 |
+(defmodel fed-tip-calc (tip-calc) |
|
33 |
+ ((rate-feeder :cell t :accessor rate-feeder :initarg :rate-feeder) |
|
34 |
+ (rate :cell t :accessor rate :initarg :rate :initform (c? (rate (rate-feeder self)))))) |
|
35 |
+ |
|
26 | 36 |
|
27 | 37 |
(defmodel tax-calc () |
28 | 38 |
((cost :cell t :accessor cost :initarg :cost :initform (c-in 0)) |
... | ... |
@@ -50,36 +60,185 @@ |
50 | 60 |
(tip self)))))) |
51 | 61 |
|
52 | 62 |
(defpackage tipcalc |
53 |
- (:use :cl+qt :tipcalc.models)) |
|
63 |
+ (:use :cl+qt :parse-number :anaphora :tipcalc.models)) |
|
54 | 64 |
(in-package :tipcalc) |
55 | 65 |
(in-readtable :qtools) |
66 |
+;(defparameter *service-rating* (make-instance 'service-rating)) |
|
67 |
+;(defparameter *tip-calc* (make-instance 'tip-calc :rate (rate *service-rating*))) |
|
56 | 68 |
|
69 |
+(define-widget edits (QWidget) ()) |
|
70 |
+(define-widget service-quality (QWidget) ()) |
|
57 | 71 |
(define-widget main-window (QWidget) ()) |
58 | 72 |
|
59 |
-(define-subwidget (main-window cost-widg) (q+:make-qlineedit main-window) |
|
60 |
- (setf (q+:placeholder-text cost-widg) "Meal Cost?")) |
|
61 |
- |
|
62 |
-(define-subwidget (main-window rate-widg) (q+:make-qlineedit main-window) |
|
63 |
- (setf (q+:placeholder-text rate-widg) "Tip Rate?")) |
|
64 |
- |
|
65 |
-(define-subwidget (main-window tip-widg) (q+:make-qlineedit main-window) |
|
66 |
- (setf (q+:placeholder-text tip-widg) "Tip . . .")) |
|
67 |
- |
|
68 |
-(define-subwidget (main-window layout) (q+:make-qhboxlayout main-window) |
|
69 |
- (q+:add-widget layout cost-widg) |
|
70 |
- (q+:add-widget layout rate-widg) |
|
71 |
- (q+:add-widget layout tip-widg)) |
|
72 |
- |
|
73 |
-(defparameter *tip-calc* (make-instance 'tip-calc)) |
|
74 |
-(cells:defobserver tip ((self tip-calc)) |
|
75 |
- ; I want this to run: |
|
76 |
- ; EITHER When the old value is not bound |
|
77 |
- ; OR When the old value is not equal to the new value |
|
78 |
- (when (or (not old-value-boundp) (/= new-value old-value)) |
|
79 |
- (setf (q+:text) |
|
80 |
- (format nil "~a" new-value)))) |
|
73 |
+(define-widget my-slider (QWidget) ()) |
|
74 |
+(define-signal (my-slider val-changed) (float)) |
|
75 |
+(define-subwidget (my-slider slider) (q+:make-qslider my-slider)) |
|
76 |
+(define-subwidget (my-slider label) (q+:make-qlabel my-slider)) |
|
77 |
+(define-subwidget (my-slider layout) (q+:make-qvboxlayout my-slider) |
|
78 |
+ (q+:add-widget slider) |
|
79 |
+ (q+:add-widget label)) |
|
80 |
+ |
|
81 |
+(define-slot (my-slider slider) ((new-val integer)) |
|
82 |
+ (declare (connected slider (value-changed integer))) |
|
83 |
+ (setf (q+:text label) (format nil "~a%" new-val)) |
|
84 |
+ (signal! my-slider (val-changed float) (coerce (/ new-val 100) 'float))) |
|
85 |
+ |
|
86 |
+(defmethod set-value ((my-slider my-slider) (new-value float)) |
|
87 |
+ (with-slots-bound (my-slider my-slider) |
|
88 |
+ (setf (q+:value slider) (round (* new-value 100))))) |
|
89 |
+ |
|
90 |
+ |
|
91 |
+(defmacro alambda (&body body) |
|
92 |
+ `(lambda (it) ,@body)) |
|
93 |
+ |
|
94 |
+(defmacro with-no-signals (widget &body body) |
|
95 |
+ (alexandria:once-only (widget) |
|
96 |
+ `(progn |
|
97 |
+ (q+:block-signals ,widget t) |
|
98 |
+ (unwind-protect |
|
99 |
+ (progn |
|
100 |
+ ,@body) |
|
101 |
+ (q+:block-signals ,widget nil))))) |
|
102 |
+ |
|
103 |
+(defmacro connect-cell-to-widget ((widget sub-widget) (cell class) place widg-value-cb) |
|
104 |
+ "This connects a widget to an observable cell. TODO: figure out the macro edge-cases here" |
|
105 |
+ `(cells:defobserver ,cell ((self ,class)) |
|
106 |
+ ; I want this to run: |
|
107 |
+ ; EITHER When the old value is not bound |
|
108 |
+ ; OR When the old value is not equal to the new value |
|
109 |
+ (format t "--> changed: ~a is not ~a~%" ,(symbol-name cell) cells:new-value) |
|
110 |
+ (with-slots-bound (,widget ,widget) |
|
111 |
+ (with-signals-blocked (,sub-widget) |
|
112 |
+ (setf ,place (funcall ,widg-value-cb cells:new-value)))))) |
|
113 |
+ |
|
114 |
+(defmacro connect-widget-to-cell ((widget signal arg-type) place &key value-mod) |
|
115 |
+ (alexandria:with-gensyms (arg) |
|
116 |
+ `(define-slot (,widget ,signal) ((,arg ,arg-type)) |
|
117 |
+ (declare (connected ,widget (,signal ,arg-type))) |
|
118 |
+ ,(when value-mod |
|
119 |
+ `(setf ,arg (funcall ,value-mod ,arg))) |
|
120 |
+ (setf ,place ,arg)))) |
|
121 |
+ |
|
122 |
+(defmacro connecting ((widget sub-widget) (cell-slot-name cell-class) cell-instance &body body) |
|
123 |
+ `(macrolet ((cell->widget (place cb) |
|
124 |
+ `(connect-cell-to-widget (,',widget ,',sub-widget) (,',cell-slot-name ,',cell-class) ,place ,cb)) |
|
125 |
+ (signal->cell (signal &key value-mod) |
|
126 |
+ `(connect-widget-to-cell (,',widget ,@signal) (,',cell-slot-name ,',cell-instance) :value-mod ,value-mod))) |
|
127 |
+ ,@body)) |
|
128 |
+ |
|
129 |
+(defmacro define-connections (&rest connections) |
|
130 |
+ (list* |
|
131 |
+ 'progn |
|
132 |
+ (loop for (widg-spec cell-spec cell-inst . body) in connections |
|
133 |
+ collect `(connecting ,widg-spec ,cell-spec ,cell-inst |
|
134 |
+ ,@body)))) |
|
135 |
+ |
|
136 |
+(let* ((s-r (make-instance 'service-rating)) |
|
137 |
+ (tc (make-instance 'tip-calc :rate (cells:c? (rate s-r))))) |
|
138 |
+ |
|
139 |
+ (defun stringify (obj) (format nil "~a" obj)) |
|
140 |
+ (defun make-keyword (str) |
|
141 |
+ (alexandria:make-keyword (string-upcase str))) |
|
142 |
+ |
|
143 |
+ (define-signal (edits cost-changed) (float)) |
|
144 |
+ (define-signal (edits rate-changed) (float)) |
|
145 |
+ (define-signal (edits tip-changed) (float)) |
|
146 |
+ |
|
147 |
+ (define-subwidget (edits cost-widg) (q+:make-qlineedit edits) |
|
148 |
+ (setf (q+:placeholder-text cost-widg) (stringify (cost tc)))) |
|
149 |
+ |
|
150 |
+ (connecting (edits cost-widg) (cost tip-calc) tc |
|
151 |
+ (signal->cell |
|
152 |
+ (cost-changed float))) |
|
153 |
+ |
|
154 |
+ (define-slot (edits cost-widg) ((new-text string)) |
|
155 |
+ (declare (connected cost-widg (text-edited string))) |
|
156 |
+ (handler-case |
|
157 |
+ (let ((num (coerce (parse-number new-text) 'float))) |
|
158 |
+ (signal! edits (cost-changed float) num)) |
|
159 |
+ ((or parse-error type-error) (c) (declare (ignore c))))) |
|
160 |
+ |
|
161 |
+ (define-subwidget (edits rate-widg) (q+:make-qslider edits) |
|
162 |
+ (setf (q+:minimum rate-widg) 6) |
|
163 |
+ (setf (q+:maximum rate-widg) 30) |
|
164 |
+ (setf (q+:value rate-widg) (round (* 100 (rate s-r))))) |
|
165 |
+ |
|
166 |
+ |
|
167 |
+ |
|
168 |
+ (connecting (edits rate-widg) (rate tip-calc) tc |
|
169 |
+ (cell->widget |
|
170 |
+ (q+:value rate-widg) |
|
171 |
+ (alambda |
|
172 |
+ (round (* 100 it)))) |
|
173 |
+ (signal->cell |
|
174 |
+ (rate-changed float))) |
|
175 |
+ |
|
176 |
+ |
|
177 |
+ (define-slot (edits rate-widg) ((new-value integer)) |
|
178 |
+ (declare (connected rate-widg (value-changed integer))) |
|
179 |
+ (handler-case |
|
180 |
+ (signal! edits (rate-changed float) (coerce (/ new-value 100) 'float)) |
|
181 |
+ ((or parse-error type-error) (c) (declare (ignore c))))) |
|
182 |
+ |
|
183 |
+ |
|
184 |
+ (define-subwidget (edits tip-widg) (q+:make-qlineedit edits) |
|
185 |
+ (setf (q+:placeholder-text tip-widg) (stringify (tip tc)))) |
|
186 |
+ |
|
187 |
+ (connecting (edits tip-widg) (tip tip-calc) tc |
|
188 |
+ (cell->widget |
|
189 |
+ (q+:text tip-widg) |
|
190 |
+ (alambda |
|
191 |
+ (stringify it)))) |
|
192 |
+ |
|
193 |
+ |
|
194 |
+ (define-subwidget (edits edit-layout) (q+:make-qhboxlayout edits) |
|
195 |
+ (q+:add-widget edit-layout cost-widg) |
|
196 |
+ (q+:add-widget edit-layout rate-widg) |
|
197 |
+ (q+:add-widget edit-layout tip-widg)) |
|
198 |
+ |
|
199 |
+ (define-signal (service-quality quality-chosen) (string)) |
|
200 |
+ |
|
201 |
+ (define-subwidget (service-quality choose-poor) (q+:make-qradiobutton service-quality) |
|
202 |
+ (setf (q+:text choose-poor) "Poor")) |
|
203 |
+ (define-slot (service-quality choose-poor) () |
|
204 |
+ (declare (connected choose-poor (toggled bool))) |
|
205 |
+ (signal! service-quality (quality-chosen string) "poor")) |
|
206 |
+ |
|
207 |
+ (define-subwidget (service-quality choose-normal) (q+:make-qradiobutton service-quality) |
|
208 |
+ (setf (q+:text choose-normal) "Normal")) |
|
209 |
+ (define-slot (service-quality choose-normal) () |
|
210 |
+ (declare (connected choose-normal (toggled bool))) |
|
211 |
+ (signal! service-quality (quality-chosen string) "normal")) |
|
212 |
+ |
|
213 |
+ |
|
214 |
+ (define-subwidget (service-quality choose-excellent) (q+:make-qradiobutton service-quality) |
|
215 |
+ (setf (q+:text choose-excellent) "Excellent")) |
|
216 |
+ (define-slot (service-quality choose-excellent) () |
|
217 |
+ (declare (connected choose-excellent (toggled bool))) |
|
218 |
+ (signal! service-quality (quality-chosen string) "excellent")) |
|
219 |
+ |
|
220 |
+ (connecting (service-quality nil) (rating service-rating) s-r |
|
221 |
+ (signal->cell |
|
222 |
+ (quality-chosen string) |
|
223 |
+ :value-mod (alambda (make-keyword it)))) |
|
224 |
+ |
|
225 |
+ (define-subwidget (service-quality quality-layout) (q+:make-qhboxlayout service-quality) |
|
226 |
+ (q+:add-widget quality-layout choose-poor) |
|
227 |
+ (q+:add-widget quality-layout choose-normal) |
|
228 |
+ (q+:add-widget quality-layout choose-excellent)) |
|
229 |
+ |
|
230 |
+ (define-subwidget (main-window edits) (make-instance 'edits)) |
|
231 |
+ (define-subwidget (main-window service-quality) (make-instance 'service-quality)) |
|
232 |
+ (define-subwidget (main-window layout) (q+:make-qvboxlayout main-window) |
|
233 |
+ (q+:add-widget layout service-quality) |
|
234 |
+ (q+:add-widget layout edits))) |
|
81 | 235 |
|
82 | 236 |
|
83 | 237 |
(defun main () |
84 | 238 |
(with-main-window (window (make-instance 'main-window)))) |
85 | 239 |
|
240 |
+(ql:quickload :swank) |
|
241 |
+(swank:create-server :port 4006) |
|
242 |
+(handler-case (main) |
|
243 |
+ (sb-sys:interactive-interrupt (c) (declare (ignore c)) (format t "~cExiting on interrupt...~%" #\return))) |
|
244 |
+ |