diff --git a/src/lisp/CMakeLists.txt b/src/lisp/CMakeLists.txt index b84f14660..d0019a40c 100644 --- a/src/lisp/CMakeLists.txt +++ b/src/lisp/CMakeLists.txt @@ -5,6 +5,9 @@ set(lcp_src_files ${CMAKE_SOURCE_DIR}/src/lisp/lcp.asd ${CMAKE_SOURCE_DIR}/src/lisp/lcp-compile ${CMAKE_SOURCE_DIR}/src/lisp/package.lisp + ${CMAKE_SOURCE_DIR}/src/lisp/types.lisp + ${CMAKE_SOURCE_DIR}/src/lisp/code-gen.lisp + ${CMAKE_SOURCE_DIR}/src/lisp/slk.lisp ${CMAKE_SOURCE_DIR}/src/lisp/lcp.lisp ${CMAKE_SOURCE_DIR}/src/lisp/lcp-test.lisp ${CMAKE_SOURCE_DIR}/tools/lcp) @@ -53,6 +56,9 @@ macro(define_add_lcp name main_src_files generated_lcp_files) ${CMAKE_SOURCE_DIR}/src/lisp/lcp.asd ${CMAKE_SOURCE_DIR}/src/lisp/lcp-compile ${CMAKE_SOURCE_DIR}/src/lisp/package.lisp + ${CMAKE_SOURCE_DIR}/src/lisp/types.lisp + ${CMAKE_SOURCE_DIR}/src/lisp/code-gen.lisp + ${CMAKE_SOURCE_DIR}/src/lisp/slk.lisp ${CMAKE_SOURCE_DIR}/src/lisp/lcp.lisp ${CMAKE_SOURCE_DIR}/src/lisp/lcp-test.lisp ${CMAKE_SOURCE_DIR}/tools/lcp) diff --git a/src/lisp/code-gen.lisp b/src/lisp/code-gen.lisp new file mode 100644 index 000000000..abf955484 --- /dev/null +++ b/src/lisp/code-gen.lisp @@ -0,0 +1,19 @@ +;;;; This file contains common code for generating C++ code. + +(in-package #:lcp) + +(defun call-with-cpp-block-output (out fun &key semicolonp name) + "Surround the invocation of FUN by emitting '{' and '}' to OUT. If +SEMICOLONP is set, the closing '}' is suffixed with ';'. NAME is used to +prepend the starting block with a name, for example \"class MyClass\"." + (if name + (format out "~A {~%" name) + (write-line "{" out)) + (funcall fun) + (if semicolonp (write-line "};" out) (write-line "}" out))) + +(defmacro with-cpp-block-output ((out &rest rest &key semicolonp name) &body body) + "Surround BODY with emitting '{' and '}' to OUT. For additional arguments, +see `CALL-WITH-CPP-BLOCK-OUTPUT' documentation." + (declare (ignorable semicolonp name)) + `(call-with-cpp-block-output ,out (lambda () ,@body) ,@rest)) diff --git a/src/lisp/lcp-test.lisp b/src/lisp/lcp-test.lisp index 2675c5737..92ca9201b 100644 --- a/src/lisp/lcp-test.lisp +++ b/src/lisp/lcp-test.lisp @@ -137,3 +137,147 @@ (subtest "arrays" (different-parse-test "char (*)[]" "char (*) []") (different-parse-test "char (*)[4]" "char (*) [4]"))) + +(defun clang-format (cpp-string) + (with-input-from-string (s cpp-string) + (string-left-trim + '(#\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=)) + +(defun undefine-cpp-types () + (setf lcp::*cpp-classes* nil) + (setf lcp::*cpp-enums* nil)) + +(deftest "slk" + (subtest "function declarations" + (undefine-cpp-types) + (is-generated (lcp.slk:save-function-declaration-for-class + (lcp:define-struct test-struct () + ())) + "void Save(const TestStruct &self, slk::Builder *builder)") + (undefine-cpp-types) + (is-generated (lcp.slk:save-function-declaration-for-class + (lcp:define-class derived (base) + ())) + "void Save(const Derived &self, slk::Builder *builder)") + (undefine-cpp-types) + (is-error (lcp.slk:save-function-declaration-for-class + (lcp:define-class derived (fst-base snd-base) + ())) + 'lcp.slk:slk-error) + (undefine-cpp-types) + (is-error (lcp.slk:save-function-declaration-for-class + (lcp:define-class (derived t-param) (base) + ())) + 'lcp.slk:slk-error) + (undefine-cpp-types) + (is-error (lcp.slk:save-function-declaration-for-class + (lcp:define-struct (test-struct fst-param snd-param) () + ())) + 'lcp.slk:slk-error) + (undefine-cpp-types)) + + (subtest "save definitions" + (undefine-cpp-types) + (is-generated (lcp.slk:save-function-definition-for-class + (lcp:define-struct test-struct () + ((int-member :int64_t) + (vec-member "std::vector<SomeType>")))) + "void Save(const TestStruct &self, slk::Builder *builder) { + slk::Save(self.int_member, builder); + slk::Save(self.vec_member, builder); + }") + (undefine-cpp-types) + (is-generated (lcp.slk:save-function-definition-for-class + (lcp:define-struct test-struct () + ((skip-member :int64_t :dont-save t)))) + "void Save(const TestStruct &self, slk::Builder *builder) {}") + (undefine-cpp-types) + (is-generated (lcp.slk:save-function-definition-for-class + (lcp:define-struct test-struct () + ((custom-member "SomeType" + :slk-save (lambda (member-name) + (check-type member-name string) + (format nil "self.~A.CustomSave(builder);" member-name)))))) + "void Save(const TestStruct &self, slk::Builder *builder) { + self.custom_member.CustomSave(builder); + }") + + (subtest "inheritance" + (undefine-cpp-types) + (is-error (lcp.slk:save-function-declaration-for-class + (lcp:define-struct derived (fst-base snd-base) + ())) + 'lcp.slk:slk-error) + (undefine-cpp-types) + (let ((base-class (lcp:define-struct base () + ((base-member :bool)))) + (derived-class (lcp:define-struct derived (base) + ((derived-member :int64_t))))) + (is-generated (lcp.slk:save-function-definition-for-class base-class) + "void Save(const Base &self, slk::Builder *builder) { + if (const auto &derived_derived = dynamic_cast<const Derived &>(self)) { + return slk::Save(derived_derived, builder); + } + slk::Save(self.base_member, builder); + }") + (is-generated (lcp.slk:save-function-definition-for-class derived-class) + "void Save(const Derived &self, slk::Builder *builder) { + // Save parent Base + { slk::Save(self.base_member, builder); } + slk::Save(self.derived_member, builder); + }")) + (undefine-cpp-types) + (let ((abstract-base-class (lcp:define-struct abstract-base () + ((base-member :bool)) + (:abstractp t))) + (derived-class (lcp:define-struct derived (abstract-base) + ((derived-member :int64_t))))) + (is-generated (lcp.slk:save-function-definition-for-class abstract-base-class) + "void Save(const AbstractBase &self, slk::Builder *builder) { + if (const auto &derived_derived = dynamic_cast<const Derived &>(self)) { + return slk::Save(derived_derived, builder); + } + LOG(FATAL) << \"`AbstractBase` is marked as an abstract class!\"; + }") + (is-generated (lcp.slk:save-function-definition-for-class derived-class) + "void Save(const Derived &self, slk::Builder *builder) { + // Save parent AbstractBase + { slk::Save(self.base_member, builder); } + slk::Save(self.derived_member, builder); + }")) + (undefine-cpp-types) + (let ((base-templated-class (lcp:define-struct (base t-param) () + ((base-member :bool)))) + (derived-class (lcp:define-struct derived (base) + ((derived-member :int64_t))))) + (is-error (lcp.slk:save-function-definition-for-class base-templated-class) + 'lcp.slk:slk-error) + (is-error (lcp.slk:save-function-definition-for-class derived-class) + 'lcp.slk:slk-error)) + (undefine-cpp-types) + (let ((base-class (lcp:define-struct base () + ((base-member :bool)))) + (derived-templated-class (lcp:define-struct (derived t-param) (base) + ((derived-member :int64_t))))) + (is-error (lcp.slk:save-function-definition-for-class base-class) + 'lcp.slk:slk-error) + (is-error (lcp.slk:save-function-definition-for-class derived-templated-class) + 'lcp.slk:slk-error))) + + (subtest "non-public members" + (undefine-cpp-types) + (is-error (lcp.slk:save-function-definition-for-class + (lcp:define-class test-class () + ((public-member :bool :scope :public) + (private-member :int64_t)))) + 'lcp.slk:slk-error) + (undefine-cpp-types) + (is-error (lcp.slk:save-function-definition-for-class + (lcp:define-struct test-struct () + ((protected-member :int64_t :scope :protected) + (public-member :char)))) + 'lcp.slk:slk-error)))) diff --git a/src/lisp/lcp.asd b/src/lisp/lcp.asd index 778470b7e..b99098507 100644 --- a/src/lisp/lcp.asd +++ b/src/lisp/lcp.asd @@ -6,6 +6,8 @@ :serial t :components ((:file "package") (:file "types") + (:file "code-gen") + (:file "slk") (:file "lcp")) :in-order-to ((test-op (test-op "lcp/test")))) diff --git a/src/lisp/lcp.lisp b/src/lisp/lcp.lisp index d9ef04a06..3385614d9 100644 --- a/src/lisp/lcp.lisp +++ b/src/lisp/lcp.lisp @@ -34,22 +34,6 @@ (let ((cpp-name (cpp-variable-name (cpp-member-symbol cpp-member)))) (if struct cpp-name (format nil "~A_" cpp-name)))) -(defun call-with-cpp-block-output (out fun &key semicolonp name) - "Surround the invocation of FUN by emitting '{' and '}' to OUT. If -SEMICOLONP is set, the closing '}' is suffixed with ';'. NAME is used to -prepend the starting block with a name, for example \"class MyClass\"." - (if name - (format out "~A {~%" name) - (write-line "{" out)) - (funcall fun) - (if semicolonp (write-line "};" out) (write-line "}" out))) - -(defmacro with-cpp-block-output ((out &rest rest &key semicolonp name) &body body) - "Surround BODY with emitting '{' and '}' to OUT. For additional arguments, -see `CALL-WITH-CPP-BLOCK-OUTPUT' documentation." - (declare (ignorable semicolonp name)) - `(call-with-cpp-block-output ,out (lambda () ,@body) ,@rest)) - (defun cpp-enum-definition (cpp-enum) "Get C++ style `CPP-ENUM' definition as a string." (declare (type cpp-enum cpp-enum)) diff --git a/src/lisp/package.lisp b/src/lisp/package.lisp index b90866758..39e5896bb 100644 --- a/src/lisp/package.lisp +++ b/src/lisp/package.lisp @@ -18,3 +18,9 @@ #:capnp-save-enum #:capnp-load-enum #:process-file)) + +(defpackage #:lcp.slk + (:use #:cl) + (:export #:save-function-declaration-for-class + #:save-function-definition-for-class + #:slk-error)) diff --git a/src/lisp/slk.lisp b/src/lisp/slk.lisp new file mode 100644 index 000000000..f60cb327d --- /dev/null +++ b/src/lisp/slk.lisp @@ -0,0 +1,123 @@ +;;;; This file contains code generation for serialization to our Save Load +;;;; Kit (SLK). It works very similarly to Cap'n Proto serialization, but +;;;; without the schema generation. + +(in-package #:lcp.slk) + +(define-condition slk-error (error) + ((message :type string :initarg :message :reader slk-error-message)) + (:report (lambda (condition stream) + (write-string (slk-error-message condition) stream)))) + +(defun save-function-declaration-for-class (cpp-class) + "Generate SLK save function declaration for CPP-CLASS. Note that the code +generation expects the declarations and definitions to be in `slk` namespace." + (check-type cpp-class lcp::cpp-class) + (when (lcp::cpp-type-type-params cpp-class) + (error 'slk-error :message + (format nil "Don't know how to save templated class '~A'" + (lcp::cpp-type-base-name cpp-class)))) + (when (< 1 (list-length (lcp::cpp-class-super-classes cpp-class))) + (error 'slk-error :message + (format nil "Don't know how to save multiple parents of '~A'" + (lcp::cpp-type-base-name cpp-class)))) + (let ((self-arg + (list 'self (format nil "const ~A &" + (lcp::cpp-type-decl cpp-class :namespace nil)))) + (builder-arg (list 'builder "slk::Builder *"))) + (lcp::cpp-function-declaration + "Save" :args (list self-arg builder-arg) + :type-params (lcp::cpp-type-type-params cpp-class)))) + +(defun save-members (cpp-class) + "Generate code for saving members of CPP-CLASS. Raise `SLK-ERROR' if the +serializable member has no public access." + (with-output-to-string (s) + (dolist (member (lcp::cpp-class-members-for-save cpp-class)) + (let ((member-name (lcp::cpp-member-name member :struct (lcp::cpp-class-structp cpp-class)))) + (when (not (eq :public (lcp::cpp-member-scope member))) + (error 'slk-error :message + (format nil "Cannot save non-public member '~A' of '~A'" + (lcp::cpp-member-symbol member) (lcp::cpp-type-base-name cpp-class)))) + (cond + ((lcp::cpp-member-slk-save member) + ;; Custom save function + (write-line (lcp::cpp-code (funcall (lcp::cpp-member-slk-save member) + member-name)) + s)) + ;; TODO: Extra args for cpp-class members + (t + (format s "slk::Save(self.~A, builder);~%" member-name))))))) + +(defun save-parents-recursively (cpp-class) + "Generate code for saving members of all parents, recursively. Raise +`SLK-ERROR' if trying to save templated parent class or if using multiple +inheritance." + (when (< 1 (list-length (lcp::cpp-class-super-classes cpp-class))) + (error 'slk-error :message + (format nil "Don't know how to save multiple parents of '~A'" + (lcp::cpp-type-base-name cpp-class)))) + (with-output-to-string (s) + ;; TODO: Stop recursing to parents if CPP-CLASS is marked as base for + ;; serialization. + (dolist (parent (lcp::cpp-class-super-classes cpp-class)) + (let ((parent-class (lcp::find-cpp-class parent))) + (assert parent-class) + (when (lcp::cpp-type-type-params parent-class) + (error 'slk-error :message + (format nil "Don't know how to save templated parent class '~A'" + (lcp::cpp-type-base-name parent-class)))) + (format s "// Save parent ~A~%" (lcp::cpp-type-name parent)) + (lcp::with-cpp-block-output (s) + (write-string (save-parents-recursively parent-class) s) + (write-string (save-members parent-class) s)))))) + +(defun forward-save-to-subclasses (cpp-class) + "Generate code which forwards the serialization to derived classes of +CPP-CLASS. Raise `SLK-ERROR' if a derived class has template parameters." + (with-output-to-string (s) + (let ((subclasses (lcp::direct-subclasses-of cpp-class))) + (dolist (subclass subclasses) + (when (lcp::cpp-type-type-params subclass) + (error 'slk-error :message + (format nil "Don't know how to save derived templated class '~A'" + (lcp::cpp-type-base-name subclass)))) + (let ((derived-class (lcp::cpp-type-name subclass)) + (derived-var (lcp::cpp-variable-name (lcp::cpp-type-base-name subclass))) + ;; TODO: Extra save arguments + (extra-args nil)) + (format s "if (const auto &~A_derived = dynamic_cast<const ~A &>(self)) { + return slk::Save(~A_derived, builder~{, ~A~}); }~%" + derived-var derived-class derived-var extra-args)))))) + +(defun save-function-code-for-class (cpp-class) + "Generate code for serializing CPP-CLASS. Raise `SLK-ERROR' on unsupported +C++ constructs, mostly related to templates." + (when (lcp::cpp-type-type-params cpp-class) + (error 'slk-error :message + (format nil "Don't know how to save templated class '~A'" + (lcp::cpp-type-base-name cpp-class)))) + (with-output-to-string (s) + (cond + ((lcp::direct-subclasses-of cpp-class) + (write-string (forward-save-to-subclasses cpp-class) s) + (if (lcp::cpp-class-abstractp cpp-class) + (format s "LOG(FATAL) << \"`~A` is marked as an abstract class!\";" + (lcp::cpp-type-name cpp-class)) + (progn + (write-string (save-parents-recursively cpp-class) s) + (write-string (save-members cpp-class) s)))) + (t + ;; TODO: Write some sort of type ID for derived classes + (write-string (save-parents-recursively cpp-class) s) + (write-string (save-members cpp-class) s))))) + +(defun save-function-definition-for-class (cpp-class) + "Generate SLK save function. Raise `SLK-ERROR' if an unsupported or invalid +class definition is encountered during code generation. Note that the code +generation expects the declarations and definitions to be in `slk` namespace." + (check-type cpp-class lcp::cpp-class) + (with-output-to-string (cpp-out) + (lcp::with-cpp-block-output + (cpp-out :name (save-function-declaration-for-class cpp-class)) + (write-line (save-function-code-for-class cpp-class) cpp-out)))) diff --git a/src/lisp/types.lisp b/src/lisp/types.lisp index 624de48ac..90752e754 100644 --- a/src/lisp/types.lisp +++ b/src/lisp/types.lisp @@ -125,6 +125,9 @@ ;; TODO: Support giving a name for reader function. (reader nil :type boolean :read-only t) (documentation nil :type (or null string) :read-only t) + ;; If T, skips this member in serialization code generation. The member may + ;; still be deserialized with custom load hook. + (dont-save nil :type boolean :read-only t) ;; CAPNP-TYPE may be a string specifying the type, or a list of ;; (member-symbol "capnp-type") specifying a union type. (capnp-type nil :type (or null string list) :read-only t) @@ -132,7 +135,11 @@ ;; Custom saving and loading code. May be a function which takes 2 ;; args: (builder-or-reader member-name) and needs to return C++ code. (capnp-save nil :type (or null function (eql :dont-save)) :read-only t) - (capnp-load nil :type (or null function) :read-only t)) + (capnp-load nil :type (or null function) :read-only t) + ;; May be a function which takes 1 argument, member-name. It needs to + ;; return C++ code. + (slk-save nil :type (or null function) :read-only t) + (slk-load nil :type (or null function) :read-only t)) (defstruct capnp-opts "Cap'n Proto serialization options for C++ class." @@ -173,6 +180,10 @@ (defvar *cpp-classes* nil "List of defined classes from LCP file") (defvar *cpp-enums* nil "List of defined enums from LCP file") +(defun cpp-class-members-for-save (cpp-class) + (check-type cpp-class cpp-class) + (remove-if #'cpp-member-dont-save (cpp-class-members cpp-class))) + (defun make-cpp-primitive-type (name) "Create an instance of CPP-PRIMITIVE-TYPE given the arguments." (check-type name cpp-primitive-type-keywords) diff --git a/src/query/plan/operator.cpp b/src/query/plan/operator.cpp index 7f88ecfeb..1dcab81b6 100644 --- a/src/query/plan/operator.cpp +++ b/src/query/plan/operator.cpp @@ -627,7 +627,7 @@ auto ExpandFromVertex(const VertexAccessor &vertex, [direction](const EdgeAccessor &edge) { return std::make_pair(edge, direction); }, - std::move(vertices)); + std::forward<decltype(vertices)>(vertices)); }; // prepare a vector of elements we'll pass to the itertools