(chapter-start ‘flow-control “constructs for looping and error management”)

(mac on-err (handler . body)

; executes 'body. If an error is raised, executes 'handler. Inside
; 'handler, the parameter 'errors is a list of error messages extracted from
; the sequence of errors that led here (Exception#cause in ruby or Throwable.getCause() in java)
`(handle-error (fn (errors traces) ,handler)
               (fn ()              ,@body)))

;; executes ‘body. Afterwards, executes ’protection. ;; ‘protection is always executed even if there is an error. (mac ensure (protection . body)

`(ensuring (fn () ,protection)
           (fn ()    ,@body)))

;; tests ‘test, as long as ’test is non-nil, ;; repeatedly executes ‘body (mac while (test . body)

`(loop ,test (do ,@body)))

;; execute ‘start, then for as long as ’test returns non-nil, ;; execute ‘body and ’update (mac looping (start test update . body)

`(do
   ,start
   (while ,test ,@body ,update)))

;; assign ‘init to ’v, then execute ‘body ’max times, ;; incrementing ‘v at each iteration (mac for (v init max . body)

(w/uniq (gi gm)
  `(with (,v nil ,gi ,init ,gm (+ ,max 1))
     (looping (assign ,v ,gi) (< ,v ,gm) (assign ,v (+ ,v 1))
       ,@body))))

;; return a new function which is the original function with ;; the given args1 already applied ;; arguments to the new function are whatever arguments remain ;; for the old function ;; Could be (mac curry things ‘(fn args (apply ,@things args))) but less readable (mac curry (f . args0)

`(fn args
     (apply ,f ,@args0 args)))

;; like curry, but the returned function takes only a single arg (assumes all ;; args but one are provided here) ;; Could be (mac curry1 things ‘(fn (arg) (,@things arg))) but less readable (mac curry1 (f . args)

`(fn (arg)
     (,f ,@args arg)))

;; if ,key is already in ,hsh - return the associated value. ;; if ,key is not already in ,hsh - evaluate ,val, store the result ;; under ,key in ,hsh, and return it (mac cache-get (hsh key val)

(w/uniq (h k)
  `(with (,h ,hsh ,k ,key)
     (or= (hash-get ,h ,k) ,val))))

;; same as ‘def, but caches the result, keyed on args, so for a given set of args the result ;; is only ever calculated once ;; ;; WARNING: in current incarnation, won’t work with destructuring args (mac defmemo (name args . body)

(let forms (filter-forms (build-def-hash) body)
  (w/uniq h
          `(let ,h (hash)
             (def ,name ,args
               ,@(map (fn (c) (cons 'comment c)) forms.comment)
               ,@(map (fn (c) (cons 'chapter c)) forms.chapter)
               (cache-get ,h (list ,@args) (do ,@(hash-get forms nil))))))))

;; memoises a function expression ;; args: the function arguments ;; body: a list of function body expressions ;; next: a function to assemble a function expression from ‘args and ’body ;; returns whatever ‘next returns, where ’body is memoised based on the value of ‘args (def memoise (args body next)

(let (memo newbody) (filter-remove '#memoise body)
  (if memo
      (w/uniq h
              `(let ,h (hash) ,(next args `((cache-get ,h (list ,@args) (do ,@newbody))))))
      (next args body))))

(assign fun/expanders

(cons
 (cons 'memoise memoise) fun/expanders))