;;; -*- Mode:Lisp; Syntax:ANSI-Common-Lisp; Coding:utf-8 -*- (defpackage #:let-plus-tests (:use #:cl #:alexandria #:lift #:let-plus) (:export #:run)) (in-package #:let-plus-tests) (deftestsuite let-plus-tests () ()) (defun run () "Run all the tests for LET-PLUS." (run-tests :suite 'let-plus-tests)) (defmacro test-r/o-and-r/w (init-form bindings r/o-form r/w-form &key (add 7) (modified-variable (caar bindings))) "Macro for autogenerating tests for testing read-only and read-write access. INIT-FORM is evaluated and bound to a value, which is then bound using LET+ with r/o-form (for read only access) and r/w-form for (read and write access). Values are compared to bindings (which are of the form (VARIABLE VALUE), where value should be numeric. MODIFIED-VARIABLE is modified, by adding ADD and checked for consistency (r/o form should not modify value, while r/w form should)." (with-unique-names (object) (once-only (add) `(let ((,object ,init-form)) (let+ ((,r/o-form ,object)) ,@(loop for (var value) in bindings collect `(ensure-same ,var ,value)) (incf ,modified-variable ,add)) (let+ ((,r/w-form ,object)) ,@(loop for (var value) in bindings collect `(ensure-same ,var ,value)) (incf ,modified-variable ,add)) (let+ ((,r/o-form ,object)) (ensure-same ,(caar bindings) (+ ,add ,(cadar bindings)))))))) (addtest (let-plus-tests) test-let*-compatibility (let+ (a (b 1) (c (+ b 3))) (ensure-same a nil) (ensure-same b 1) (ensure-same c 4))) (addtest (let-plus-tests) test-unrecognized-form (ensure-warning (macroexpand '(let+ (((&does-not-exist a b) '(1 2 3))))))) (defclass foo () ((bar :accessor bar :initarg :bar) (baz :accessor baz-acc :initarg :baz)) (:documentation "Class for testing purposes.")) (addtest (let-plus-tests) test-slots (test-r/o-and-r/w (make-instance 'foo :bar 1 :baz 2) ((bar 1) (baz2 2)) (&slots-r/o bar (baz2 baz)) (&slots bar (baz2 baz)))) (addtest (let-plus-tests) test-accessors (test-r/o-and-r/w (make-instance 'foo :bar 1 :baz 2) ((bar 1) (baz 2)) (&accessors-r/o bar (baz baz-acc)) (&accessors bar (baz baz-acc)))) (defstruct foo2 "Structure for testing purposes." bar baz) (addtest (let-plus-tests) test-structure (test-r/o-and-r/w (make-foo2 :bar 1 :baz 2) ((bar 1) (baz 2)) (&structure-r/o foo2- bar (baz baz)) (&structure foo2- bar (baz baz)))) (addtest (let-plus-tests) test-values (let+ (((&values a &ign c) (values 1 2 3))) (ensure-same a 1) (ensure-same c 3))) (addtest (let-plus-tests) test-values-recursive (let+ (((&values (a b) &ign c) (values '(1 4) 2 3))) (ensure-same a 1) (ensure-same b 4) (ensure-same c 3))) (declaim (notinline two-element-vector)) (defun two-element-vector () #(1 2)) (addtest (let-plus-tests) test-array (let+ ((#2A((a &ign) (b c)) #2A((1 2) (3 4)))) (ensure-same a 1) (ensure-same b 3) (ensure-same c 4)) (ensure-error (let+ ((#(a b c) (two-element-vector)))))) (addtest (let-plus-tests) test-array-elements (test-r/o-and-r/w (make-array '(2 2) :initial-contents '((1 2) (3 4))) ((a 1) (b 3) (c 4)) (&array-elements-r/o (a 0 0) (b 1 0) (c 1 1)) (&array-elements (a 0 0) (b 1 0) (c 1 1)))) (addtest (let-plus-tests) test-flet (let+ (((&flet add1 (x) (1+ x)) )) (ensure-same (add1 2) 3))) (addtest (let-plus-tests) test-labels (let+ (((&labels my-factorial (x) (if (<= 2 x) (* x (my-factorial (1- x))) 1)))) (ensure-same (my-factorial 4) 24))) (addtest (let-plus-tests) test-labels/mutual-recursion (let+ (((&labels my-even? (x) (cond ((zerop x) t) ((plusp x) (my-odd? (1- x))) (t nil)))) ((&labels my-odd? (x) (my-even? (1- x))))) (ensure-same (my-odd? 4) nil) (ensure-same (my-even? 4) t) (ensure-same (my-odd? 5) t) (ensure-same (my-even? 5) nil))) (defun labels-test-function () 10) (addtest (let-plus-tests) test-labels/dont-splice (ensure-same (let+ (((&labels foo () (labels-test-function)))) (labels ((labels-test-function () 5)) (labels-test-function)) (foo)) 10) (ensure-same (let+ (((&labels foo () (labels-test-function)))) (labels ((labels-test-function () 5)) (values (foo) (labels-test-function)))) (values 10 5)) (ensure-same (let+ (((&labels foo () (labels-test-function)))) (let+ (((&labels labels-test-function () 5))) (values (foo) (labels-test-function)))) (values 10 5))) (addtest (let-plus-tests) test-plist (test-r/o-and-r/w (list 'a 1 :b 2 'c '3) ((a 1) (b 2) (c 3) (d 4)) (&plist-r/o a (b :b) (c nil) (d nil 4)) (&plist a (b :b) (c nil) (d nil 4)))) (addtest (let-plus-tests) test-hash-table (test-r/o-and-r/w (let ((table (make-hash-table))) (setf (gethash 'a table) 1 (gethash :b table) 2 (gethash 'c table) 3) table) ((a 1) (b 2) (c 3) (d 4)) (&hash-table-r/o a (b :b) (c nil) (d nil 4)) (&hash-table a (b :b) (c nil) (d nil 4)))) (eval-when (:compile-toplevel :load-toplevel :execute) (defstruct ab "A structure for testing defstruct+." a b) (define-structure-let+ (ab) a b)) (addtest (let-plus-tests) test-defstruct+ (test-r/o-and-r/w (make-ab :a 3 :b 7) ((aa 3) (bb 7)) (&ab-r/o aa bb) (&ab aa bb))) (addtest (let-plus-tests) test-fwrap (let+ (((&fwrap add) (lambda (a b &key (c 0)) (+ a b c)))) (ensure-same (add 1 2) 3) (ensure-same (add 1 2 :c 3) 6))) (addtest (let-plus-tests) test-flet+ (ensure-same (let+ (((&flet+ add3 (#(a b c)) (+ a b c)))) (add3 #(1 2 3))) 6)) (addtest (let-plus-tests) test-labels+ (let+ (((&labels+ foo ((a . b)) (if a (foo (cons (cdr a) (cons (car a) b))) b)))) (ensure-same (foo '((1 2 3) . nil)) '(3 2 1))) (ensure-no-warning (compile nil '(lambda (x) (let+ (((&labels+ foo (&ign)))) (foo x)))))) (addtest (let-plus-tests) test-defun+ (defun+ foo ((a . b)) "bar" (+ a b)) (ensure-same (foo '(1 . 2)) 3) (ensure-same (documentation 'foo 'function) "bar" :test #'string=) (let ((expansion (macroexpand-1 '(defun+ foo ((a . b)) "foo docstring" (declare (type integer a b)) (+ a b))))) (ensure-same 'defun (first expansion)) (ensure-same 'foo (second expansion)) (ensure-same "foo docstring" (fourth expansion)) (let ((let+-expansion (fifth expansion))) (ensure-same 'let+ (first let+-expansion)) (ensure-same '(declare (type integer a b)) (third let+-expansion)) (ensure-same '(+ a b) (fourth let+-expansion))))) (addtest (let-plus-tests) test-&warning (ensure-warning (macroexpand '(let+ ((&foo 1)) &foo))) (ensure-warning (macroexpand '(let+ ((&foo)) &foo))) (ensure-warning (macroexpand '(let+ (&foo) &foo)))) (addtest (let-plus-tests) test-empty-let+ (ensure-same (let+ nil 1) 1 :test #'eql)) (addtest (let-plus-tests) test-recursive-let+ (ensure-same (let+ ((#((a . b)) (vector (cons 1 2)))) (values a b)) (values 1 2)) ;; (ensure-same (let+ (((#(a) #(b c)) (cons #(1) #(2 3)))) ;; (values a b c)) ;; (values 1 2 3)) ) (addtest (let-plus-tests) test-complex (let ((c #C(3 5))) (let+ (((&complex x y) c)) (ensure-same x 3) (ensure-same y 5)))) (addtest (let-plus-tests) test-nil (ensure (let+ ((() '())) t)) (ensure-error (let+ ((() '(1))))) ;; (ensure-error (let+ ((() 1)))) ) (addtest (let-plus-tests) test-empty-let+-with-declaration (ensure-same (let+ () (declare (optimize speed)) t) t) (ensure-same (let+ ((&ign 1)) (declare (optimize speed)) t) t))