| | 243 | (defun find-doc (body) |
|---|
| | 244 | "Given a function definition BODY, extract the docstring, if any. |
|---|
| | 245 | Skips over any declarations that precede the docstring. See also CLHS |
|---|
| | 246 | 3.4.11" |
|---|
| | 247 | (do ((body body (cdr body))) |
|---|
| | 248 | ((or (not (listp (car body))) |
|---|
| | 249 | (not (eq 'declare (caar body)))) |
|---|
| | 250 | (when (and (stringp (car body)) |
|---|
| | 251 | (cdr body)) |
|---|
| | 252 | (car body))))) |
|---|
| | 253 | |
|---|
| | 254 | (defun insert-after-declarations (body forms-to-insert) |
|---|
| | 255 | "Given a function definition body, insert FORMS-TO-INSERT after all |
|---|
| | 256 | declarations and documentation in BODY." |
|---|
| | 257 | (loop for rest on body |
|---|
| | 258 | for form = (car rest) |
|---|
| | 259 | with decls |
|---|
| | 260 | with doc |
|---|
| | 261 | while (or (and (listp form) (eq 'declare (car form))) |
|---|
| | 262 | (and (not doc) (cdr rest) (stringp form))) |
|---|
| | 263 | when (stringp form) |
|---|
| | 264 | do (setf doc form) |
|---|
| | 265 | do (push form decls) |
|---|
| | 266 | finally (return-from insert-after-declarations (append (nreverse decls) forms-to-insert rest)))) |
|---|
| | 267 | |
|---|
| | 268 | (defun make-args (args) |
|---|
| | 269 | "Parse the lambda list ARGS, returning a list that contains the |
|---|
| | 270 | arguments in the lambda list prepared so that the list can be applied |
|---|
| | 271 | to a function accepting that lambda list. |
|---|
| | 272 | |
|---|
| | 273 | For example: |
|---|
| | 274 | |
|---|
| | 275 | (MAKE-ARGS '(A B &OPTIONAL C &REST D &KEY E F)) => (A B C :E E :F F) |
|---|
| | 276 | |
|---|
| | 277 | It is used to forward arguments to a transaction wrapper generated by |
|---|
| | 278 | DEFTRANSACTION to the actual transaction so that the wrapper function |
|---|
| | 279 | can be declared with the lambda list of the transaction function |
|---|
| | 280 | itself," |
|---|
| | 281 | (do ((args args (cdr args)) |
|---|
| | 282 | result |
|---|
| | 283 | in-keywords-p) |
|---|
| | 284 | ((not args) |
|---|
| | 285 | (nreverse result)) |
|---|
| | 286 | (let ((arg (funcall (if (listp (car args)) #'caar #'car) args))) |
|---|
| | 287 | (cond |
|---|
| | 288 | ((eql #\& (aref (symbol-name arg) 0)) |
|---|
| | 289 | (case arg |
|---|
| | 290 | (&optional) |
|---|
| | 291 | (&rest (setf args (cdr args))) ; skip argument, too |
|---|
| | 292 | (&key (setf in-keywords-p t)) |
|---|
| | 293 | (otherwise (error "unsupported lambda list option ~A in DEFTRANSACTION" arg)))) |
|---|
| | 294 | (t |
|---|
| | 295 | (when in-keywords-p |
|---|
| | 296 | (push (intern (symbol-name arg) :keyword) result)) |
|---|
| | 297 | (push arg result)))))) |
|---|
| | 298 | |
|---|
| 244 | | "Define a transaction function tx-NAME and a function NAME executing tx-NAME in the context |
|---|
| 245 | | of the current store. The arguments to NAME will be serialized to the transaction-log, and |
|---|
| 246 | | should must be supported by the binary encoder. tx-NAME will be called during a roll-forward." |
|---|
| 247 | | (dolist (arg args) |
|---|
| 248 | | (when (listp arg) |
|---|
| 249 | | (error "can't have argument defaults in transaction declaration for transaction ~A, please implement a wrapper" name))) |
|---|
| 250 | | (let ((args-name (gensym)) |
|---|
| 251 | | (tx-name (intern (string-upcase (concatenate 'string "tx-" (symbol-name name))) |
|---|
| 252 | | (symbol-package name)))) |
|---|
| 253 | | `(progn |
|---|
| 254 | | (defun ,tx-name ,args |
|---|
| 255 | | (unless (in-transaction-p) |
|---|
| 256 | | (error 'not-in-transaction)) |
|---|
| 257 | | ,@body) |
|---|
| 258 | | (defun ,name (&rest ,args-name) |
|---|
| 259 | | (execute (make-instance 'transaction |
|---|
| 260 | | :function-symbol ',tx-name |
|---|
| 261 | | :timestamp (get-universal-time) |
|---|
| 262 | | :args ,args-name)))))) |
|---|
| | 300 | "Define a transaction function tx-NAME and a function NAME executing |
|---|
| | 301 | tx-NAME in the context of the current store. The arguments to NAME |
|---|
| | 302 | will be serialized to the transaction-log, and should must be |
|---|
| | 303 | supported by the binary encoder. tx-NAME will be called during a |
|---|
| | 304 | roll-forward." |
|---|
| | 305 | (let ((name name) |
|---|
| | 306 | (args args) |
|---|
| | 307 | (body body)) |
|---|
| | 308 | (dolist (arg args) |
|---|
| | 309 | (when (listp arg) |
|---|
| | 310 | (error "can't have argument defaults in transaction declaration for transaction ~A, please implement a wrapper" name))) |
|---|
| | 311 | (let ((tx-name (intern (format nil "TX-~A" name) |
|---|
| | 312 | (symbol-package name)))) |
|---|
| | 313 | `(progn |
|---|
| | 314 | (defun ,tx-name ,args |
|---|
| | 315 | ,@(insert-after-declarations body |
|---|
| | 316 | '((unless (in-transaction-p) |
|---|
| | 317 | (error 'not-in-transaction))))) |
|---|
| | 318 | (defun ,name ,args |
|---|
| | 319 | ,@(let ((doc (find-doc body))) |
|---|
| | 320 | (when doc (list (format nil "[Transaction function wrapper ~A invokes a store transaction]~%~A" name doc)))) |
|---|
| | 321 | ,@(let ((rest (member '&rest args))) |
|---|
| | 322 | (when rest `((declare (ignore ,(second rest)))))) |
|---|
| | 323 | (execute (make-instance 'transaction |
|---|
| | 324 | :function-symbol ',tx-name |
|---|
| | 325 | :timestamp (get-universal-time) |
|---|
| | 326 | :args (list ,@(make-args args))))))))) |
|---|