LCP: Split up PROCESS-FILE
Summary: Split up `process-file` so that looking at the generated code for an LCP form is easier from the REPL. `process-lcp`, `generate-hpp` and `generate-cpp` now perform the generation of C++ code, but take a list of "C++ elements" (the results of LCP forms) as input and write their output to streams. They do no reading/evaluating of LCP forms of their own. `read-lcp` and `read-lcp-file` are used to read and evaluate a stream of LCP forms. The latter is a specialized version for file streams which also reports the position of the form within the file when an error happens. `process-lcp-string` and `process-lcp-file` are convenient wrappers around the main functionality that take a string (file) and output to strings (files). Using `read-lcp` and `read-lcp-file` they process LCP forms and pass them off to `process-lcp` for code generation. Reviewers: mtomic, teon.banek Reviewed By: teon.banek Subscribers: pullbot Differential Revision: https://phabricator.memgraph.io/D2097
This commit is contained in:
@@ -225,6 +225,10 @@ included in the method declaration."
|
|||||||
${returns} ${namespace}${method-name}(${args}) ${const} ${override} ${delete}
|
${returns} ${namespace}${method-name}(${args}) ${const} ${override} ${delete}
|
||||||
cpp<#)))
|
cpp<#)))
|
||||||
|
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
;;;
|
||||||
|
;;; C++ elements
|
||||||
|
|
||||||
(defstruct cpp-list
|
(defstruct cpp-list
|
||||||
values)
|
values)
|
||||||
|
|
||||||
@@ -278,71 +282,64 @@ included in the method declaration."
|
|||||||
(append *cpp-impl* (mapcar (lambda (cpp) (cons namespaces cpp))
|
(append *cpp-impl* (mapcar (lambda (cpp) (cons namespaces cpp))
|
||||||
args)))))
|
args)))))
|
||||||
|
|
||||||
(defun read-lcp (filepath)
|
(defun process-lcp (cpp-elements &key cpp hpp lcp-file hpp-file cpp-file slk-serialize-p)
|
||||||
"Read the file FILEPATH and return a list of C++ meta information that should
|
"Process a list of C++ elements.
|
||||||
be formatted and output."
|
|
||||||
(with-open-file (in-stream filepath)
|
|
||||||
(let ((*readtable* (named-readtables:find-readtable 'lcp-syntax))
|
|
||||||
(stream-pos 0))
|
|
||||||
(handler-case
|
|
||||||
(loop :for form := (read-preserving-whitespace in-stream nil 'eof)
|
|
||||||
:until (eq form 'eof)
|
|
||||||
:for res := (handler-case (eval form)
|
|
||||||
(error (err)
|
|
||||||
;; Seek to the start of the stream.
|
|
||||||
(file-position in-stream 0)
|
|
||||||
(error "~%~A:~A: error:~2%~A~2%in:~2%~A"
|
|
||||||
(uiop:native-namestring filepath)
|
|
||||||
(count-newlines
|
|
||||||
in-stream
|
|
||||||
:stop-position (1+ stream-pos))
|
|
||||||
err form)))
|
|
||||||
:do (setf stream-pos (file-position in-stream))
|
|
||||||
:when (typep res '(or raw-cpp cpp-type cpp-list))
|
|
||||||
:collect res)
|
|
||||||
(end-of-file ()
|
|
||||||
;; Seek to the start of the stream.
|
|
||||||
(file-position in-stream 0)
|
|
||||||
(error "~%~A:~A:error: READ error, did you forget a closing ')'?"
|
|
||||||
(uiop:native-namestring filepath)
|
|
||||||
(count-newlines in-stream :stop-position (1+ stream-pos))))))))
|
|
||||||
|
|
||||||
(defun process-file (lcp-file &key slk-serialize)
|
To process a C++ element means to generate code for it and any of the
|
||||||
"Process a LCP-FILE and write the output to .hpp file in the same directory."
|
additional functionalities that have been specified.
|
||||||
(multiple-value-bind (filename extension)
|
|
||||||
(uiop:split-name-type lcp-file)
|
A C++ element can be:
|
||||||
(assert (string= (string-downcase extension) "lcp"))
|
|
||||||
(let ((hpp-file (concatenate 'string filename ".hpp"))
|
- an instance of CPP-CLASS or CPP-ENUM (e.g. returned by DEFINE-CLASS or
|
||||||
;; Unlike hpp, for cpp file use the full path. This allows us to
|
DEFINE-ENUM) -- specifies the definition of a C++ class or enum, along with
|
||||||
;; have our own accompanying .cpp files
|
any additional functionality
|
||||||
(cpp-file (concatenate 'string lcp-file ".cpp"))
|
|
||||||
(serializep slk-serialize)
|
- an instance of RAW-CPP -- specifies raw C++ code to include into the generated
|
||||||
;; Reset globals
|
code
|
||||||
(*cpp-inner-types* nil)
|
|
||||||
(*cpp-impl*)
|
- an instance of CPP-LIST -- specifies a list of C++ elements which are
|
||||||
;; Don't reset *cpp-classes* if we want to have support for
|
processed recursively
|
||||||
;; procesing multiple files.
|
|
||||||
;; (*cpp-classes* nil)
|
CPP and HPP are streams to which the generated hpp and cpp code will be output.
|
||||||
;; (*cpp-enums* nil)
|
Note that these can be any streams, not just file streams.
|
||||||
)
|
|
||||||
;; First read and evaluate the whole file, then output the evaluated
|
LCP-FILE, HPP-FILE and CPP-FILE are pathname designators that will be used for
|
||||||
;; cpp-code. This allows us to generate code which may rely on
|
the purposes of C++ includes or other such functionality. They can be omitted,
|
||||||
;; evaluation done after the code definition.
|
in which case the string \"<UNKNOWN>\" will be used.
|
||||||
(with-open-file (out hpp-file :direction :output :if-exists :supersede)
|
|
||||||
|
SLK-SERIALIZE-P determines whether SLK serialization code is generated."
|
||||||
|
(generate-hpp cpp-elements hpp
|
||||||
|
:lcp-file lcp-file
|
||||||
|
:hpp-file hpp-file
|
||||||
|
:cpp-file cpp-file
|
||||||
|
:slk-serialize-p slk-serialize-p)
|
||||||
|
;; NOTE: Some code may rely on the fact that the .cpp file is generated after
|
||||||
|
;; the .hpp.
|
||||||
|
(let ((*generating-cpp-impl-p* t))
|
||||||
|
(generate-cpp cpp-elements cpp
|
||||||
|
:lcp-file lcp-file
|
||||||
|
:hpp-file hpp-file
|
||||||
|
:cpp-file cpp-file
|
||||||
|
:slk-serialize-p slk-serialize-p)))
|
||||||
|
|
||||||
|
(defun generate-hpp (cpp-elements out &key lcp-file hpp-file cpp-file slk-serialize-p)
|
||||||
|
"Process a list of C++ elements to generate C++ header file code.
|
||||||
|
|
||||||
|
OUT is a stream to write the generated code to.
|
||||||
|
|
||||||
|
LCP-FILE, HPP-FILE, CPP-FILE and SLK-SERIALIZE-P are as in PROCESS-LCP."
|
||||||
|
(declare (ignore hpp-file cpp-file))
|
||||||
(format out "~@{// ~A~%~}" +emacs-read-only+ +vim-read-only+)
|
(format out "~@{// ~A~%~}" +emacs-read-only+ +vim-read-only+)
|
||||||
(format out "// DO NOT EDIT! Generated using LCP from '~A'~2%"
|
(format out "// DO NOT EDIT! Generated using LCP from '~A'~2%"
|
||||||
(file-namestring lcp-file))
|
(or lcp-file "<UNKNOWN>"))
|
||||||
(dolist (res (read-lcp lcp-file))
|
(dolist (res cpp-elements)
|
||||||
(write-line (cpp-code res) out)))
|
(write-line (cpp-code res) out))
|
||||||
(when *cpp-namespaces*
|
(alexandria:when-let
|
||||||
(error "Unclosed namespaces: ~A" (reverse *cpp-namespaces*)))
|
((types-for-slk
|
||||||
;; Collect types for serialization
|
(when slk-serialize-p
|
||||||
(let ((types-for-slk (when serializep
|
|
||||||
(append (remove-if (complement #'cpp-class-slk-opts) *cpp-classes*)
|
(append (remove-if (complement #'cpp-class-slk-opts) *cpp-classes*)
|
||||||
(remove-if (complement #'cpp-enum-serializep) *cpp-enums*)))))
|
(remove-if (complement #'cpp-enum-serializep) *cpp-enums*)))))
|
||||||
(when types-for-slk
|
|
||||||
;; Append top-level declarations for SLK serialization
|
;; Append top-level declarations for SLK serialization
|
||||||
(with-open-file (out hpp-file :direction :output :if-exists :append)
|
|
||||||
(terpri out)
|
(terpri out)
|
||||||
(write-line "// SLK serialization declarations" out)
|
(write-line "// SLK serialization declarations" out)
|
||||||
(write-line "#include \"slk/serialization.hpp\"" out)
|
(write-line "#include \"slk/serialization.hpp\"" out)
|
||||||
@@ -360,14 +357,18 @@ be formatted and output."
|
|||||||
(cpp-enum
|
(cpp-enum
|
||||||
(format out "~A;~%" (lcp.slk:save-function-declaration-for-enum type-for-slk))
|
(format out "~A;~%" (lcp.slk:save-function-declaration-for-enum type-for-slk))
|
||||||
(format out "~A;~%" (lcp.slk:load-function-declaration-for-enum type-for-slk))))))))
|
(format out "~A;~%" (lcp.slk:load-function-declaration-for-enum type-for-slk))))))))
|
||||||
;; Generate the .cpp file. Note, that some code may rely on the fact
|
|
||||||
;; that .cpp file is generated after .hpp.
|
(defun generate-cpp (cpp-elements out &key lcp-file hpp-file cpp-file slk-serialize-p)
|
||||||
(let ((*generating-cpp-impl-p* t))
|
"Process a list of C++ elements to generate C++ source file code.
|
||||||
(with-open-file (out cpp-file :direction :output :if-exists :supersede)
|
|
||||||
|
OUT is a stream to write the generated code to.
|
||||||
|
|
||||||
|
LCP-FILE, HPP-FILE, CPP-FILE and SLK-SERIALIZE-P are as in PROCESS-LCP."
|
||||||
|
(declare (ignore cpp-elements cpp-file))
|
||||||
(format out "~@{// ~A~%~}" +emacs-read-only+ +vim-read-only+)
|
(format out "~@{// ~A~%~}" +emacs-read-only+ +vim-read-only+)
|
||||||
(format out "// DO NOT EDIT! Generated using LCP from '~A'~2%"
|
(format out "// DO NOT EDIT! Generated using LCP from '~A'~2%"
|
||||||
(file-namestring lcp-file))
|
(or lcp-file "<UNKNOWN>"))
|
||||||
(format out "#include \"~A\"~2%" (file-namestring hpp-file))
|
(format out "#include \"~A\"~2%" (or hpp-file "<UNKNOWN>"))
|
||||||
;; First output the C++ code from the user
|
;; First output the C++ code from the user
|
||||||
(with-namespaced-output (out open-namespace)
|
(with-namespaced-output (out open-namespace)
|
||||||
(dolist (cpp *cpp-impl*)
|
(dolist (cpp *cpp-impl*)
|
||||||
@@ -375,7 +376,11 @@ be formatted and output."
|
|||||||
(open-namespace namespaces)
|
(open-namespace namespaces)
|
||||||
(write-line (cpp-code code) out))))
|
(write-line (cpp-code code) out))))
|
||||||
;; Generate SLK serialization
|
;; Generate SLK serialization
|
||||||
(when types-for-slk
|
(alexandria:when-let
|
||||||
|
((types-for-slk
|
||||||
|
(when slk-serialize-p
|
||||||
|
(append (remove-if (complement #'cpp-class-slk-opts) *cpp-classes*)
|
||||||
|
(remove-if (complement #'cpp-enum-serializep) *cpp-enums*)))))
|
||||||
(write-line "// Autogenerated SLK serialization code" out)
|
(write-line "// Autogenerated SLK serialization code" out)
|
||||||
(with-namespaced-output (out open-namespace)
|
(with-namespaced-output (out open-namespace)
|
||||||
(open-namespace '("slk"))
|
(open-namespace '("slk"))
|
||||||
@@ -392,4 +397,142 @@ be formatted and output."
|
|||||||
(write-line (lcp.slk:load-function-definition-for-class cpp-type) out)))
|
(write-line (lcp.slk:load-function-definition-for-class cpp-type) out)))
|
||||||
(cpp-enum
|
(cpp-enum
|
||||||
(write-line (lcp.slk:save-function-definition-for-enum cpp-type) out)
|
(write-line (lcp.slk:save-function-definition-for-enum cpp-type) out)
|
||||||
(write-line (lcp.slk:load-function-definition-for-enum cpp-type) out))))))))))))
|
(write-line (lcp.slk:load-function-definition-for-enum cpp-type) out)))))))
|
||||||
|
|
||||||
|
(defun read-lcp (stream)
|
||||||
|
"Read and evaluate LCP forms.
|
||||||
|
|
||||||
|
Forms are read from the stream STREAM and immediately evaluated, one by one. The
|
||||||
|
given stream is read until EOF is reached. In the case of a reading error, a
|
||||||
|
condition of type END-OF-FILE is signaled.
|
||||||
|
|
||||||
|
Return a list of results."
|
||||||
|
(let ((*readtable* (named-readtables:find-readtable 'lcp-syntax)))
|
||||||
|
(loop :for form := (read-preserving-whitespace stream nil 'eof)
|
||||||
|
:until (eq form 'eof)
|
||||||
|
:for res := (eval form)
|
||||||
|
:when (typep res '(or raw-cpp cpp-type cpp-list))
|
||||||
|
:collect res)))
|
||||||
|
|
||||||
|
(defun read-lcp-file (stream)
|
||||||
|
"Read and evaluate LCP forms from a file stream.
|
||||||
|
|
||||||
|
The behavior is just as in READ-LCP, except that STREAM must be a file stream.
|
||||||
|
The reported error messages will contain the name of the file and the line
|
||||||
|
number on which the error ocurred.
|
||||||
|
|
||||||
|
In case of a reading error, a condition of type ERROR is signaled reporting the
|
||||||
|
file and the line number of the erroneous form.
|
||||||
|
|
||||||
|
In case of an evaluation error, a condition of type ERROR is signaled reporting
|
||||||
|
the file and the line number of the erroneous form. Additionally, a restart
|
||||||
|
named DECLINE will be established around the newly signaled condition which can
|
||||||
|
be used by a handler to force the signalling of the original error instead.
|
||||||
|
|
||||||
|
Return a list of results."
|
||||||
|
(let ((filepath (pathname stream))
|
||||||
|
(*readtable* (named-readtables:find-readtable 'lcp-syntax))
|
||||||
|
(stream-pos 0))
|
||||||
|
(handler-case
|
||||||
|
(loop :for form := (read-preserving-whitespace stream nil 'eof)
|
||||||
|
:until (eq form 'eof)
|
||||||
|
:for res
|
||||||
|
:= (decline-case (eval form)
|
||||||
|
(error (err)
|
||||||
|
;; Seek to the start of the stream.
|
||||||
|
(file-position stream 0)
|
||||||
|
(error "~%~A:~A: error:~2%~A~2%in:~2%~A"
|
||||||
|
(uiop:native-namestring filepath)
|
||||||
|
(count-newlines
|
||||||
|
stream
|
||||||
|
:stop-position (1+ stream-pos))
|
||||||
|
err form)))
|
||||||
|
:do (setf stream-pos (file-position stream))
|
||||||
|
:when (typep res '(or raw-cpp cpp-type cpp-list))
|
||||||
|
:collect res)
|
||||||
|
(end-of-file ()
|
||||||
|
;; Seek to the start of the stream.
|
||||||
|
(file-position stream 0)
|
||||||
|
(error "~%~A:~A:error: READ error, did you forget a closing ')'?"
|
||||||
|
(uiop:native-namestring filepath)
|
||||||
|
(count-newlines stream :stop-position (1+ stream-pos)))))))
|
||||||
|
|
||||||
|
(defun process-lcp-string (string &key slk-serialize-p)
|
||||||
|
"Process the C++ elements produced by reading and evaluating LCP forms from
|
||||||
|
the string STRING.
|
||||||
|
|
||||||
|
SLK-SERIALIZE-P is as in PROCESS-LCP.
|
||||||
|
|
||||||
|
The generated code is returned as two values, both of which are strings. The
|
||||||
|
strings represent the C++ source file and C++ header file code respectively."
|
||||||
|
(with-retry-restart (reprocess-lcp-string "Reprocess the LCP string")
|
||||||
|
(restart-case
|
||||||
|
(let* (;; Reset globals that influence the evaluation of LCP forms
|
||||||
|
(*cpp-inner-types* :toplevel)
|
||||||
|
(*cpp-impl* '())
|
||||||
|
(*cpp-namespaces* '())
|
||||||
|
(cpp-elements (with-input-from-string (lcp string)
|
||||||
|
(read-lcp lcp))))
|
||||||
|
;; Check for unclosed namespaces in the LCP file
|
||||||
|
(when *cpp-namespaces*
|
||||||
|
(error "Unclosed namespaces: ~A" (reverse *cpp-namespaces*)))
|
||||||
|
;; Process the result
|
||||||
|
(with-output-to-string (hpp)
|
||||||
|
(with-output-to-string (cpp)
|
||||||
|
(process-lcp cpp-elements
|
||||||
|
:cpp cpp
|
||||||
|
:hpp hpp
|
||||||
|
:lcp-file "<PROCESS-LCP-STRING>"
|
||||||
|
:hpp-file "<PROCESS-LCP-STRING>"
|
||||||
|
:cpp-file "<PROCESS-LCP-STRING>"
|
||||||
|
:slk-serialize-p slk-serialize-p)
|
||||||
|
(return-from process-lcp-string
|
||||||
|
(values (get-output-stream-string hpp)
|
||||||
|
(get-output-stream-string cpp))))))
|
||||||
|
(clean-reprocess-lcp-string ()
|
||||||
|
:report "Reprocess the LCP string with a clean registry"
|
||||||
|
(setf *cpp-classes* '()
|
||||||
|
*cpp-enums* '())
|
||||||
|
(invoke-restart 'reprocess-lcp-string)))))
|
||||||
|
|
||||||
|
(defun process-lcp-file (lcp-file &key slk-serialize-p)
|
||||||
|
"Process the C++ elements produced by reading and evaluating LCP forms from
|
||||||
|
the file named by the pathname designator LCP-FILE.
|
||||||
|
|
||||||
|
SLK-SERIALIZE-P is as in PROCESS-LCP.
|
||||||
|
|
||||||
|
The generated code is written into two files. The files are in the same
|
||||||
|
directory as the LCP file. C++ source file code is written to
|
||||||
|
\"<LCP-FILENAME>.lcp.cpp\" while C++ header file code is written to
|
||||||
|
\"<LCP-FILENAME>.hpp\"."
|
||||||
|
(multiple-value-bind (filename extension)
|
||||||
|
(uiop:split-name-type lcp-file)
|
||||||
|
(assert (string= (string-downcase extension) "lcp"))
|
||||||
|
(let ((hpp-file (concatenate 'string filename ".hpp"))
|
||||||
|
(cpp-file (concatenate 'string lcp-file ".cpp")))
|
||||||
|
(with-retry-restart (reprocess-lcp-file "Reprocess the LCP file")
|
||||||
|
(restart-case
|
||||||
|
(let* (;; Reset globals that influence the evaluation of LCP forms
|
||||||
|
(*cpp-inner-types* :toplevel)
|
||||||
|
(*cpp-impl* '())
|
||||||
|
(*cpp-namespaces* '())
|
||||||
|
(cpp-elements (with-open-file (lcp lcp-file)
|
||||||
|
(read-lcp-file lcp))))
|
||||||
|
;; Check for unclosed namespaces in the LCP file
|
||||||
|
(when *cpp-namespaces*
|
||||||
|
(error "Unclosed namespaces: ~A" (reverse *cpp-namespaces*)))
|
||||||
|
;; Process the results
|
||||||
|
(with-open-file (hpp hpp-file :direction :output :if-exists :supersede)
|
||||||
|
(with-open-file (cpp cpp-file :direction :output :if-exists :supersede)
|
||||||
|
(process-lcp cpp-elements
|
||||||
|
:cpp cpp
|
||||||
|
:hpp hpp
|
||||||
|
:lcp-file lcp-file
|
||||||
|
:hpp-file hpp-file
|
||||||
|
:cpp-file cpp-file
|
||||||
|
:slk-serialize-p slk-serialize-p))))
|
||||||
|
(clean-reprocess-lcp-file ()
|
||||||
|
:report "Reprocess the LCP file with a clean registry"
|
||||||
|
(setf *cpp-classes* '()
|
||||||
|
*cpp-enums* '())
|
||||||
|
(invoke-restart 'reprocess-lcp-file)))))))
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
#:in-impl
|
#:in-impl
|
||||||
#:namespace
|
#:namespace
|
||||||
#:pop-namespace
|
#:pop-namespace
|
||||||
#:process-file
|
#:process-lcp-file
|
||||||
#:lcp-syntax))
|
#:lcp-syntax))
|
||||||
|
|
||||||
(defpackage #:lcp.slk
|
(defpackage #:lcp.slk
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
STRING and reports whether it was successful.
|
STRING and reports whether it was successful.
|
||||||
|
|
||||||
If EOF-ERROR-P is T and EOF is reached before all of the characters are matched,
|
If EOF-ERROR-P is T and EOF is reached before all of the characters are matched,
|
||||||
an END-OF-FILE error is signalled.
|
an END-OF-FILE error is signaled.
|
||||||
|
|
||||||
Otherwise, returns 2 values, SUCCESSP and COUNT. SUCCESSP is a boolean denoting
|
Otherwise, returns 2 values, SUCCESSP and COUNT. SUCCESSP is a boolean denoting
|
||||||
whether it was able to match all of the characters. COUNT is the number of
|
whether it was able to match all of the characters. COUNT is the number of
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
;;; IS-CONDITION which uses HANDLER-CASE to catch *any* condition (any subclass
|
;;; IS-CONDITION which uses HANDLER-CASE to catch *any* condition (any subclass
|
||||||
;;; of CONDITION). This is wrong, because not every condition is an error.
|
;;; of CONDITION). This is wrong, because not every condition is an error.
|
||||||
;;; Because of this, Prove used to catch LCP's warnings and would fail because
|
;;; Because of this, Prove used to catch LCP's warnings and would fail because
|
||||||
;;; an LCP error was expected (which would have been signalled).
|
;;; an LCP error was expected (which would have been signaled).
|
||||||
|
|
||||||
(in-package #:prove.test)
|
(in-package #:prove.test)
|
||||||
|
|
||||||
|
|||||||
@@ -103,3 +103,58 @@ WARNING has been established. The handler muffles the warning by calling
|
|||||||
MUFFLE-WARNING."
|
MUFFLE-WARNING."
|
||||||
`(handler-bind ((warning #'muffle-warning))
|
`(handler-bind ((warning #'muffle-warning))
|
||||||
,@body))
|
,@body))
|
||||||
|
|
||||||
|
(defmacro with-retry-restart ((restart format-string
|
||||||
|
&optional (format-arguments nil format-arguments-p))
|
||||||
|
&body body)
|
||||||
|
"Set up a restart as if by WITH-SIMPLE-RESTART, but with retry behavior. The
|
||||||
|
restart can be used to re-execute BODY an arbitrary number of times. The most
|
||||||
|
common use case is restarting the execution of some BODY until it succeeds, i.e.
|
||||||
|
finishes without any errors.
|
||||||
|
|
||||||
|
RESTART, FORMAT-STRING, FORMAT-ARGUMENTS and BODY are as in WITH-SIMPLE-RESTART.
|
||||||
|
The value produced by the implicit progn BODY is returned."
|
||||||
|
(alexandria:with-gensyms (block)
|
||||||
|
`(loop :named ,block :do
|
||||||
|
(with-simple-restart (,restart
|
||||||
|
,format-string
|
||||||
|
,@(when format-arguments-p format-arguments))
|
||||||
|
(return-from ,block
|
||||||
|
(progn ,@body))))))
|
||||||
|
|
||||||
|
(defun generate-decline-case-handlers (block clauses)
|
||||||
|
(loop :for (type lambda-list . body) :in clauses
|
||||||
|
:for c := (first lambda-list)
|
||||||
|
:for condition := (or c (gensym (string 'condition)))
|
||||||
|
:for fbody
|
||||||
|
:= `(,@(unless c
|
||||||
|
`((declare (ignore ,condition))))
|
||||||
|
(with-simple-restart (decline "Decline the condition")
|
||||||
|
(return-from ,block
|
||||||
|
(progn ,@body))))
|
||||||
|
:collect `(lambda (,condition) ,@fbody)))
|
||||||
|
|
||||||
|
(defmacro decline-case (form &body clauses)
|
||||||
|
"Bind a number of condition handlers but allow the handlers to decline the
|
||||||
|
handling at any time by invoking a special restart. The behavior is a hybrid of
|
||||||
|
HANDLER-BIND and HANDLER-CASE.
|
||||||
|
|
||||||
|
Once a handler has been found, its body is executed without performing a
|
||||||
|
transfer of control (HANDLER-BIND-like). However, if the execution of the body
|
||||||
|
finishes normally (without transferring control), control is transferred to the
|
||||||
|
first form after DECLINE-CASE (HANDLER-CASE-like).
|
||||||
|
|
||||||
|
The declining functionality is provided by establishing a restart named DECLINE
|
||||||
|
around the body of the handler. At any point within the body of the handler,
|
||||||
|
invoking the restart will decline the handling of the condition, transferring
|
||||||
|
control back to the signalling function in search of a new handler.
|
||||||
|
|
||||||
|
FORM and CLAUSES are as in HANDLER-CASE. The value produced by the form FORM is
|
||||||
|
returned in case a condition, if any, isn't handled. Otherwise, the value of the
|
||||||
|
last form within the body of the corresponding handler is returned."
|
||||||
|
(alexandria:with-gensyms (block)
|
||||||
|
(let ((types (mapcar #'first clauses))
|
||||||
|
(handlers (generate-decline-case-handlers block clauses)))
|
||||||
|
`(block ,block
|
||||||
|
(handler-bind (,@(mapcar #'list types handlers))
|
||||||
|
,form)))))
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ fi
|
|||||||
|
|
||||||
slk_serialize=""
|
slk_serialize=""
|
||||||
if [[ "$2" == "SLK_SERIALIZE" ]]; then
|
if [[ "$2" == "SLK_SERIALIZE" ]]; then
|
||||||
slk_serialize=":slk-serialize t"
|
slk_serialize=":slk-serialize-p t"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ $debug == "true" ]]; then
|
if [[ $debug == "true" ]]; then
|
||||||
@@ -37,14 +37,14 @@ if [[ $debug == "true" ]]; then
|
|||||||
(load \"${quicklisp_install_dir}/setup.lisp\")
|
(load \"${quicklisp_install_dir}/setup.lisp\")
|
||||||
(ql:quickload :lcp :silent t)
|
(ql:quickload :lcp :silent t)
|
||||||
(let ((*debugger-hook* #'lcp.debug:lcp-debugger-hook))
|
(let ((*debugger-hook* #'lcp.debug:lcp-debugger-hook))
|
||||||
(lcp:process-file \"$lcp_file\" $slk_serialize))
|
(lcp:process-lcp-file \"$lcp_file\" $slk_serialize))
|
||||||
" | sbcl --noinform --noprint
|
" | sbcl --noinform --noprint
|
||||||
else
|
else
|
||||||
echo \
|
echo \
|
||||||
"
|
"
|
||||||
(load \"${quicklisp_install_dir}/setup.lisp\")
|
(load \"${quicklisp_install_dir}/setup.lisp\")
|
||||||
(ql:quickload :lcp :silent t)
|
(ql:quickload :lcp :silent t)
|
||||||
(lcp:process-file \"$lcp_file\" $slk_serialize)
|
(lcp:process-lcp-file \"$lcp_file\" $slk_serialize)
|
||||||
" | sbcl --script
|
" | sbcl --script
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user