Skip to content

Commit

Permalink
Fix and complete everything, hopefully
Browse files Browse the repository at this point in the history
  • Loading branch information
phoe committed May 9, 2020
1 parent 30dd961 commit 0083fdb
Show file tree
Hide file tree
Showing 16 changed files with 277 additions and 191 deletions.
38 changes: 22 additions & 16 deletions koans-solved/backquote.lisp
Original file line number Diff line number Diff line change
Expand Up @@ -19,47 +19,53 @@
(let ((x '(123))
(z '(7 8 9)))
;; ' quotes an expression normally.
(assert-equal ____ '(x 45 6 z))
(assert-equal '(x 45 6 z) '(x 45 6 z))
;; ` backquotes an expression; without any unquotes, it is equivalent to
;; using the normal quote.
(assert-equal ____ `(x 45 6 z))
(assert-equal '(x 45 6 z) `(x 45 6 z))
;; , unquotes a part of the expression.
(assert-equal ____ `(,x 45 6 z))
(assert-equal ____ `(,x 45 6 ,z))
(assert-equal '((123) 45 6 z) `(,x 45 6 z))
(assert-equal '((123) 45 6 (7 8 9)) `(,x 45 6 ,z))
;; ,@ splices an expression into the into the list surrounding it.
(assert-equal ____ `(,x 45 6 ,@z))
(assert-equal ____ `(,@x 45 6 ,@z))))
(assert-equal '((123) 45 6 7 8 9) `(,x 45 6 ,@z))
(assert-equal '(123 45 6 7 8 9) `(,@x 45 6 ,@z))))

(define-test backquote-forms
;; Because of its properties, backquote is useful for constructing Lisp forms
;; that are macroexpansions or parts of macroexpansions.
(let ((variable 'x))
;; Fill in the blank without without using backquote/unquote notation.
(assert-equal ____
(assert-equal '(if (typep x 'string)
(format nil "The value of ~A is ~A" 'x x)
(error 'type-error :datum x :expected-type 'string))
`(if (typep ,variable 'string)
(format nil "The value of ~A is ~A" ',variable ,variable)
(error 'type-error :datum ,variable
:expected-type 'string))))
(let ((error-type 'type-error)
(error-arguments '(:datum x :expected-type 'string)))
;; Fill in the blank without without using backquote/unquote notation.
(assert-equal ____
(assert-equal '(if (typep x 'string)
(format nil "The value of ~A is ~A" 'x x)
(error 'type-error :datum x :expected-type 'string))
`(if (typep x 'string)
(format nil "The value of ~A is ~A" 'x x)
(error ',error-type ,@error-arguments)))))

(define-test numbers-and-words
(let ((number 5)
(word 'dolphin))
(true-or-false? ____ (equal '(1 3 5) `(1 3 5)))
(true-or-false? ____ (equal '(1 3 5) `(1 3 number)))
(assert-equal _____ `(1 3 ,number))
(assert-equal _____ `(word ,word ,word word))))
(true-or-false? t (equal '(1 3 5) `(1 3 5)))
(true-or-false? nil (equal '(1 3 5) `(1 3 number)))
(assert-equal '(1 3 5) `(1 3 ,number))
(assert-equal '(word dolphin dolphin word) `(word ,word ,word word))))

(define-test splicing
(let ((axis '(x y z)))
(assert-equal '(the axis are ____) `(the axis are ,axis))
(assert-equal '(the axis are ____) `(the axis are ,@axis)))
(assert-equal '(the axis are (x y z)) `(the axis are ,axis))
(assert-equal '(the axis are x y z) `(the axis are ,@axis)))
(let ((coordinates '((43.15 77.6) (42.36 71.06))))
(assert-equal ____ `(the coordinates are ,coordinates))
(assert-equal ____ `(the coordinates are ,@coordinates))))
(assert-equal '(the coordinates are ((43.15 77.6) (42.36 71.06)))
`(the coordinates are ,coordinates))
(assert-equal '(the coordinates are (43.15 77.6) (42.36 71.06))
`(the coordinates are ,@coordinates))))
178 changes: 97 additions & 81 deletions koans-solved/condition-handlers.lisp
Original file line number Diff line number Diff line change
Expand Up @@ -37,25 +37,25 @@
(define-test type-hierarchy
;; Inheritance for condition types works the same way as for classes.
(let ((condition (make-condition 'my-condition)))
(true-or-false? ____ (typep condition 'my-condition))
(true-or-false? ____ (typep condition 'condition))
(true-or-false? ____ (typep condition 'warning))
(true-or-false? ____ (typep condition 'error)))
(true-or-false? t (typep condition 'my-condition))
(true-or-false? t (typep condition 'condition))
(true-or-false? nil (typep condition 'warning))
(true-or-false? nil (typep condition 'error)))
(let ((condition (make-condition 'my-warning)))
(true-or-false? ____ (typep condition 'my-warning))
(true-or-false? ____ (typep condition 'warning))
(true-or-false? ____ (typep condition 'error)))
(true-or-false? t (typep condition 'my-warning))
(true-or-false? t (typep condition 'warning))
(true-or-false? nil (typep condition 'error)))
(let ((condition (make-condition 'my-serious-condition)))
(true-or-false? ____ (typep condition 'my-serious-condition))
(true-or-false? ____ (typep condition 'serious-condition))
(true-or-false? ____ (typep condition 'warning))
(true-or-false? ____ (typep condition 'error)))
(true-or-false? t (typep condition 'my-serious-condition))
(true-or-false? t (typep condition 'serious-condition))
(true-or-false? nil (typep condition 'warning))
(true-or-false? nil (typep condition 'error)))
(let ((condition (make-condition 'my-error)))
(true-or-false? ____ (typep condition 'my-error))
(true-or-false? ____ (typep condition 'my-serious-condition))
(true-or-false? ____ (typep condition 'serious-condition))
(true-or-false? ____ (typep condition 'warning))
(true-or-false? ____ (typep condition 'error))))
(true-or-false? t (typep condition 'my-error))
(true-or-false? nil (typep condition 'my-serious-condition))
(true-or-false? t (typep condition 'serious-condition))
(true-or-false? nil (typep condition 'warning))
(true-or-false? t (typep condition 'error))))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

Expand All @@ -64,104 +64,120 @@

(defvar *list*)

(defun handle-my-error (condition)
(define-condition foo () ())

(define-condition bar (foo) ())

(define-condition baz (bar) ())

(defun handle-foo (condition)
(declare (ignore condition))
(push :my-error *list*))
(push :foo *list*))

(defun handle-error (condition)
(defun handle-bar (condition)
(declare (ignore condition))
(push :error *list*))
(push :bar *list*))

(defun handle-my-serious-condition (condition)
(defun handle-baz (condition)
(declare (ignore condition))
(push :my-serious-condition *list*))
(push :baz *list*))

(define-test handler-bind
;; When a condition is signaled, all handlers whose type matches the
;; condition's type are allowed to execute.
(let ((*list* '()))
(handler-bind ((my-error #'handle-my-error)
(error #'handle-error)
(my-serious-condition #'handle-my-serious-condition))
(signal (make-condition 'my-error)))
(assert-equal ____ *list*)))
(handler-bind ((bar #'handle-bar)
(foo #'handle-foo)
(baz #'handle-baz))
(signal (make-condition 'baz)))
(assert-equal '(:baz :foo :bar) *list*)))

(define-test handler-order
;; The order of binding handlers matters.
(let ((*list* '()))
(handler-bind ((error #'handle-error)
(my-error #'handle-my-error)
(my-serious-condition #'handle-my-serious-condition))
(signal (make-condition 'my-error)))
(assert-equal ____ *list*)))
(handler-bind ((foo #'handle-foo)
(bar #'handle-bar)
(baz #'handle-baz))
(signal (make-condition 'baz)))
(assert-equal '(:baz :bar :foo) *list*)))

(define-test multiple-handler-binds
;; It is possible to bind handlers in steps.
(let ((*list* '()))
(handler-bind ((error #'handle-error)
(my-serious-condition #'handle-my-serious-condition))
(handler-bind ((my-error #'handle-my-error))
(signal (make-condition 'my-error))))
(assert-equal ____ *list*)))
(handler-bind ((foo #'handle-foo)
(baz #'handle-baz))
(handler-bind ((bar #'handle-bar))
(signal (make-condition 'baz))))
(assert-equal '(:baz :foo :bar) *list*)))

(define-test same-handler
;; The same handler may be bound multiple times.
(let ((*list* '()))
(handler-bind ((error #'handle-error)
(error #'handle-error))
(handler-bind ((my-error #'handle-my-error)
(error #'handle-error)
(my-error #'handle-my-error))
(signal (make-condition 'my-error))))
(assert-equal ____ *list*)))
(handler-bind ((foo #'handle-foo)
(foo #'handle-foo))
(handler-bind ((bar #'handle-bar)
(foo #'handle-foo)
(bar #'handle-bar))
(signal (make-condition 'baz))))
(assert-equal '(:foo :foo :bar :foo :bar) *list*)))

(define-test handler-types
;; A handler is not executed if it does not match the condition type.
(let ((*list* '()))
(handler-bind ((error #'handle-error)
(my-error #'handle-my-error)
(my-serious-condition #'handle-my-serious-condition))
(signal (make-condition 'my-serious-condition)))
(assert-equal ____ *list*)))
(handler-bind ((foo #'handle-foo)
(bar #'handle-bar)
(baz #'handle-baz))
(signal (make-condition 'bar)))
(assert-equal '(:bar :foo) *list*)))

(define-test handler-transfer-of-control
;; A handler may decline to handle the condition if it returns normally,
;; or it may handle the condition by transferring control elsewhere.
(let ((*list* '()))
(block my-block
(handler-bind ((error #'handle-error)
(error (lambda (condition)
(declare (ignore condition))
(return-from my-block)))
(error #'handle-error))
(signal (make-condition 'my-error))))
(assert-equal ____ *list*)))
(handler-bind ((foo #'handle-foo)
(foo (lambda (condition)
(declare (ignore condition))
(return-from my-block)))
(foo #'handle-foo))
(signal (make-condition 'foo))))
(assert-equal '(:foo) *list*)))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(defun handle-error (condition)
(declare (ignore condition))
(push :error *list*))

(define-condition my-error (error) ())

(defun handle-my-error (condition)
(declare (ignore condition))
(push :my-error *list*))

(define-test handler-case
;; HANDLER-CASE always transfers control before executing the case forms.
(let ((*list* '()))
(handler-case (signal (make-condition 'my-error))
(error (condition) (handle-error condition))
(my-error (condition) (handle-my-error condition)))
(assert-equal ____ *list*)))
(assert-equal '(:error) *list*)))

(define-test handler-case-order
;; The order of handler cases matters.
(let ((*list* '()))
(handler-case (signal (make-condition 'my-error))
(my-error (condition) (handle-my-error condition))
(error (condition) (handle-error condition)))
(assert-equal ____ *list*)))
(assert-equal '(:my-error) *list*)))

(define-test handler-case-type
;; A handler cases is not executed if it does not match the condition type.
(let ((*list* '()))
(handler-case (signal (make-condition 'error))
(my-error (condition) (handle-my-error condition))
(error (condition) (handle-error condition)))
(assert-equal ____ *list*)))
(assert-equal '(:error) *list*)))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

Expand All @@ -172,18 +188,18 @@
;; ASSERT-ERROR is a Lisp Koans macro which verifies that the correct error
;; type is signaled.
(assert-equal 3 (divide 6 2))
(assert-error 'division-by-zero (divide 6 0))
(assert-error 'type-error (divide 6 :zero)))
(assert-error (divide 6 0) 'division-by-zero)
(assert-error (divide 6 :zero) 'type-error))

(define-test error-signaling-handler-case
(flet ((try-to-divide (numerator denominator)
;; In code outside Lisp Koans, HANDLER-CASE should be used.
(handler-case (divide numerator denominator)
(division-by-zero () :division-by-zero)
(type-error () :type-error))))
(assert-equal ____ (try-to-divide 6 2))
(assert-equal ____ (try-to-divide 6 0))
(assert-equal ____ (try-to-divide 6 :zero))))
(assert-equal 3 (try-to-divide 6 2))
(assert-equal :division-by-zero (try-to-divide 6 0))
(assert-equal :type-error (try-to-divide 6 :zero))))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

Expand All @@ -192,18 +208,18 @@

(define-test accessors-division-by-zero
(let ((condition (handler-case (divide 6 0) (division-by-zero (c) c))))
(assert-equal ____ (arithmetic-error-operands condition))
(assert-equal '(6 0) (arithmetic-error-operands condition))
(let ((operation (arithmetic-error-operation condition)))
(assert-equal ____ (funcall operation 12 4)))))
(assert-equal 3 (funcall operation 12 4)))))

(define-test accessors-type-error
(let ((condition (handler-case (divide 6 :zero) (type-error (c) c))))
(assert-equal ____ (type-error-datum condition))
(assert-equal :zero (type-error-datum condition))
(let ((expected-type (type-error-expected-type condition)))
(true-or-false? ____ (typep :zero expected-type))
(true-or-false? ____ (typep 0 expected-type))
(true-or-false? ____ (typep "zero" expected-type))
(true-or-false? ____ (typep 0.0 expected-type)))))
(true-or-false? nil (typep :zero expected-type))
(true-or-false? t (typep 0 expected-type))
(true-or-false? nil (typep "zero" expected-type))
(true-or-false? t (typep 0.0 expected-type)))))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

Expand All @@ -218,9 +234,9 @@
;; The macro CHECK-TYPE signals a TYPE-ERROR if the object is not of the
;; specified type.
(check-type line string)
(cond ((= 0 (search "TIMESTAMP" line)) :timestamp)
((= 0 (search "HTTP" line)) :http)
((= 0 (search "LOGIN" line)) :login)
(cond ((eql 0 (search "TIMESTAMP" line)) :timestamp)
((eql 0 (search "HTTP" line)) :http)
((eql 0 (search "LOGIN" line)) :login)
;; The function ERROR should be used for signaling serious conditions
;; and errors: if the condition is not handled, it halts program
;; execution and starts the Lisp debugger.
Expand All @@ -231,12 +247,12 @@
(flet ((try-log-line-type (line)
(handler-case (log-line-type line)
(error (condition) condition))))
(assert-equal ____ (try-log-line-type "TIMESTAMP 2020-05-08 16:59:39"))
(assert-equal ____ (try-log-line-type "HTTP GET / from 127.0.0.1"))
(assert-equal ____ (try-log-line-type "LOGIN administrator:hunter2"))
(assert-equal :timestamp (try-log-line-type "TIMESTAMP 2020-05-08 16:59:39"))
(assert-equal :http (try-log-line-type "HTTP GET / from 127.0.0.1"))
(assert-equal :login (try-log-line-type "LOGIN administrator:hunter2"))
(let ((condition (try-log-line-type "WARNING: 95% of disk space used")))
(assert-equal ____ (line condition))
(assert-equal ____ (reason condition)))
(assert-equal "WARNING: 95% of disk space used" (line condition))
(assert-equal :unknown-log-line-type (reason condition)))
(let ((condition (try-log-line-type 5555)))
(assert-equal 'string (____ condition))
(assert-equal 5555 (____ condition)))))
(assert-equal 'string (type-error-expected-type condition))
(assert-equal 5555 (type-error-datum condition)))))
Loading

0 comments on commit 0083fdb

Please sign in to comment.