summaryrefslogtreecommitdiff
path: root/src/tg-types/macros.lisp
diff options
context:
space:
mode:
Diffstat (limited to 'src/tg-types/macros.lisp')
-rw-r--r--src/tg-types/macros.lisp134
1 files changed, 134 insertions, 0 deletions
diff --git a/src/tg-types/macros.lisp b/src/tg-types/macros.lisp
new file mode 100644
index 0000000..668df17
--- /dev/null
+++ b/src/tg-types/macros.lisp
@@ -0,0 +1,134 @@
1;; SPDX-License-Identifier: EUPL-1.2
2;; SPDX-FileCopyrightText: 2025 Uko Kokņevičs <perkontevs@gmail.com>
3(defpackage :ukkoclot/tg-types/macros
4 (:use :c2cl)
5 (:import-from :ukkoclot/bot/impl :arg-encode :bot :do-call :will-arg-encode)
6 (:import-from :ukkoclot/hash-tables :gethash-lazy)
7 (:import-from :ukkoclot/strings :lisp->snake-case)
8 (:export :define-tg-method :define-tg-type))
9(in-package :ukkoclot/tg-types/macros)
10
11(eval-when (:compile-toplevel :load-toplevel :execute)
12 (defstruct (field (:constructor make-field%)) name type default skip-if-default (parser 'identity))
13
14 (defparameter +unique+ (gensym))
15
16 (defun make-field (name type &optional (default +unique+) &key (parser 'identity) (skip-if-default (not (eq default +unique+))))
17 (let ((default (if (eq default +unique+)
18 (list 'error (format nil "No value given for ~A" name))
19 default)))
20 (make-field% :name name
21 :type type
22 :default default
23 :skip-if-default skip-if-default
24 :parser parser)))
25
26 (defun parse-field-specs (field-specs)
27 (loop for field-spec in field-specs
28 collect (apply #'make-field field-spec)))
29
30 (defun field-hash-key (field)
31 (string-downcase (lisp->snake-case (symbol-name (field-name field)))))
32
33 (defun field-accessor (struc-name field)
34 (intern (concatenate 'string (symbol-name struc-name) "-" (symbol-name (field-name field)))))
35
36 (defun field->defun-spec (field)
37 (list (field-name field) (field-default field)))
38
39 (defun field->format-arg (field name struc)
40 `(',(field-name field) (,(field-accessor name field) ,struc)))
41
42 (defun field->ftype-spec (field)
43 (list (intern (symbol-name (field-name field)) :keyword) (field-type field)))
44
45 (defun field->gethash-spec (field hash-table-sym)
46 (let ((hash-key (field-hash-key field)))
47 (list 'gethash-lazy hash-key hash-table-sym (field-default field))))
48
49 (defun field->sethash-spec (field name struc hash-table-sym)
50 (let ((hash-key (field-hash-key field))
51 (skip-if-default (field-skip-if-default field))
52 (default (field-default field)))
53 (if skip-if-default
54 (let ((tmpsym (gensym "TMP")))
55 `(let ((,tmpsym (,(field-accessor name field) ,struc)))
56 (unless (equal ,tmpsym ,default)
57 (setf (gethash ,hash-key ,hash-table-sym) ,tmpsym))))
58 `(setf (gethash ,hash-key ,hash-table-sym) (,(field-accessor name field) ,struc)))))
59
60 (defun field->let-gethash-spec (field hash-table-sym)
61 (list (field-name field)
62 (list 'funcall
63 (list 'function (field-parser field))
64 (field->gethash-spec field hash-table-sym))))
65
66 (defun field->make-spec (field)
67 (list (intern (symbol-name (field-name field)) :keyword)
68 (field-name field)))
69
70 (defun field->struct-spec (field)
71 (list (field-name field) (field-default field) :type (field-type field))))
72
73;; TODO: Automatically derive path from name
74;; TODO: Automatically derive mapfn from type
75;; TODO: Skip values that are already their defaults
76(defmacro define-tg-method (
77 (name type path mapfn &optional (method :POST))
78 &body field-specs)
79 (let ((fields (parse-field-specs field-specs))
80 (args-plist (gensym "ARGS-PLIST-"))
81 (bot (gensym "BOT-")))
82 `(progn
83 (declaim (ftype (function (bot &key ,@(loop for field in fields
84 collect (field->ftype-spec field)))
85 ,type)
86 ,name))
87 (defun ,name (,bot &rest ,args-plist &key ,@(loop for field in fields collect (field->defun-spec field)))
88 (declare ,@(loop for field in fields collect (list 'ignore (field-name field))))
89 (do-call ,bot ,method ,path ,mapfn ,args-plist)))))
90
91(defmacro define-tg-type (name &body field-specs)
92 (let* ((fields (parse-field-specs field-specs))
93 (revfields (reverse fields))
94 (make-name (intern (concatenate 'string "MAKE-" (symbol-name name))))
95 (hash->name (intern (concatenate 'string "HASH->" (symbol-name name))))
96 (parse-name-array (intern (concatenate 'string "PARSE-" (symbol-name name) "-ARRAY")))
97 (printer (gensym (concatenate 'string "PRINT-" (symbol-name name))))
98 (hash (gensym "HASH-"))
99 (array (gensym "ARRAY-"))
100 (struc (gensym (symbol-name name)))
101 (stream (gensym "STREAM"))
102 (depth (gensym "DEPTH"))
103 (pprint-args (gensym "PPRINT-ARGS")))
104 `(progn
105 (defstruct (,name (:print-function ,printer))
106 ,@(loop for field in fields
107 collect (field->struct-spec field)))
108 (defun ,printer (,struc ,stream ,depth)
109 (declare (ignore ,depth))
110 (let (,pprint-args)
111 ,@(loop for field in revfields
112 collecting
113 (if (field-skip-if-default field)
114 `(let ((value (,(field-accessor name field) ,struc)))
115 (unless (equal value ,(field-default field))
116 (setf ,pprint-args (list* ',(field-name field) value ,pprint-args))))
117 `(setf ,pprint-args (list* ',(field-name field) (,(field-accessor name field) ,struc) ,pprint-args))))
118 (format ,stream "~A~<[~;~@{~_~1I~W = ~W~^, ~}~;]~:>" ',name ,pprint-args)))
119 (defun ,hash->name (,hash)
120 (when ,hash
121 (let ,(loop for field in fields
122 collect (field->let-gethash-spec field hash))
123 (,make-name ,@(loop for field in fields
124 append (field->make-spec field))))))
125 (defmethod arg-encode ((,struc ,name))
126 (let ((,hash (make-hash-table)))
127 ,@(loop for field in fields
128 collect (field->sethash-spec field name struc hash))
129 ,hash))
130 (defmethod will-arg-encode ((,struc ,name))
131 t)
132 (defun ,parse-name-array (,array)
133 (when ,array
134 (map 'vector #',hash->name ,array))))))