summaryrefslogtreecommitdiff
path: root/src/tg/type-macros.lisp
diff options
context:
space:
mode:
Diffstat (limited to 'src/tg/type-macros.lisp')
-rw-r--r--src/tg/type-macros.lisp125
1 files changed, 125 insertions, 0 deletions
diff --git a/src/tg/type-macros.lisp b/src/tg/type-macros.lisp
new file mode 100644
index 0000000..06de32d
--- /dev/null
+++ b/src/tg/type-macros.lisp
@@ -0,0 +1,125 @@
1;; SPDX-License-Identifier: EUPL-1.2
2;; SPDX-FileCopyrightText: 2025 Uko Kokņevičs <perkontevs@gmail.com>
3(defpackage :ukkoclot/tg/type-macros
4 (:use :c2cl :iterate)
5 (:import-from :ukkoclot/hash-tables :gethash-lazy)
6 (:import-from :ukkoclot/strings :lisp->snake-case)
7 (:local-nicknames
8 (:jzon :com.inuoe.jzon))
9 (:export :define-tg-type))
10(in-package :ukkoclot/tg/type-macros)
11
12(eval-when (:compile-toplevel :load-toplevel :execute)
13 (defstruct (field (:constructor make-field%)) name type default skip-if-default)
14
15 (defparameter +unique+ (gensym))
16
17 (defun make-field (name type &optional (default +unique+) &key (skip-if-default (not (eq default +unique+))))
18 (let ((default (if (eq default +unique+)
19 `(error ,(format nil "No value given for ~A" name))
20 default)))
21 (make-field% :name name
22 :type type
23 :default default
24 :skip-if-default skip-if-default)))
25
26 (defun type-constructor (name)
27 (intern (concatenate 'string "MAKE-" (symbol-name name))))
28
29 (defun field-accessor (name field)
30 (intern (concatenate 'string (symbol-name name) "-" (symbol-name (field-name field)))))
31
32 (defun field-hash-key (field)
33 (string-downcase (lisp->snake-case (symbol-name (field-name field)))))
34
35 (defun field-keyword (field)
36 (intern (symbol-name (field-name field)) :keyword))
37
38 (defun parse-field-specs (field-specs)
39 (iter (for field-spec in field-specs)
40 (collect (apply #'make-field field-spec))))
41
42 (defun emit-append-to-pprint-args (field value pprint-args)
43 `(setf ,pprint-args (list* ',(field-name field) ,value ,pprint-args)))
44
45 (defun emit-coerced-field (field value)
46 `(list ,(field-hash-key field) ,value ',(field-type field)))
47
48 (defun emit-collect-nondefault-fields (name fields obj collector)
49 (let ((value (gensym "VALUE")))
50 (iter (for field in (reverse fields))
51 (collect
52 (if (field-skip-if-default field)
53 `(let ((,value (,(field-accessor name field) ,obj)))
54 (unless (equal ,value ,(field-default field))
55 ,(funcall collector field value)))
56 (funcall collector field (list (field-accessor name field) obj)))))))
57
58 (defun emit-constructor-args (field)
59 `(,(field-keyword field) ,(field-name field)))
60
61 (defun emit-gethash (field source)
62 `(gethash-lazy ,(field-hash-key field) ,source ,(field-default field)))
63
64 (defun emit-jzon-coerced-fields (name fields)
65 (let ((obj (gensym "OBJ"))
66 (result (gensym "RESULT")))
67 `(defmethod jzon:coerced-fields ((,obj ,name))
68 (let (,result)
69 ,@(emit-collect-nondefault-fields
70 name fields obj
71 (lambda (field value)
72 `(setf ,result (cons ,(emit-coerced-field field value) ,result))))
73 ,result))))
74
75 (defun emit-let-gethash (field source)
76 `(,(field-name field)
77 (parse-value ',(field-type field) ,(emit-gethash field source))))
78
79 (defun emit-parse-value (name fields)
80 (let ((type-sym (gensym "TYPE-SYM"))
81 (source (gensym "SOURCE")))
82 `(defmethod parse-value ((,type-sym (eql ',name)) ,source)
83 (let ,(iter (for field in fields)
84 (collect (emit-let-gethash field source)))
85 (,(type-constructor name)
86 ,@(print (iter (for field in fields)
87 (appending (print (emit-constructor-args field))))))))))
88
89 (defun emit-printer (name printer-name fields)
90 (let ((obj (gensym "OBJ"))
91 (stream (gensym "STREAM"))
92 (depth (gensym "DEPTH"))
93 (pprint-args (gensym "PPRINT-ARGS")))
94 `(defun ,printer-name (,obj ,stream ,depth)
95 (declare (ignore ,depth))
96 (let (,pprint-args)
97 ,@(emit-collect-nondefault-fields
98 name fields obj
99 (lambda (field value)
100 `(setf ,pprint-args (list* ',(field-name field) ,value ,pprint-args))))
101 ;; ,@(iter (for field in (reverse fields))
102 ;; (collect
103 ;; (if (field-skip-if-default field)
104 ;; `(let ((,value (,(field-accessor name field) ,obj)))
105 ;; (unless (equal ,value ,(field-default field))
106 ;; ,(emit-append-to-pprint-args field value pprint-args)))
107 ;; (emit-append-to-pprint-args field `(,(field-accessor name field) ,obj) pprint-args))))
108 (format ,stream "~A~<[~;~@{~_~1I~W = ~W~^, ~}~;]~:>" ',name ,pprint-args)))))
109
110 (defun emit-struct (name printer-name fields)
111 `(defstruct (,name (:print-function ,printer-name))
112 ,@(iter (for field in fields)
113 (collect (emit-struct-field field)))))
114
115 (defun emit-struct-field (field)
116 `(,(field-name field) ,(field-default field) :type ,(field-type field))))
117
118(defmacro define-tg-type (name &body field-specs)
119 (let ((fields (parse-field-specs field-specs))
120 (printer-name (gensym "PRINTER")))
121 `(progn
122 ,(emit-struct name printer-name fields)
123 ,(emit-printer name printer-name fields)
124 ,(emit-parse-value name fields)
125 ,(emit-jzon-coerced-fields name fields))))