git.fiddlerwoaroof.com
Browse code

Various tests

- Fleshed out the tip calc with utility macros
- modified test for nicer output
- generators sorta implemented via signals

fiddlerwoaroof authored on 24/10/2015 05:48:03
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
+