Merge branch (bug #4008)
[maxima.git] / src / defmfun-check.lisp
blob432afd4e1ac00ffc95380492fec380adb30e187b
1 ;;;; -*- Mode: Lisp; Package: Maxima; Syntax: Common-Lisp; Base: 10 -*- ;;;;
3 (in-package :maxima)
5 ;; We got this code from cmucl, so we don't actually need all of this.
6 #+cmucl
7 (progn
8 (defun parse-lambda-list (list)
9 (kernel:parse-lambda-list list))
10 (defun parse-body (body environment &optional (doc-string-allowed t))
11 (system:parse-body body environment doc-string-allowed))
14 #-cmucl
15 (eval-when (compile load eval)
16 ;;;; Borrowed from cmucl src/code/extensions.lisp. Used in parsing
17 ;;;; lambda lists.
19 ;;;; The Collect macro:
21 ;;; Collect-Normal-Expander -- Internal
22 ;;;
23 ;;; This function does the real work of macroexpansion for normal collection
24 ;;; macros. N-Value is the name of the variable which holds the current
25 ;;; value. Fun is the function which does collection. Forms is the list of
26 ;;; forms whose values we are supposed to collect.
27 ;;;
28 (defun collect-normal-expander (n-value fun forms)
29 `(progn
30 ,@(mapcar #'(lambda (form) `(setq ,n-value (,fun ,form ,n-value))) forms)
31 ,n-value))
33 ;;; Collect-List-Expander -- Internal
34 ;;;
35 ;;; This function deals with the list collection case. N-Tail is the pointer
36 ;;; to the current tail of the list, which is NIL if the list is empty.
37 ;;;
38 (defun collect-list-expander (n-value n-tail forms)
39 (let ((n-res (gensym)))
40 `(progn
41 ,@(mapcar #'(lambda (form)
42 `(let ((,n-res (cons ,form nil)))
43 (cond (,n-tail
44 (setf (cdr ,n-tail) ,n-res)
45 (setq ,n-tail ,n-res))
47 (setq ,n-tail ,n-res ,n-value ,n-res)))))
48 forms)
49 ,n-value)))
52 ;;; Collect -- Public
53 ;;;
54 ;;; The ultimate collection macro...
55 ;;;
56 (defmacro collect (collections &body body)
57 "Collect ({(Name [Initial-Value] [Function])}*) {Form}*
58 Collect some values somehow. Each of the collections specifies a bunch of
59 things which collected during the evaluation of the body of the form. The
60 name of the collection is used to define a local macro, a la MACROLET.
61 Within the body, this macro will evaluate each of its arguments and collect
62 the result, returning the current value after the collection is done. The
63 body is evaluated as a PROGN; to get the final values when you are done, just
64 call the collection macro with no arguments.
66 Initial-Value is the value that the collection starts out with, which
67 defaults to NIL. Function is the function which does the collection. It is
68 a function which will accept two arguments: the value to be collected and the
69 current collection. The result of the function is made the new value for the
70 collection. As a totally magical special-case, the Function may be Collect,
71 which tells us to build a list in forward order; this is the default. If an
72 Initial-Value is supplied for Collect, the stuff will be rplacd'd onto the
73 end. Note that Function may be anything that can appear in the functional
74 position, including macros and lambdas."
76 (let ((macros ())
77 (binds ()))
78 (dolist (spec collections)
79 (unless (<= 1 (length spec) 3)
80 (error (intl:gettext "Malformed collection specifier: ~S.") spec))
81 (let ((n-value (gensym))
82 (name (first spec))
83 (default (second spec))
84 (kind (or (third spec) 'collect)))
85 (push `(,n-value ,default) binds)
86 (if (eq kind 'collect)
87 (let ((n-tail (gensym)))
88 (if default
89 (push `(,n-tail (last ,n-value)) binds)
90 (push n-tail binds))
91 (push `(,name (&rest args)
92 (collect-list-expander ',n-value ',n-tail args))
93 macros))
94 (push `(,name (&rest args)
95 (collect-normal-expander ',n-value ',kind args))
96 macros))))
97 `(macrolet ,macros (let* ,(nreverse binds) ,@body))))
99 ;;; Borrowed from cmucl src/compiler/proclaim.lisp
101 ;;; Parse-Lambda-List -- Interface
103 ;;; Break a lambda-list into its component parts. We return eleven values:
104 ;;; 1] A list of the required args.
105 ;;; 2] A list of the optional arg specs.
106 ;;; 3] True if a rest arg was specified.
107 ;;; 4] The rest arg.
108 ;;; 5] A boolean indicating whether keywords args are present.
109 ;;; 6] A list of the keyword arg specs.
110 ;;; 7] True if &allow-other-keys was specified.
111 ;;; 8] A list of the &aux specifiers.
112 ;;; 9] True if a more arg was specified.
113 ;;; 10] The &more context var
114 ;;; 11] The &more count var
116 ;;; The top-level lambda-list syntax is checked for validity, but the arg
117 ;;; specifiers are just passed through untouched. If something is wrong, we
118 ;;; use Compiler-Error, aborting compilation to the last recovery point.
120 (defun parse-lambda-list (list)
121 (declare (list list))
122 (collect ((required)
123 (optional)
124 (keys)
125 (aux))
126 (flet ((compiler-error (&rest args)
127 (apply #'error args))
128 (compiler-note (&rest args)
129 (apply #'warn args)))
130 (let ((restp nil)
131 (rest nil)
132 (morep nil)
133 (more-context nil)
134 (more-count nil)
135 (keyp nil)
136 (allowp nil)
137 (state :required))
138 (dolist (arg list)
139 ;; check for arguments that have the syntactic form of a
140 ;; keyword argument without being a recognized lambda-list keyword
141 (when (and (symbolp arg)
142 (let ((name (symbol-name arg)))
143 (and (/= (length name) 0)
144 (char= (char name 0) #\&))))
145 (unless (member arg lambda-list-keywords)
146 (compiler-note
147 "~S uses lambda-list keyword naming convention, but is not a recognized lambda-list keyword."
148 arg)))
149 (if (member arg lambda-list-keywords)
150 (ecase arg
151 (&optional
152 (unless (eq state :required)
153 (compiler-error "Misplaced &optional in lambda-list: ~S." list))
154 (setq state '&optional))
155 (&rest
156 (unless (member state '(:required &optional))
157 (compiler-error "Misplaced &rest in lambda-list: ~S." list))
158 (setq state '&rest))
159 (&more
160 (unless (member state '(:required &optional))
161 (compiler-error "Misplaced &more in lambda-list: ~S." list))
162 (setq morep t state '&more-context))
163 (&key
164 (unless (member state '(:required &optional :post-rest
165 :post-more))
166 (compiler-error "Misplaced &key in lambda-list: ~S." list))
167 (setq keyp t)
168 (setq state '&key))
169 (&allow-other-keys
170 (unless (eq state '&key)
171 (compiler-error "Misplaced &allow-other-keys in lambda-list: ~S." list))
172 (setq allowp t state '&allow-other-keys))
173 (&aux
174 (when (member state '(&rest &more-context &more-count))
175 (compiler-error "Misplaced &aux in lambda-list: ~S." list))
176 (setq state '&aux)))
177 (case state
178 (:required (required arg))
179 (&optional (optional arg))
180 (&rest
181 (setq restp t rest arg state :post-rest))
182 (&more-context
183 (setq more-context arg state '&more-count))
184 (&more-count
185 (setq more-count arg state :post-more))
186 (&key (keys arg))
187 (&aux (aux arg))
189 (compiler-error "Found garbage in lambda-list when expecting a keyword: ~S." arg)))))
191 (when (eq state '&rest)
192 (compiler-error "&rest not followed by required variable."))
194 (values (required) (optional) restp rest keyp (keys) allowp (aux)
195 morep more-context more-count)))))
197 (defun parse-body (body environment &optional (doc-string-allowed t))
198 "This function is to parse the declarations and doc-string out of the body of
199 a defun-like form. Body is the list of stuff which is to be parsed.
200 Environment is ignored. If Doc-String-Allowed is true, then a doc string
201 will be parsed out of the body and returned. If it is false then a string
202 will terminate the search for declarations. Three values are returned: the
203 tail of Body after the declarations and doc strings, a list of declare forms,
204 and the doc-string, or NIL if none."
205 (declare (ignore environment))
206 (let ((decls ())
207 (doc nil))
208 (do ((tail body (cdr tail)))
209 ((endp tail)
210 (values tail (nreverse decls) doc))
211 (let ((form (car tail)))
212 (cond ((and (stringp form) (cdr tail))
213 (if doc-string-allowed
214 (setq doc form
215 ;; Only one doc string is allowed.
216 doc-string-allowed nil)
217 (return (values tail (nreverse decls) doc))))
218 ((not (and (consp form) (symbolp (car form))))
219 (return (values tail (nreverse decls) doc)))
220 ((eq (car form) 'declare)
221 (push form decls))
223 (return (values tail (nreverse decls) doc))))))))
226 (defun defmfun-keywords (fname options valid-keywords)
227 ;; options looks like (((mequal) $opt1 val1) ((mequal) $opt2 val2) ...)
229 ;; Convert to a new list that looks like (:opt1 val1 :opt2 val2 ...)
231 (unless (listp options)
232 (merror "Invalid Maxima keyword options: ~M" options))
233 (when (every #'(lambda (o)
234 ;; Make sure every option has the right form.
235 (let ((ok (and (listp o)
236 (= (length o) 3)
237 (eq (caar o) 'mequal))))
238 (unless ok
239 (merror (intl:gettext "~M: Badly formed keyword option: ~M")
240 fname o))
241 ok))
242 options)
243 (mapcan #'(lambda (o)
244 (destructuring-bind (mequal opt val)
246 (declare (ignore mequal))
247 (if (or (null valid-keywords)
248 (member opt valid-keywords))
249 (flet ((keywordify (x)
250 (intern (subseq (symbol-name x) 1) :keyword)))
251 (list (keywordify opt) val))
252 (merror (intl:gettext "~M: Unrecognized keyword: ~M")
253 fname opt))))
254 options)))
256 ;; Internal macro to do the heavy lifting of defining a function that
257 ;; checks the number of arguments of a function. This is intended to
258 ;; give nice error messages to user-callable functions when the number
259 ;; of arguments is incorrect.
261 ;; The function to check arguments is named NAME. The actual
262 ;; implementation is in a new function named IMPL, which is called by
263 ;; NAME. A compiler-macro is also defined so that Lisp calls of NAME
264 ;; get automatically converted to IMPL.
266 ;; The lambda-list supports &optional and &rest args. Keyword args
267 ;; (&key) are also supported. Maxima keyword args (a=b) are converted
268 ;; to Lisp keywords appropriately. Unrecognized keywords signal a
269 ;; Maxima error.
271 ;; The variable %%PRETTY-FNAME is defined such that the body can refer
272 ;; to this variable to get the pretty name of the defined function for
273 ;; use in printing error messages or what not. This allows the
274 ;; implementation to print out the function name that would also be
275 ;; used when printing out error messages for incorrect number of
276 ;; arguments.
278 (defmacro defun-checked-form ((name impl-name) lambda-list &body body)
279 ;; Carefully check the number of arguments and print a nice message
280 ;; if the number doesn't match the expected number.
281 (multiple-value-bind (required-args
282 optional-args
283 restp
284 rest-arg
285 keywords-present-p
286 keyword-args
287 allow-other-keys-p)
288 (parse-lambda-list lambda-list)
290 (when (and keywords-present-p
291 (or optional-args restp))
292 (error "Keyword args cannot be used with optional args or rest args"))
294 (let* ((required-len (length required-args))
295 (optional-len (length optional-args))
296 (impl-doc (format nil "Implementation for ~S" name))
297 (nargs (gensym "NARGS-"))
298 (args (gensym "REST-ARG-"))
299 (rest-name (gensym "REST-ARGS"))
300 (pretty-fname
301 (cond (optional-args
302 ;; Can't do much with optional args, so just use the function name.
303 name)
304 (restp
305 ;; Use maxima syntax for rest args: foo(a,b,[c]);
306 `((,name) ,@required-args ((mlist) ,rest-arg)))
307 (keywords-present-p
308 ;; Not exactly sure how to do this
309 (let* ((index 1)
310 (keys (mapcar
311 #'(lambda (k)
312 (multiple-value-bind (name val)
313 (if (consp k)
314 (values
315 (intern (format nil "$~A" (car k)))
316 (second k))
317 (values
318 (intern (format nil "$~A" k))
319 nil))
320 (incf index)
321 `((mequal) ,name ,val)))
322 keyword-args)))
323 `((,name) ,@required-args ,@keys)))
325 ;; Just have required args: foo(a,b)
326 `((,name) ,@required-args))))
327 (maxima-keywords
328 (unless allow-other-keys-p
329 (mapcar #'(lambda (x)
330 (intern (concatenate
331 'string "$"
332 (symbol-name
333 (if (consp x)
334 (car x)
335 x)))))
336 keyword-args))))
338 (multiple-value-bind (forms decls doc-string)
339 (parse-body body nil t)
340 (setf doc-string (if doc-string (list doc-string)))
341 `(progn
342 (defun ,impl-name ,lambda-list
343 ,impl-doc
344 ,@decls
345 (block ,name
346 (let ((%%pretty-fname ',pretty-fname))
347 (declare (ignorable %%pretty-fname))
348 ,@forms)))
350 (defun ,name (&rest ,args)
351 ,@doc-string
352 (let ((,nargs (length ,args)))
353 (declare (ignorable ,nargs))
354 ,@(cond
355 ((or restp keywords-present-p)
356 ;; When a rest arg is given, there's no upper
357 ;; limit to the number of args. Just check that
358 ;; we have enough args to satisfy the required
359 ;; args.
360 (unless (null required-args)
361 `((when (< ,nargs ,required-len)
362 (merror (intl:gettext "~M: expected at least ~M arguments but got ~M: ~M")
363 ',pretty-fname
364 ,required-len
365 ,nargs
366 (list* '(mlist) ,args))))))
367 (optional-args
368 ;; There are optional args (but no rest
369 ;; arg). Verify that we don't have too many args,
370 ;; and that we still have all the required args.
372 (when (> ,nargs ,(+ required-len optional-len))
373 (merror (intl:gettext "~M: expected at most ~M arguments but got ~M: ~M")
374 ',pretty-fname
375 ,(+ required-len optional-len)
376 ,nargs
377 (list* '(mlist) ,args)))
378 (when (< ,nargs ,required-len)
379 (merror (intl:gettext "~M: expected at least ~M arguments but got ~M: ~M")
380 ',pretty-fname
381 ,required-len
382 ,nargs
383 (list* '(mlist) ,args)))))
385 ;; We only have required args.
386 `((unless (= ,nargs ,required-len)
387 (merror (intl:gettext "~M: expected exactly ~M arguments but got ~M: ~M")
388 ',pretty-fname
389 ,required-len
390 ,nargs
391 (list* '(mlist) ,args))))))
392 ,(cond
393 (keywords-present-p
394 `(apply #',impl-name
395 (append
396 (subseq ,args 0 ,required-len)
397 (defmfun-keywords ',pretty-fname
398 (nthcdr ,required-len ,args)
399 ',maxima-keywords))))
401 `(apply #',impl-name ,args)))))
402 ,(cond
403 (keywords-present-p
404 `(define-compiler-macro ,name (&rest ,rest-name)
405 ,(format nil "Compiler-macro to convert calls to ~S to ~S" name impl-name)
406 (let ((args (append (subseq ,rest-name 0 ,required-len)
407 (defmfun-keywords ',pretty-fname
408 (nthcdr ,required-len ,rest-name)
409 ',maxima-keywords))))
410 `(,',impl-name ,@args))))
412 `(define-compiler-macro ,name (&rest ,rest-name)
413 ,(format nil "Compiler-macro to convert calls to ~S to ~S" name impl-name)
414 `(,',impl-name ,@,rest-name)))))))))
416 ;; Define a Lisp function that should check the number of arguments to
417 ;; a function and print out a nice Maxima error message instead of
418 ;; signaling a Lisp error. In this case, the function is not
419 ;; explicitly exposed to the user and can just have an impl name of
420 ;; "name-impl".
421 (defmacro defun-checked (name lambda-list &body body)
422 ;; Defun-checked must not be used with functions that are exposed to
423 ;; the (Maxima) user. That is, it can't start with "$".
424 (when (char-equal #\$ (char (string name) 0))
425 (error "DEFUN-CHECKED functions cannot start with $: ~S~%" name))
426 `(defun-checked-form (,name ,(intern (concatenate 'string
427 (string name)
428 "-IMPL")))
429 ,lambda-list ,@body))
431 ;; Define user-exposed functions that are written in Lisp.
433 ;; If the function name NAME starts with #\$ we check the number of
434 ;; arguments. In this case, two functions are created: NAME and
435 ;; NAME-IMPL (without the leading $). NAME is the user function that
436 ;; checks for the argument count and NAME-IMPL is the actual
437 ;; implementation..
439 ;; If the function name doesn't start with $, we still allow it, but
440 ;; these should be replaced with plain defun eventually.
442 #+nil
443 (defmacro defmfun (name lambda-list &body body)
444 (flet ((add-props ()
445 ;; We make sure that the ARG-LIST property is added
446 ;; first, so that it will end up last in the list.
447 `(progn
448 (putprop ',name ',lambda-list 'arg-list)
449 (defprop ,name t translated))))
450 (let ((maclisp-narg-p (and (symbolp lambda-list) (not (null lambda-list)))))
451 (cond
452 ((or (char/= #\$ (aref (string name) 0))
453 maclisp-narg-p)
454 ;; If NAME doesn't start with $, it's an internal function not
455 ;; directly exposed to the user. Basically define the function
456 ;; as is, taking care to support the Maclisp narg syntax.
457 (cond (maclisp-narg-p
458 ;; Support MacLisp narg syntax: (defun foo a ...)
459 `(progn
460 ,(add-props)
461 (defun ,name (&rest narg-rest-argument
462 &aux (,lambda-list (length narg-rest-argument)))
463 ,@body)))
465 `(progn
466 ,(add-props)
467 (defun ,name ,lambda-list ,@body)))))
469 ;; Function name begins with $, so it's exposed to the user;
470 ;; carefully check the number of arguments and print a nice
471 ;; message if the number doesn't match the expected number.
472 #+nil
473 (unless (char= #\$ (aref (string name) 0))
474 (warn "First character of function name must start with $: ~S~%" name))
475 (multiple-value-bind (required-args
476 optional-args
477 restp
478 rest-arg
479 keywords-present-p
480 keyword-args
481 allow-other-keys-p)
482 (parse-lambda-list lambda-list)
484 (when (and keywords-present-p
485 (or optional-args restp))
486 (error "Keyword args cannot be used with optional args or rest args"))
488 (let* ((required-len (length required-args))
489 (optional-len (length optional-args))
490 (impl-name (intern (concatenate 'string
491 (subseq (string name) 1)
492 "-IMPL")))
493 (impl-doc (format nil "Implementation for ~S" name))
494 (nargs (gensym "NARGS-"))
495 (args (gensym "REST-ARG-"))
496 (rest-name (gensym "REST-ARGS"))
497 (pretty-fname
498 (cond (optional-args
499 ;; Can't do much with optional args, so just use the function name.
500 name)
501 (restp
502 ;; Use maxima syntax for rest args: foo(a,b,[c]);
503 `((,name) ,@required-args ((mlist) ,rest-arg)))
504 (keywords-present-p
505 ;; Not exactly sure how to do this
506 (let* ((index 1)
507 (keys (mapcar
508 #'(lambda (k)
509 (multiple-value-bind (name val)
510 (if (consp k)
511 (values
512 (intern (format nil "$~A" (car k)))
513 (second k))
514 (values
515 (intern (format nil "$~A" k))
516 nil))
517 (incf index)
518 `((mequal) ,name ,val)))
519 keyword-args)))
520 `((,name) ,@required-args ,@keys)))
522 ;; Just have required args: foo(a,b)
523 `((,name) ,@required-args))))
524 (maxima-keywords
525 (unless allow-other-keys-p
526 (mapcar #'(lambda (x)
527 (intern (concatenate
528 'string "$"
529 (symbol-name
530 (if (consp x)
531 (car x)
532 x)))))
533 keyword-args))))
535 (multiple-value-bind (forms decls doc-string)
536 (parse-body body nil t)
537 (setf doc-string (if doc-string (list doc-string)))
538 `(progn
539 (defun ,impl-name ,lambda-list
540 ,impl-doc
541 ,@decls
542 (block ,name
543 (let ((%%pretty-fname ',pretty-fname))
544 (declare (ignorable %%pretty-fname))
545 ,@forms)))
546 ,(add-props)
547 ; We don't put this putprop in add-props because
548 ; add-props is for both user and internal functions
549 ; while the impl-name property is only for user
550 ; functions.
551 (putprop ',name ',impl-name 'impl-name)
552 (defun ,name (&rest ,args)
553 ,@doc-string
554 (let ((,nargs (length ,args)))
555 (declare (ignorable ,nargs))
556 ,@(cond
557 ((or restp keywords-present-p)
558 ;; When a rest arg is given, there's no upper
559 ;; limit to the number of args. Just check that
560 ;; we have enough args to satisfy the required
561 ;; args.
562 (unless (null required-args)
563 `((when (< ,nargs ,required-len)
564 (merror (intl:gettext "~M: expected at least ~M arguments but got ~M: ~M")
565 ',pretty-fname
566 ,required-len
567 ,nargs
568 (list* '(mlist) ,args))))))
569 (optional-args
570 ;; There are optional args (but no rest
571 ;; arg). Verify that we don't have too many args,
572 ;; and that we still have all the required args.
574 (when (> ,nargs ,(+ required-len optional-len))
575 (merror (intl:gettext "~M: expected at most ~M arguments but got ~M: ~M")
576 ',pretty-fname
577 ,(+ required-len optional-len)
578 ,nargs
579 (list* '(mlist) ,args)))
580 (when (< ,nargs ,required-len)
581 (merror (intl:gettext "~M: expected at least ~M arguments but got ~M: ~M")
582 ',pretty-fname
583 ,required-len
584 ,nargs
585 (list* '(mlist) ,args)))))
587 ;; We only have required args.
588 `((unless (= ,nargs ,required-len)
589 (merror (intl:gettext "~M: expected exactly ~M arguments but got ~M: ~M")
590 ',pretty-fname
591 ,required-len
592 ,nargs
593 (list* '(mlist) ,args))))))
594 ,(cond
595 (keywords-present-p
596 `(apply #',impl-name
597 (append
598 (subseq ,args 0 ,required-len)
599 (defmfun-keywords ',pretty-fname
600 (nthcdr ,required-len ,args)
601 ',maxima-keywords))))
603 `(apply #',impl-name ,args)))))
604 ,(cond
605 (keywords-present-p
606 `(define-compiler-macro ,name (&rest ,rest-name)
607 (let ((args (append (subseq ,rest-name 0 ,required-len)
608 (defmfun-keywords ',pretty-fname
609 (nthcdr ,required-len ,rest-name)
610 ',maxima-keywords))))
611 `(,',impl-name ,@args))))
613 `(define-compiler-macro ,name (&rest ,rest-name)
614 `(,',impl-name ,@,rest-name)))))))))))))
616 (defmacro defmfun (name lambda-list &body body)
617 (flet ((add-props ()
618 ;; We make sure that the ARG-LIST property is added
619 ;; first, so that it will end up last in the list.
620 `(progn
621 (putprop ',name ',lambda-list 'arg-list)
622 (defprop ,name t translated))))
623 (let ((impl-name (intern (concatenate 'string
624 (subseq (string name) 1)
625 "-IMPL")))
626 (maclisp-narg-p (and (symbolp lambda-list) (not (null lambda-list)))))
627 (cond
628 ((or (char/= #\$ (aref (string name) 0))
629 maclisp-narg-p)
630 ;; If NAME doesn't start with $, it's an internal function not
631 ;; directly exposed to the user. Basically define the function
632 ;; as is, taking care to support the Maclisp narg syntax.
633 (cond (maclisp-narg-p
634 ;; Support MacLisp narg syntax: (defun foo a ...)
635 `(progn
636 ,(add-props)
637 (defun ,name (&rest narg-rest-argument
638 &aux (,lambda-list (length narg-rest-argument)))
639 ,@body)))
641 `(progn
642 ,(add-props)
643 (defun ,name ,lambda-list ,@body)))))
645 ;; Function name begins with $, so it's exposed to the user;
646 ;; carefully check the number of arguments and print a nice
647 ;; message if the number doesn't match the expected number.
648 #+nil
649 (unless (char= #\$ (aref (string name) 0))
650 (warn "First character of function name must start with $: ~S~%" name))
651 `(progn
652 (defun-checked-form (,name ,impl-name) ,lambda-list
653 ,@body)
654 ,(add-props)
655 ;; We don't put this putprop in add-props because
656 ;; add-props is for both user and internal functions
657 ;; while the impl-name property is only for user
658 ;; functions.
659 (putprop ',name ',impl-name 'impl-name)))))))
661 ;; Examples:
662 ;; (defmfun $foobar (a b) (list '(mlist) a b))
663 ;; (defmfun $foobar1 (a b &optional c) (list '(mlist) a b c))
664 ;; (defmfun $foobar1a (a b &optional (c 99)) (list '(mlist) a b c))
665 ;; (defmfun $foobar2 (a b &rest c) (list '(mlist) a b (list* '(mlist) c)))
666 ;; (defmfun $foobar3 (a b &optional c &rest d) "foobar3 function" (list '(mlist) a b c (list* '(mlist) d)))
668 ;; (defmfun $foobar4 (a b &key c) (list '(mlist) a b c))
669 ;; (defmfun $foobar5 (a b &key (c 42)) (list '(mlist) a b c))
670 ;; (defmfun $foobar6 (a b &key (c 42) &allow-other-keys) (list '(mlist) a b c))
672 ;; foobar5(1,2) => [1, 2, 42]
673 ;; foobar5(1,2,c=99) => [1, 2, 99]
674 ;; foobar5(1,2,c=99,d=4) => error: unrecognized keyword d
675 ;; foobar6(1,2,c=42,d=99) => [1, 2, 42]
677 ;; This works by accident, kind of:
678 ;; (defmfun $baz (a &aux (b (1+ a))) (list '(mlist) a b))
680 ;; This should produce compile errors
681 ;; (defmfun $zot (a &optional c &key b) (list '(mlist) a b))
684 ;; Defines a simplifying function for Maxima whose name is BASE-NAME.
685 ;; The noun and verb properties are set up appropriately, along with
686 ;; setting the operator property. The noun form is created from the
687 ;; BASE-NAME by prepending a "%"; the verb form, by prepending "$".
688 ;; The verb function is defined appropriately too.
690 ;; For example, let's say we want to define a Maxima function named
691 ;; foo of two args with a corresponding simplifier to simplify special
692 ;; cases or numerically evaluate it. Then:
694 ;; (def-simplifier foo (x y)
695 ;; (cond ((float-numerical-eval-p x y)
696 ;; (foo-eval x y))
697 ;; (t
698 ;; (give-up))))
700 ;; This expands to
702 ;; (progn
703 ;; (defprop %foo simp-%foo operators)
704 ;; (defprop $foo %foo verb)
705 ;; (defprop %foo $foo noun)
706 ;; (defprop $foo %foo alias)
707 ;; (defprop %foo $foo reversealias)
708 ;; (defun simp-%foo (form #:unused-5230 #:z-5229)
709 ;; (declare (ignore #:unused-5230))
710 ;; (let ((x (simpcheck (nth 1 form) #:z-5229))
711 ;; (y (simpcheck (nth 2 form) #:z-5229)))
712 ;; (arg-count-check 2 form)
713 ;; (macrolet ((give-up ()
714 ;; '(eqtest (list '(%foo) x y) form)))
715 ;; (cond
716 ;; ((float-numerical-eval-p x y)
717 ;; (foo-eval x y))
718 ;; (t
719 ;; (give-up))))))
721 ;; Note carefully that the expansion defines a macro GIVE-UP to
722 ;; handle the default case of the simplifier when we can't do any
723 ;; simplification. Call this in the default case for the COND.
725 (defmacro def-simplifier (base-name lambda-list &body body)
726 (let* ((noun-name (intern (concatenate 'string "%" (string base-name))))
727 (verb-name (intern (concatenate 'string "$" (string base-name))))
728 (simp-name (intern (concatenate 'string "SIMP-" (string noun-name))))
729 (z-arg (gensym "Z-"))
730 (unused-arg (gensym "UNUSED-"))
731 (arg-forms (loop for arg in lambda-list
732 and count from 1
733 collect (list arg `(simpcheck (nth ,count form) ,z-arg)))))
734 `(progn
735 ;; Set up properties
736 (defprop ,noun-name ,simp-name operators)
737 ;; The verb and alias properties are needed to make things like
738 ;; quad_qags(jacobi_sn(x,.5)...) work.
739 (defprop ,verb-name ,noun-name verb)
740 (defprop ,verb-name ,noun-name alias)
741 ;; The reversealias property is needed by grind to print out
742 ;; the right thing. Without it, grind(jacobi_sn(x,m)) prints
743 ;; '?%jacobi_sn(x,m)". Also needed for labels in plots which
744 ;; would show up as %jacobi_sn instead of jacobi_sn.
745 (defprop ,noun-name ,verb-name reversealias)
747 ;; Define the simplifier
748 (defun ,simp-name (form ,unused-arg ,z-arg)
749 (declare (ignore ,unused-arg))
750 (arg-count-check ,(length lambda-list) form)
751 (let ,arg-forms
752 (flet ((give-up ()
753 ;; Should this also return from the function?
754 ;; That would fit in better with giving up.
755 (eqtest (list '(,noun-name) ,@lambda-list) form)))
756 ,@body))))))