;; SPDX-License-Identifier: EUPL-1.2 ;; SPDX-FileCopyrightText: 2025 Uko Kokņevičs (defpackage :ukkoclot/tg/method-macros (:documentation "Macros for easy defining TG methods.") (:use :c2cl :iterate) (:import-from :alexandria :with-gensyms) (:import-from :com.dieggsy.f-string :enable-f-strings) (:import-from :serapeum :take) (:import-from :str) (:import-from :ukkoclot/state :bot) (:import-from :ukkoclot/transport :do-call) (:export :define-tg-method)) (in-package :ukkoclot/tg/method-macros) (eval-when (:compile-toplevel :load-toplevel :execute) (enable-f-strings) (defstruct (param (:constructor make-param%)) name type default skip-if-default) (defparameter +unique+ (gensym)) ;; TODO: Fix optional-and-key ! (defun make-param (name type ; lint:suppress avoid-optional-and-key &optional (default +unique+) &key (skip-if-default (not (eq default +unique+)))) (let ((default (if (eq default +unique+) `(error ,#f"No value given for {name}") default))) (make-param% :name name :type type :default default :skip-if-default skip-if-default))) (defun parse-param-specs (param-specs) (iter (for param-spec in param-specs) (collect (apply #'make-param param-spec)))) (defun path-from-name (name) (let ((str (str:camel-case name))) (if (str:ends-with-p "%" str :ignore-case nil) (take (- (length str) 1) str) str))) (defun emit-append-to-args (param args) `(setf ,args (acons ',(param-name param) ,(param-name param) ,args))) (defun emit-arg-type (param) `(,(intern (symbol-name (param-name param)) :keyword) ,(param-type param))) (defun emit-defun-arg (param) `(,(param-name param) ,(param-default param))) (defun emit-defun (name return-type params method) (with-gensyms (args bot) `(defun ,name (,bot &key ,@(iter (for param in params) ; lint:suppress lambda-list-invalid (collect (emit-defun-arg param)))) (let (,args) ,@(iter (for param in (reverse params)) (collect (if (param-skip-if-default param) `(unless (equal ,(param-name param) ,(param-default param)) ,(emit-append-to-args param args)) (emit-append-to-args param args)))) (do-call ,bot ,method ,(path-from-name name) ',return-type ,args))))) (defun emit-ftype (name return-type params) `(declaim (ftype (function (bot &key ,@(iter (for param in params) (collect (emit-arg-type param)))) ,return-type) ,name)))) (defmacro define-tg-method ((name type &optional (method :POST)) &body param-specs) (let ((params (parse-param-specs param-specs))) `(progn ,(emit-ftype name type params) ,(emit-defun name type params method))))