@@ 4,4 4,177 @@
;;; SPDX-License-Identifier: MPL-2.0
;;; Home Page: <https://git.sr.ht/~nytpu/dbc-scheme>
-;; TODO
+;; Given a list \var{l1} of promises and a list \var{l2} of quoted
+;; representations of those promises, verify that all of \var{l1}'s
+;; expressions are true, otherwise raise an exception with the given
+;; \var{msg} and the quoted version of the expression consed with the
+;; \var{addl-irritants}.
+(define (execute-contracts l1 l2 msg . addl-irritants)
+ (every
+ (lambda (expr quoted)
+ (if (force expr)
+ #t
+ (error msg
+ (append (if (list? quoted) quoted (list quoted))
+ addl-irritants))))
+ l1 l2))
+
+;; Transform a series of expressions into a list of \scheme{quote}d
+;; expressions.
+(define-syntax quotify
+ (syntax-rules ()
+ ((_ q ...)
+ (list (quote q) ...))))
+
+;; \scheme{verify-contracts} will verify that a series of expressions returns
+;; true, otherwise raising an exception with the quoted representation of the
+;; expression that failed.
+;;
+;; It has two variants:
+;; \itemlist[
+;; \item{
+;; Given a series of predicates taking a single argument and a "return"
+;; value \var{r}, evaluate each predicate with \var{r} and ensure all of
+;; them return true.
+;; }
+;; \item{
+;; Given a series of thunk expressions, verify that all of them return
+;; true.
+;; }
+;; ]
+(define-syntax verify-contracts
+ (syntax-rules (return)
+ ((_) #t)
+ ((_ (return r)) r)
+ ((_ c1 c2 ... (return r))
+ (let ((return r))
+ (if (execute-contracts (list (delay (c1 return))
+ (delay (c2 return)) ...)
+ (quotify c1 c2 ...)
+ "postcondition assertion violation"
+ return)
+ return
+ #f)))
+ ((_ c1 c2 ...)
+ (execute-contracts (list (delay c1) (delay c2) ...)
+ (quotify c1 c2 ...)
+ "precondition assertion violation"))))
+
+;;> Define a lambda expression as with \scheme{lambda}, however add
+;;> precondition and/or postcondition contracts.
+;;>
+;;> Given a \scheme{(pre contracts ...)}, where \var{contracts ...} is zero
+;;> or more thunk expressions, evaluate each thunk sequentially in the same
+;;> scope as the function body. If a thunk returns \scheme{#f}, then raise
+;;> an exception noting a precondition violation with the irritants being the
+;;> (\scheme{quote}d) thunk that failed.
+;;>
+;;> Given a \scheme{(post contracts ...)}, where \var{contracts ...} is zero
+;;> or more predicate functions taking a single argument, evaluate each
+;;> expression sequentially given the return value of the function; in the
+;;> same scope as the function body and after the function has completed
+;;> evaluation. If a predicate returns \scheme{#f}, then raise an exception
+;;> noting a postcondition violation with the irritants being the expression
+;;> that failed and the original return value.
+;;>
+;;> The contracts are short-circuit evaluated, i.e. they are evaluated
+;;> sequentially from left to right and evaluation terminates if a contract
+;;> fails. As such, it is recommended to have contracts sorted from most
+;;> general to most specific.
+;;>
+;;> \bold{Example:}
+;;>
+;;> \schemeblock{
+;;> (define add-map
+;;> (lambda-contract (add-map . lists)
+;;> (pre (every list? lists)
+;;> (>= (length lists) 2)
+;;> (apply = (map length lists)))
+;;> (post list?
+;;> (lambda (ret) (= (length ret) (length (car lists)))))
+;;> (apply map + lists)))
+;;> }
+;;> \scheme{(add-map '(1 2 3) '(4 5 6))} => \scheme{(5 7 9)}.
+;;>
+;;> \scheme{(add-map '(1 2 3) '(4 5))} => \code{Error: precondition assertion violation: (apply = (map length lists))}.
+;;>
+;;> \scheme{(add-map '(1 2 3) 1)} => \code{Error: precondition assertion violation: (every list? lists)}
+;;>
+;;> \scheme{(add-map '(1 2 3))} => \code{Error: precondition assertion violation: (>= (length lists) 2)}
+;;>
+;;> \bold{Known issues:}
+;;>
+;;> Postcondition contract checking will not operate on functions returning
+;;> multiple values. If the postcondition is omitted then the multiple
+;;> values will be returned as normal. Precondition contracts are not
+;;> affected.
+(define-syntax lambda-contract
+ (syntax-rules (pre post)
+ ((_ formals (pre c1 ...) (post c2 ...) e1 e2 ...)
+ (lambda formals
+ (verify-contracts
+ c2 ...
+ (return (begin (verify-contracts c1 ...)
+ e1 e2 ...)))))
+ ((_ formals (post c2 ...) (pre c1 ...) e1 e2 ...)
+ (lambda-contract formals (pre c1 ...) (post c2 ...) e1 e2 ...))
+ ((_ formals (pre c ...) e1 e2 ...)
+ (lambda-contract params (pre c ...) (post) e1 e2 ...))
+ ((_ formals (post c ...) e1 e2 ...)
+ (lambda-contract formals (pre) (post c ...) e1 e2 ...))
+ ((_ formals e1 e2 ...)
+ (lambda formals e1 e2 ...))))
+
+;;> Define a top-level function binding as with \scheme{define}'s "defun"
+;;> syntax, however use \scheme{lambda-contract} instead of a regular lambda.
+;;>
+;;> The semantics of the pre- and post-conditions is equivalent to
+;;> \scheme{define-lambda.}
+;;>
+;;> \bold{Example:}
+;;>
+;;> \schemeblock{
+;;> (define-contract (add-map . lists)
+;;> (pre (every list? lists)
+;;> (>= (length lists) 2)
+;;> (apply = (map length lists)))
+;;> (post list?
+;;> (lambda (ret) (= (length ret) (length (car lists)))))
+;;> (apply map + lists))
+;;> }
+;;> The above is equivalent to:
+;;> \schemeblock{
+;;> (define add-map
+;;> (lambda-contract lists
+;;> (pre (every list? lists)
+;;> (>= (length lists) 2)
+;;> (apply = (map length lists)))
+;;> (post list?
+;;> (lambda (ret) (= (length ret) (length (car lists)))))
+;;> (apply map + lists)))
+;;? }
+;;>
+;;> \bold{Known issues:}
+;;>
+;;> Binding to a \scheme{lambda} expression instead of using the "defun"
+;;> syntax breaks contract checking. Use a plain \scheme{define} with
+;;> \scheme{lambda-contract} instead.
+;;>
+;;> See \scheme{lambda-contract} for issues with the contract system in
+;;> general.
+(define-syntax define-contract
+ (syntax-rules (pre post)
+ ((_ formals (pre c1 ...) (post c2 ...) e1 e2 ...)
+ (define formals
+ (verify-contracts
+ c2 ...
+ (return (begin (verify-contracts c1 ...)
+ e1 e2 ...)))))
+ ((_ formals (post c2 ...) (pre c1 ...) e1 e2 ...)
+ (define-contract formals (pre c1 ...) (post c2 ...) e1 e2 ...))
+ ((_ formals (pre c ...) e1 e2 ...)
+ (define-contract formals (pre c ...) (post) e1 e2 ...))
+ ((_ formals (post c ...) e1 e2 ...)
+ (define-contract formals (pre) (post c ...) e1 e2 ...))
+ ((_ formals e1 e2 ...)
+ (define formals e1 e2 ...))))
@@ 5,7 5,13 @@
;;; Home Page: <https://git.sr.ht/~nytpu/dbc-scheme>
;;> TODO
+;;>
+;;> View additional details at the
+;;> \hyperlink[https://git.sr.ht/~nytpu/dbc-scheme]{project's repository}.
(define-library (nytpu contracts)
- #;(export)
- #;(import)
+ (export lambda-contract
+ define-contract)
+ (import (scheme base)
+ (scheme lazy)
+ (srfi 1))
(include "contracts.scm"))