From dbd226d05d5d5800dd4462cae434de6ae3994fd9 Mon Sep 17 00:00:00 2001 From: Lovro Lugovic <lovro.lugovic@memgraph.io> Date: Fri, 24 May 2019 14:16:42 +0200 Subject: [PATCH] LCP: Minor improvements Summary: - Add a unified `generate_lcp` target - Simplify SLK-ERROR and CLONE-ERROR - Rework WITH-VARS - Expect CPP-CLASS, not CPP-TYPE - Fix CPP-TYPE-REFERENCE-P - Add CPP-GENSYM - Add util.lisp to LCP's source files in CMake - Make the CMake variable `lcp_src_files` a cache (persistent) variable - Add `lcp_src_files` as a dependency to `test_lcp` - Rename `lcp_compile` to `compile-lcp` - Fetch docstrings for CPP-NAME-* functions at run-time - Remove existing C++ entities on redefinition Reviewers: mtomic, teon.banek Reviewed By: teon.banek Subscribers: pullbot Differential Revision: https://phabricator.memgraph.io/D2094 --- src/CMakeLists.txt | 3 + src/lisp/CMakeLists.txt | 12 ++- src/lisp/clone.lisp | 30 ++++---- src/lisp/code-gen.lisp | 73 +++++++++++++++--- src/lisp/{lcp-compile => compile-lcp} | 1 + src/lisp/names.lisp | 27 +++---- src/lisp/slk.lisp | 14 ++-- src/lisp/test.lisp | 104 +++++++++++++------------- src/lisp/types.lisp | 27 ++++++- tests/unit/CMakeLists.txt | 2 +- 10 files changed, 185 insertions(+), 108 deletions(-) rename src/lisp/{lcp-compile => compile-lcp} (87%) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e2421b000..ec8d748a4 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -321,6 +321,9 @@ target_compile_definitions(mg-single-node-ha PUBLIC MG_SINGLE_NODE_HA) # END Memgraph Single Node High Availability # ---------------------------------------------------------------------------- +add_custom_target(generate_lcp) +add_dependencies(generate_lcp generate_lcp_single_node generate_lcp_single_node_ha generate_lcp_distributed) + string(TOLOWER ${CMAKE_BUILD_TYPE} lower_build_type) # STATIC library used to store key-value pairs diff --git a/src/lisp/CMakeLists.txt b/src/lisp/CMakeLists.txt index 91521081e..3a3be3125 100644 --- a/src/lisp/CMakeLists.txt +++ b/src/lisp/CMakeLists.txt @@ -3,7 +3,7 @@ # Don't forget to repeat this list below in `define_add_lcp`. set(lcp_src_files ${CMAKE_SOURCE_DIR}/src/lisp/lcp.asd - ${CMAKE_SOURCE_DIR}/src/lisp/lcp-compile + ${CMAKE_SOURCE_DIR}/src/lisp/compile-lcp ${CMAKE_SOURCE_DIR}/src/lisp/package.lisp ${CMAKE_SOURCE_DIR}/src/lisp/names.lisp ${CMAKE_SOURCE_DIR}/src/lisp/types.lisp @@ -13,12 +13,17 @@ set(lcp_src_files ${CMAKE_SOURCE_DIR}/src/lisp/lcp.lisp ${CMAKE_SOURCE_DIR}/src/lisp/debug.lisp ${CMAKE_SOURCE_DIR}/src/lisp/test.lisp + ${CMAKE_SOURCE_DIR}/src/lisp/util.lisp ${CMAKE_SOURCE_DIR}/tools/lcp) +# Make `lcp_src_files` a persistent (cache) variable so that +# tests/unit/CMakeLists.txt can see it. +set(lcp_src_files "${lcp_src_files}" CACHE INTERNAL "") + add_custom_target(lcp DEPENDS ${lcp_src_files} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} - COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/lcp-compile) + COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/compile-lcp) # Define `add_lcp` function named `name` for registering a lcp file for generation. # @@ -53,7 +58,7 @@ macro(define_add_lcp name main_src_files generated_lcp_files) # not visible when invoked in another file. set(lcp_src_files ${CMAKE_SOURCE_DIR}/src/lisp/lcp.asd - ${CMAKE_SOURCE_DIR}/src/lisp/lcp-compile + ${CMAKE_SOURCE_DIR}/src/lisp/compile-lcp ${CMAKE_SOURCE_DIR}/src/lisp/package.lisp ${CMAKE_SOURCE_DIR}/src/lisp/names.lisp ${CMAKE_SOURCE_DIR}/src/lisp/types.lisp @@ -63,6 +68,7 @@ macro(define_add_lcp name main_src_files generated_lcp_files) ${CMAKE_SOURCE_DIR}/src/lisp/lcp.lisp ${CMAKE_SOURCE_DIR}/src/lisp/debug.lisp ${CMAKE_SOURCE_DIR}/src/lisp/test.lisp + ${CMAKE_SOURCE_DIR}/src/lisp/util.lisp ${CMAKE_SOURCE_DIR}/tools/lcp) add_custom_command(OUTPUT ${h_file} ${cpp_file} COMMAND ${CMAKE_SOURCE_DIR}/tools/lcp ${lcp_file} ${slk_serialize} diff --git a/src/lisp/clone.lisp b/src/lisp/clone.lisp index 0f9e1c1c4..eac856c41 100644 --- a/src/lisp/clone.lisp +++ b/src/lisp/clone.lisp @@ -1,18 +1,14 @@ (in-package #:lcp.clone) -(define-condition clone-error (error) - ((message :type string :initarg :message :reader clone-error-message) - (format-args :type list :initform nil :initarg :format-args :reader clone-error-format-args)) - (:report (lambda (condition stream) - (apply #'format stream - (clone-error-message condition) - (clone-error-format-args condition))))) +(define-condition clone-error (simple-error) + ()) -(defun clone-error (message &rest format-args) - (error 'clone-error :message message :format-args format-args)) +(defun clone-error (format-control &rest format-arguments) + (error 'clone-error :format-control format-control + :format-arguments format-arguments)) (defun cloning-parent (cpp-class) - (check-type cpp-class lcp::cpp-type) + (check-type cpp-class lcp::cpp-class) (let ((supers (lcp::cpp-class-super-classes cpp-class)) (opts (lcp::cpp-class-clone-opts cpp-class))) (unless opts @@ -29,14 +25,14 @@ (car supers))))) (defun cloning-root (cpp-class) - (check-type cpp-class lcp::cpp-type) + (check-type cpp-class lcp::cpp-class) (let ((parent-class (cloning-parent cpp-class))) (if parent-class (cloning-root parent-class) cpp-class))) (defun members-for-cloning (cpp-class) - (check-type cpp-class lcp::cpp-type) + (check-type cpp-class lcp::cpp-class) (alexandria:flatten (reverse (loop :for current := cpp-class :then (cloning-parent current) @@ -117,7 +113,7 @@ (lcp::cpp-type-decl object-type)))))) (defun clone-vector (elem-type source-name dest-name &key args) - (lcp::with-vars ((loop-counter "i")) + (lcp::with-cpp-gensyms ((loop-counter "i")) (format nil "~A.resize(~A.size()); for (auto ~A = 0; ~A < ~A.size(); ++~A) { ~A }" @@ -129,7 +125,8 @@ :args args)))) (defun clone-map (key-type value-type source-name dest-name &key args) - (lcp::with-vars ((loop-var "kv") (entry-var "entry")) + (lcp::with-cpp-gensyms ((loop-var "kv") + (entry-var "entry")) (let ((entry-type (lcp::make-cpp-type "pair" :namespace '("std") @@ -146,7 +143,7 @@ dest-name entry-var)))) (defun clone-optional (value-type source-name dest-name &key args) - (lcp::with-vars ((value-var "value")) + (lcp::with-cpp-gensyms ((value-var "value")) (format nil "if (~A) { ~A ~A; @@ -165,7 +162,8 @@ dest-name))) (defun clone-pair (first-type second-type source-name dest-name &key args) - (lcp::with-vars ((first-var "first") (second-var "second")) + (lcp::with-cpp-gensyms ((first-var "first") + (second-var "second")) (with-output-to-string (cpp-out) (lcp::with-cpp-block-output (cpp-out) (format cpp-out diff --git a/src/lisp/code-gen.lisp b/src/lisp/code-gen.lisp index 2df2a153f..92df6f63f 100644 --- a/src/lisp/code-gen.lisp +++ b/src/lisp/code-gen.lisp @@ -65,23 +65,72 @@ context which binds OPEN-NAMESPACE-FUN function for opening namespaces." (cl-ppcre:regex-replace-all (string #\Newline) documentation (format nil "~%/// ")))) -(defvar *variable-idx* 0 "Used to generate unique variable names") +(defvar *cpp-gensym-counter* 0 "Used to generate unique variable names") -(defmacro with-vars (vars &body body) - "Generates unique variable names for use in generated code by -appending an index to desired variable names. Useful when generating -loops which might reuse counter names. +(defun cpp-gensym (&optional (prefix "var")) + "Generate a unique C++ name. -Usage example: - (with-vars ((loop-counter \"i\")) +The name is constructed by concatenating the string PREFIX with the current +value of *CPP-GENSYM-COUNTER*. Afterwards, the value of *CPP-GENSYM-COUNTER* is +incremented by 1. + +Despite the suggestive name \"gensym\", this function cannot guarantee that the +name is globally unique (because C++ has no concept equivalent to uninterned +symbols). The name is only unique across all of the names generated by the +function." + (prog1 (format nil "~A~A" prefix *cpp-gensym-counter*) + (incf *cpp-gensym-counter*))) + +(defmacro with-cpp-gensyms (vars &body body) + "Evaluate and return the result of the implicit progn BODY with the variables +specified within VARS bound to strings representing unique C++ names. + +Each element of VARS is either a symbol SYMBOL or a pair (SYMBOL NAME). Bare +symbols are equivalent to the pair (SYMBOL SYMBOL-NAME) where SYMBOL-NAME is the +result of (cpp-name-for-variable SYMBOL). + +Each pair (SYMBOL NAME) specifies a single unique C++ name. SYMBOL should be a +symbol naming the variable to which the generated C++ name will bound. NAME +should be a prefix that will be used to construct the name using CPP-GENSYM. + +Example: + + (with-cpp-gensyms ((loop-counter \"i\")) (format nil \"for (auto ~A = 0; ~A < v.size(); ++~A) { // do something }\" - loop-counter loop-counter loop-counter))" - `(let* ((*variable-idx* (1+ *variable-idx*)) - ,@(loop :for var :in vars :collecting - `(,(first var) - (format nil "~A~A" ,(second var) *variable-idx*)))) + loop-counter loop-counter loop-counter)) + + ;;; >> + ;;; for (auto i0 = 0; i0 < v.size(); ++i0) { + ;;; // do something + ;;; } + +Example: + + Assume *CPP-GENSYM-COUNTER* is 0. + + (defun gen1 () + (with-cpp-gensyms ((hello \"hello\")) + (format t \"int ~a;~%\" hello))) + + (defun gen2 () + (with-cpp-gensyms ((hello \"hello\")) + (gen1) + (format t \"int ~a;~%\" hello))) + + (gen2) + + ;;; >> + ;;; int hello1; + ;;; int hello0;" + `(let* (,@(mapcar + (lambda (var) + (destructuring-bind (sym &optional name) + (alexandria:ensure-list var) + (let ((name (or name (cpp-name-for-variable sym)))) + `(,sym (cpp-gensym ,name))))) + vars)) ,@body)) (defun cpp-member-reader-name (cpp-member) diff --git a/src/lisp/lcp-compile b/src/lisp/compile-lcp similarity index 87% rename from src/lisp/lcp-compile rename to src/lisp/compile-lcp index 2d224ebcf..28e6d24be 100755 --- a/src/lisp/lcp-compile +++ b/src/lisp/compile-lcp @@ -9,4 +9,5 @@ echo \ " (load \"${quicklisp_install_dir}/setup.lisp\") (ql:quickload :lcp :silent t) +(ql:quickload :lcp/test :silent t) " | sbcl --script diff --git a/src/lisp/names.lisp b/src/lisp/names.lisp index 27381f3ad..1bacc042d 100644 --- a/src/lisp/names.lisp +++ b/src/lisp/names.lisp @@ -154,19 +154,20 @@ The name function's name is of the form CPP-<CPP-OBJECT>-NAME. The namestring function's name is of the form ENSURE-NAMESTRING-FOR-<CPP-OBJECT>." - `(progn - (defun ,(alexandria:symbolicate 'cpp-name-for- cpp-object) - (thing &key from-style) - ,(documentation name-op 'function) - (check-type thing (or symbol string)) - (,name-op thing :from-style from-style)) - (defun ,(alexandria:symbolicate 'ensure-namestring-for- cpp-object) (thing) - ,(format nil +cpp-namestring-docstring+ - (string-downcase cpp-object) - (string-downcase cpp-object) - name-op) - (check-type thing (or symbol string)) - (ensure-namestring-for thing #',name-op)))) + (let ((cpp-name-for (alexandria:symbolicate 'cpp-name-for- cpp-object))) + `(progn + (defun ,cpp-name-for (thing &key from-style) + (check-type thing (or symbol string)) + (,name-op thing :from-style from-style)) + (setf (documentation ',cpp-name-for 'function) + (documentation ',name-op 'function)) + (defun ,(alexandria:symbolicate 'ensure-namestring-for- cpp-object) (thing) + ,(format nil +cpp-namestring-docstring+ + (string-downcase cpp-object) + (string-downcase cpp-object) + name-op) + (check-type thing (or symbol string)) + (ensure-namestring-for thing #',name-op))))) (define-cpp-name namespace lower-snake-case-name) (define-cpp-name class pascal-case-name) diff --git a/src/lisp/slk.lisp b/src/lisp/slk.lisp index a1f19cee5..216980c31 100644 --- a/src/lisp/slk.lisp +++ b/src/lisp/slk.lisp @@ -4,16 +4,12 @@ (in-package #:lcp.slk) -(define-condition slk-error (error) - ((message :type string :initarg :message :reader slk-error-message) - (format-args :type list :initform nil :initarg :format-args :reader slk-error-format-args)) - (:report (lambda (condition stream) - (apply #'format stream - (slk-error-message condition) - (slk-error-format-args condition))))) +(define-condition slk-error (simple-error) + ()) -(defun slk-error (message &rest format-args) - (error 'slk-error :message message :format-args format-args)) +(defun slk-error (format-control &rest format-arguments) + (error 'slk-error :format-control format-control + :format-arguments format-arguments)) ;;; CPP-CLASS serialization generation diff --git a/src/lisp/test.lisp b/src/lisp/test.lisp index cbeb626cf..8c8ca7f60 100644 --- a/src/lisp/test.lisp +++ b/src/lisp/test.lisp @@ -182,8 +182,10 @@ CPP-TYPE-DECL." '(#\Newline) (uiop:run-program "clang-format -style=file" :input s :output '(:string :stripped t))))) -(defun is-generated (got expected) - (is (clang-format got) (clang-format expected) :test #'string=)) +(defmacro is-generated (got expected) + `(is (let ((lcp::*cpp-gensym-counter* 0)) + (clang-format ,got)) + (clang-format ,expected) :test #'string=)) (defun undefine-cpp-types () (setf lcp::*cpp-classes* nil) @@ -784,9 +786,9 @@ CPP-TYPE-DECL." "Filter Clone(ExpressionStorage *exp_storage) const { Filter object; object.expressions_.resize(expressions_.size()); - for (auto i1 = 0; i1 < expressions_.size(); ++i1) { - object.expressions_[i1] = - expressions_[i1] ? expressions_[i1]->Clone(exp_storage) : nullptr; + for (auto i0 = 0; i0 < expressions_.size(); ++i0) { + object.expressions_[i0] = + expressions_[i0] ? expressions_[i0]->Clone(exp_storage) : nullptr; } return object; }"))) @@ -841,15 +843,15 @@ CPP-TYPE-DECL." "object.member_ = member_;") (single-member-test (member "std::vector<Klondike>") "object.member_.resize(member_.size()); - for (auto i1 = 0; i1 < member_.size(); ++i1) { - object.member_[i1] = member_[i1].Clone(); + for (auto i0 = 0; i0 < member_.size(); ++i0) { + object.member_[i0] = member_[i0].Clone(); }") (single-member-test (member "std::vector<std::vector<Klondike>>") "object.member_.resize(member_.size()); - for (auto i1 = 0; i1 < member_.size(); ++i1) { - object.member_[i1].resize(member_[i1].size()); - for (auto i2 = 0; i2 < member_[i1].size(); ++i2) { - object.member_[i1][i2] = member_[i1][i2].Clone(); + for (auto i0 = 0; i0 < member_.size(); ++i0) { + object.member_[i0].resize(member_[i0].size()); + for (auto i1 = 0; i1 < member_[i0].size(); ++i1) { + object.member_[i0][i1] = member_[i0][i1].Clone(); } }")) (subtest "optional" @@ -857,9 +859,9 @@ CPP-TYPE-DECL." "object.member_ = member_;") (single-member-test (member "std::optional<Klondike>") "if (member_) { - Klondike value1; - value1 = (*member_).Clone(); - object.member_.emplace(std::move(value1)); + Klondike value0; + value0 = (*member_).Clone(); + object.member_.emplace(std::move(value0)); } else { object.member_ = std::nullopt; }")) @@ -869,52 +871,52 @@ CPP-TYPE-DECL." (single-member-test (member "std::unordered_map<int32_t, std::unordered_map<int32_t, std::string>>") "object.member_ = member_;") (single-member-test (member "std::unordered_map<int32_t, Klondike>") - "for (const auto &kv1 : member_) { + "for (const auto &kv0 : member_) { std::pair<int32_t, Klondike> entry1; { int32_t first2; - first2 = kv1.first; - Klondike second2; - second2 = kv1.second.Clone(); - entry1 = std::make_pair(std::move(first2), std::move(second2)); + first2 = kv0.first; + Klondike second3; + second3 = kv0.second.Clone(); + entry1 = std::make_pair(std::move(first2), std::move(second3)); } object.member_.emplace(std::move(entry1)); }") (single-member-test (member "std::unordered_map<int32_t, std::unordered_map<int32_t, Klondike>>") - "for (const auto &kv1 : member_) { + "for (const auto &kv0 : member_) { std::pair<int32_t, std::unordered_map<int32_t, Klondike>> entry1; { int32_t first2; - first2 = kv1.first; - std::unordered_map<int32_t, Klondike> second2; - for (const auto &kv3 : kv1.second) { - std::pair<int32_t, Klondike> entry3; + first2 = kv0.first; + std::unordered_map<int32_t, Klondike> second3; + for (const auto &kv4 : kv0.second) { + std::pair<int32_t, Klondike> entry5; { - int32_t first4; - first4 = kv3.first; - Klondike second4; - second4 = kv3.second.Clone(); - entry3 = std::make_pair(std::move(first4), std::move(second4)); + int32_t first6; + first6 = kv4.first; + Klondike second7; + second7 = kv4.second.Clone(); + entry5 = std::make_pair(std::move(first6), std::move(second7)); } - second2.emplace(std::move(entry3)); + second3.emplace(std::move(entry5)); } - entry1 = std::make_pair(std::move(first2), std::move(second2)); + entry1 = std::make_pair(std::move(first2), std::move(second3)); } object.member_.emplace(std::move(entry1)); }") (single-member-test (member "std::unordered_map<Klondike, Klondike>") - "for (const auto &kv1 : member_) { + "for (const auto &kv0 : member_) { std::pair<Klondike, Klondike> entry1; { Klondike first2; - first2 = kv1.first.Clone(); - Klondike second2; - second2 = kv1.second.Clone(); - entry1 = std::make_pair(std::move(first2), std::move(second2)); + first2 = kv0.first.Clone(); + Klondike second3; + second3 = kv0.second.Clone(); + entry1 = std::make_pair(std::move(first2), std::move(second3)); } object.member_.emplace(std::move(entry1)); @@ -924,42 +926,42 @@ CPP-TYPE-DECL." "object.member_ = member_;") (single-member-test (member "std::pair<int32_t, Klondike>") "{ - int32_t first1; - first1 = member_.first; + int32_t first0; + first0 = member_.first; Klondike second1; second1 = member_.second.Clone(); - object.member_ = std::make_pair(std::move(first1), std::move(second1)); + object.member_ = std::make_pair(std::move(first0), std::move(second1)); }") (single-member-test (member "std::pair<Klondike, int32_t>") "{ - Klondike first1; - first1 = member_.first.Clone(); + Klondike first0; + first0 = member_.first.Clone(); int32_t second1; second1 = member_.second; - object.member_ = std::make_pair(std::move(first1), std::move(second1)); + object.member_ = std::make_pair(std::move(first0), std::move(second1)); }") (single-member-test (member "std::pair<Klondike, Klondike>") "{ - Klondike first1; - first1 = member_.first.Clone(); + Klondike first0; + first0 = member_.first.Clone(); Klondike second1; second1 = member_.second.Clone(); - object.member_ = std::make_pair(std::move(first1), std::move(second1)); + object.member_ = std::make_pair(std::move(first0), std::move(second1)); }") (single-member-test (member "std::pair<std::string, std::pair<int32_t, Klondike>>") "{ - std::string first1; - first1 = member_.first; + std::string first0; + first0 = member_.first; std::pair<int32_t, Klondike> second1; { int32_t first2; first2 = member_.second.first; - Klondike second2; - second2 = member_.second.second.Clone(); - second1 = std::make_pair(std::move(first2), std::move(second2)); + Klondike second3; + second3 = member_.second.second.Clone(); + second1 = std::make_pair(std::move(first2), std::move(second3)); } - object.member_ = std::make_pair(std::move(first1), std::move(second1)); + object.member_ = std::make_pair(std::move(first0), std::move(second1)); }")) (subtest "pointers" (single-member-test (member "Klondike *") diff --git a/src/lisp/types.lisp b/src/lisp/types.lisp index 8df9fd236..c13b44da8 100644 --- a/src/lisp/types.lisp +++ b/src/lisp/types.lisp @@ -825,7 +825,7 @@ not an instance of UNSUPPORTED-CPP-TYPE)." (defun cpp-type-reference-p (cpp-type) "Test whether CPP-TYPE represents a reference type." (check-type cpp-type cpp-type) - (string= (cpp-type-name cpp-type) "*")) + (string= (cpp-type-name cpp-type) "&")) (defun cpp-type-smart-pointer-p (cpp-type) "Test whether CPP-TYPE represents a smart pointer type." @@ -1038,10 +1038,26 @@ defined.") "A list of strings naming the enclosing classes of the current class being defined. The names are ordered from outermost to innermost enclosing class.") +(defun cons-or-replace (element list &key (test #'eql) (key #'identity)) + "Cons the ELEMENT to the LIST and remove existing elements. + +All elements that compare equal (under TEST) with ELEMENT are removed. KEY will +be applied to each element of LIST before calling TEST." + (cons element (remove-if (lambda (other) (funcall test element other)) + list :key key))) + (defun register-enum (cpp-enum) + "Register the given CPP-ENUM instance with the enum registry." (check-type cpp-enum cpp-enum) (prog1 cpp-enum - (push cpp-enum *cpp-enums*) + ;; Add or redefine the enum. + (setf *cpp-enums* + (cons cpp-enum + (delete-if + (lambda (other) + (string= (cpp-type-decl cpp-enum) (cpp-type-decl other))) + *cpp-enums*))) + ;; Add to the parent's inner types. (unless (eq *cpp-inner-types* :toplevel) (push cpp-enum *cpp-inner-types*)))) @@ -1050,7 +1066,12 @@ defined. The names are ordered from outermost to innermost enclosing class.") (check-type cpp-class cpp-class) (prog1 cpp-class ;; Add or redefine the class. - (push cpp-class *cpp-classes*) + (setf *cpp-classes* + (cons cpp-class + (delete-if + (lambda (other) + (string= (cpp-type-decl cpp-class) (cpp-type-decl other))) + *cpp-classes*))) ;; Add to the parent's inner types. (unless (eq *cpp-inner-types* :toplevel) (push cpp-class *cpp-inner-types*)))) diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index 5f8fa7fa6..deac0accf 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -378,7 +378,7 @@ target_link_libraries(${test_prefix}auth mg-auth kvstore_lib) add_custom_command( OUTPUT test_lcp - DEPENDS lcp test_lcp.lisp + DEPENDS ${lcp_src_files} lcp test_lcp.lisp COMMAND sbcl --script ${CMAKE_CURRENT_SOURCE_DIR}/test_lcp.lisp) add_custom_target(test_lcp ALL DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/test_lcp) add_test(test_lcp ${CMAKE_CURRENT_BINARY_DIR}/test_lcp)