1 ;; -*- Mode: Lisp; Package: Maxima; Syntax: Common-Lisp; Base: 10 -*- ;;;;
2 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
4 ;;; Copyright (c) 1984,1987 by William Schelter,University of Texas ;;;;;
5 ;;; All rights reserved ;;;;;
6 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
10 (defmacro defun-prop
(f arg
&body body
)
12 #+gcl
(eval-when (eval) (compiler::compiler-def-hook
(first f
) body
))
13 `(setf (get ',(first f
) ',(second f
)) #'(lambda ,arg
,@body
)))
15 ;; Should we give this a different name?
16 (defvar *fortran-print
* nil
17 "Tells EXPLODEN we are printing numbers for Fortran so include the exponent marker.")
19 (defun appears (tree var
)
20 (cond ((equal tree var
)
23 (t (appears (car tree
) var
)
24 (appears (cdr tree
) var
)))
27 (defun appears1 (tree var
)
32 (appears (car tree
) var
)
33 (appears (cdr tree
) var
)))
36 (defun appears-in (tree var
)
37 "Yields t if var appears in tree"
39 (if (or (symbolp var
) (fixnump var
))
43 ;; A more portable implementation of ml-typep. I (rtoy) think it
44 ;; would probably be better to replace uses of
45 ;; ml-typep with the corresponding Common Lisp typep or type-of or
46 ;; subtypep, as appropriate.
47 (defun ml-typep (x &optional type
)
49 (cl:let
((pred (get type
'ml-typep
)))
61 (cl:hash-table
'hash-table
)
65 (defprop :extended-number extended-number-p ml-typep
)
66 (defprop array arrayp ml-typep
)
67 (defprop atom atom ml-typep
)
70 (eval-when (:compile-toplevel
:load-toplevel
:execute
)
71 (shadow '(cl:compiled-function-p
) (find-package :maxima
))
74 (defun compiled-function-p (x)
75 (and (functionp x
) (not (symbolp x
))
76 (not (eval:interpreted-function-p x
))))
78 (defprop compiled-function compiled-function-p ml-typep
)
79 (defprop extended-number extended-number-p ml-typep
)
80 (defprop fixnum fixnump ml-typep
)
81 (defprop list consp ml-typep
)
82 (defprop number numberp ml-typep
)
83 (defprop string stringp ml-typep
)
84 (defprop symbol symbolp ml-typep
)
87 (defvar *maxima-arrays
* nil
88 "Trying to track down any functional arrays in maxima")
90 (defun *array
(name maclisp-type
&rest dimlist
&aux aarray
)
91 (cond ((member maclisp-type
'(readtable obarray
) :test
#'eq
)
92 (error " bad type ~S" maclisp-type
)))
93 (pushnew name
*maxima-arrays
*) ;for tracking down old ones.
94 (setq aarray
(make-array dimlist
:initial-element
(case maclisp-type
98 (cond ((null name
) aarray
)
100 (setf (symbol-array name
) aarray
)
102 (t (error "~S is illegal first arg for *array" name
))))
104 ;;; Change maclisp array referencing.
105 ;;; Idea1: Make changes in the code which will allow the code to still run in maclisp,
106 ;;;yet will allow, with the appropriate macro definitions of array,arraycall, etc,
107 ;;;to put the array into the value-cell.
108 ;;; Idea2: Make changes in the array referencing of (a dim1 dim2..) to (arraycall nil (symbol-array a) dim1..)
109 ;;;which would then allow expansion into something which is common lisp compatible, for
110 ;;;the day when (a 2 3) no longer is equivalent to (aref (symbol-function a) 2 3).
111 ;;;I. change (array a typ dim1 dim2..) to expand to (defvar a (make-array (list dim1 dim2 ...) :type typ')
112 ;;;II. change (a dim1 dim2..) to (arraycall nil (symbol-array a) dim1 dim2 ..)
114 ;;(defmacro symbol-array (ar)
115 ;; `(symbol-function ,ar))
116 ;;(defmacro arraycall (ignore ar &rest dims)
117 ;; `(aref ,ar ,@ dims))
118 ;;;IV. change array setting to use (setf (arraycall nil ar dim1.. ) val)
119 ;;;which will generate the correct setting code on the lispm and will
120 ;;;still work in maclisp.
122 (defmacro maxima-error
(datum &rest args
)
123 `(cerror "without any special action" ,datum
,@args
))
125 (defmacro show
(&rest l
)
127 collecting
`(format t
"~%The value of ~A is ~A" ',v
,v
) into tem
128 finally
(return `(progn ,@ tem
))))
130 (defmacro defquote
(fn (aa . oth
) &body rest
&aux help ans
)
131 (setq help
(intern (format nil
"~a-~a" fn
'#:aux
)))
132 (cond ((eq aa
'&rest
)
135 `(defmacro ,fn
(&rest
,(car oth
))
136 `(,',help
',,(car oth
)))
137 `(defun ,help
(,(car oth
)) ,@rest
))))
138 (t (when (member '&rest oth
)
139 (error "at present &rest may only occur as first item in a defquote argument"))
142 `(defmacro ,fn
(,aa . other
)
143 (setq other
(loop for v in other collecting
(list 'quote v
)))
144 (check-arg other
(eql (length other
) ,(length oth
))
145 ,(format nil
"wrong number of args to ~a" fn
))
146 `(,',help
',,aa
,@ other
))
147 `(defun ,help
(,aa
,@ oth
) ,@rest
)))))
151 ;;the resulting function will translate to defvar and will behave
152 ;;correctly for the evaluator.
154 ;;(defun gg fexpr (ll)
156 ;;(defquote gg (&rest ll)
159 ;;(DEFQUOTE GG ( &rest C)
160 ;; (list (car c) (second c) ))
161 ;;the big advantage of using the following over defmspec is that it
162 ;;seems to translate more easily, since it is a fn.
163 ;;New functions which wanted quoted arguments should be defined using
167 (defun onep (x) (eql 1 x
))
169 (defun extended-number-p (x)
170 (member (type-of x
) '(bignum rational float
)))
172 (defvar *scan-string-buffer
* nil
)
174 (defun macsyma-read-string (a-string &aux answer
)
175 (cond ((not (or (search "$" a-string
:test
#'char-equal
)
176 (search ";" a-string
:test
#'char-equal
)))
177 (vector-push-extend #\$ a-string
)))
178 (with-input-from-string (stream a-string
)
179 (setq answer
(third (mread stream
)))
182 (defvar *sharp-read-buffer
*
183 (make-array 140 :element-type
' #.
(array-element-type "a") :fill-pointer
0 :adjustable t
))
185 (defmfun $-read-aux
(arg stream
&aux
(meval-flag t
) (*mread-prompt
* ""))
186 (declare (special *mread-prompt
*)
188 (setf (fill-pointer *sharp-read-buffer
*) 0)
189 (cond ((eql #\$
(peek-char t stream
))
191 (setq meval-flag nil
)))
192 (with-output-to-string (st *sharp-read-buffer
*)
194 (loop while
(not (eql char
#\$
))
196 (setq char
(tyi stream
))
197 (write-char char st
))))
199 (list 'meval
* (list 'quote
(macsyma-read-string *sharp-read-buffer
*)))
200 (list 'quote
(macsyma-read-string *sharp-read-buffer
*))))
202 (defun x$-cl-macro-read
(stream sub-char arg
)
203 (declare (ignore arg
))
204 ($-read-aux sub-char stream
))
206 (set-dispatch-macro-character #\
# #\$
#'x$-cl-macro-read
)
208 (defvar *macsyma-readtable
*)
210 (defun find-lisp-readtable-for-macsyma ()
211 (cond ((and (boundp '*macsyma-readtable
*)
212 (readtablep *macsyma-readtable
*))
214 (t (setq *macsyma-readtable
* (copy-readtable nil
))
215 (set-dispatch-macro-character #\
# #\$
'x$-cl-macro-read
*macsyma-readtable
*)
216 *macsyma-readtable
*)))
218 (defun set-readtable-for-macsyma ()
219 (setq *readtable
* (find-lisp-readtable-for-macsyma)))
221 (defvar *reset-var
* t
)
223 (defvar *variable-initial-values
* (make-hash-table)
224 "Hash table containing all Maxima defmvar variables and their initial
227 (defmacro defmvar
(var &rest val-and-doc
)
228 "If *reset-var* is true then loading or eval'ing will reset value, otherwise like defvar"
229 (cond ((> (length val-and-doc
) 2)
230 (setq val-and-doc
(list (car val-and-doc
) (second val-and-doc
)))))
232 (unless (gethash ',var
*variable-initial-values
*)
233 (setf (gethash ',var
*variable-initial-values
*)
234 ,(first val-and-doc
)))
235 (defvar ,var
,@val-and-doc
)))
237 (defmfun $mkey
(variable)
238 "($mkey '$demo)==>:demo"
239 (intern (string-left-trim "$" (string variable
)) 'keyword
))
242 `(narg1 ,x narg-rest-argument
))
244 (defun narg1 (x l
&aux tem
)
245 (cond ((null x
) (length l
))
246 (t (setq tem
(nthcdr (1- x
) l
))
247 (cond ((null tem
) (error "arg ~A beyond range ~A " x
(length l
)))
250 (defmacro listify
(x)
251 `(listify1 ,x narg-rest-argument
))
253 (defmacro setarg
(i val
)
254 `(setarg1 ,i
,val narg-rest-argument
))
256 (defun setarg1 (i val l
)
257 (setf (nth (1- i
) l
) val
)
260 (defun listify1 (n narg-rest-argument
)
261 (cond ((minusp n
) (copy-list (last narg-rest-argument
(- n
))) )
263 (t (subseq narg-rest-argument
0 n
))))
265 ;; This has been replaced by src/defmfun-check.lisp. I'm leaving this
266 ;; here for now until we finish up fixing everything like using defun
267 ;; for internal functions and updating user-exposed functions to use
268 ;; defmfun instead of defun.
270 (defmacro defmfun
(function &body rest
&aux .n.
)
271 (cond ((and (car rest
) (symbolp (car rest
)))
272 ;;old maclisp narg syntax
273 (setq .n.
(car rest
))
275 `(&rest narg-rest-argument
&aux
(, .n.
(length narg-rest-argument
))))))
277 ;; I (rtoy) think we can consider all defmfun's as translated functions.
278 (defprop ,function t translated
)
279 (defun ,function .
,rest
)))
282 ;;(defun foo a (show a )(show (listify a)) (show (arg 3)))
284 (defmacro defun-maclisp
(function &body body
&aux .n.
)
285 (when (typep body
'(cons symbol
))
286 ;;old maclisp narg syntax
287 (setq .n.
(car body
))
289 (cons `(&rest narg-rest-argument
&aux
(, .n.
(length narg-rest-argument
)))
292 ;; I (rtoy) think we can consider all defmfun's as translated functions.
293 (defprop ,function t translated
)
294 (defun ,function .
,body
)))
296 (defun exploden (symb)
297 (let* (#+(and gcl
(not gmp
)) (big-chunk-size 120)
298 #+(and gcl
(not gmp
)) (tentochunksize (expt 10 big-chunk-size
))
300 (cond ((symbolp symb
)
301 (setq string
(print-invert-case symb
)))
303 (setq string
(exploden-format-float symb
)))
306 ;; When obase > 10, prepend leading zero to
307 ;; ensure that output is readable as a number.
308 (let ((leading-digit (if (> *print-base
* 10) #\
0)))
310 #+(and gcl
(not gmp
))
316 do
(multiple-value-setq (big rem
)
317 (floor big tentochunksize
))
319 while
(not (eql 0 big
)))))
320 (setq chunks
(nreverse chunks
))
321 (setq ans
(coerce (format nil
"~d" (car chunks
)) 'list
))
322 (if (and leading-digit
(not (digit-char-p (car ans
) 10.
)))
323 (setq ans
(cons leading-digit ans
)))
324 (loop for v in
(cdr chunks
)
325 do
(setq tem
(coerce (format nil
"~d" v
) 'list
))
326 (loop for i below
(- big-chunk-size
(length tem
))
327 do
(setq tem
(cons #\
0 tem
)))
328 (setq ans
(nconc ans tem
)))
329 (return-from exploden ans
)))
331 (setq string
(format nil
"~A" symb
))
332 (setq string
(coerce string
'list
))
333 (if (and leading-digit
(not (digit-char-p (car string
) 10.
)))
334 (setq string
(cons leading-digit string
)))
335 (return-from exploden string
)))))
337 (t (setq string
(format nil
"~A" symb
))))
338 (assert (stringp string
))
339 (coerce string
'list
)))
341 (defvar *exploden-strip-float-zeros
* t
) ;; NIL => allow trailing zeros
343 (defun exploden-format-float (symb)
344 (declare (special $maxfpprintprec
))
347 (effective-printprec (if (or (= $fpprintprec
0)
348 (> $fpprintprec $maxfpprintprec
))
351 ;; When printing out something for Fortran, we want to be
352 ;; sure to print the exponent marker so that Fortran
353 ;; knows what kind of number it is. It turns out that
354 ;; Fortran's exponent markers are the same as Lisp's so
355 ;; we just need to make sure the exponent marker is
359 ;; Strings for non-finite numbers as specified for input in Fortran 2003 spec;
360 ;; they apparently did not exist in earlier versions.
361 ((float-nan-p symb
) "NAN")
362 ((float-inf-p symb
) (if (< symb
0) "-INF" "INF"))
363 (t (format nil
"~e" symb
))))
364 (multiple-value-bind (form digits
)
368 ;; Work around for GCL bug #47404.
369 ;; Avoid numeric comparisons with NaN, which erroneously return T.
370 #+gcl
((or (float-inf-p symb
) (float-nan-p symb
))
371 (return-from exploden-format-float
(format nil
"~a" symb
)))
374 ((integer-log10 (floor (/ (log a
) #.
(log 10.0))))
375 (scale (1+ integer-log10
)))
376 (if (< scale effective-printprec
)
377 (values "~,vf" (- effective-printprec scale
))
378 (values "~,ve" (1- effective-printprec
)))))
379 #-gcl
((or (float-inf-p symb
) (float-nan-p symb
))
380 (return-from exploden-format-float
(format nil
"~a" symb
)))
382 (values "~,ve" (1- effective-printprec
))))
384 ;; Call FORMAT using format string chosen above.
385 (setq string
(format nil form digits a
))
387 ;; EXPLODEN is often called after NFORMAT, so it doesn't
388 ;; usually see a negative argument. I can't guarantee
389 ;; a non-negative argument, so handle negative here.
391 (setq string
(concatenate 'string
"-" string
)))))
393 (if *exploden-strip-float-zeros
*
394 (or (strip-float-zeros string
) string
)
397 (defun trailing-zeros-regex-f-0 (s)
398 (pregexp:pregexp-match-positions
'#.
(pregexp:pregexp
"^(.*\\.[0-9]*[1-9])00*$")
400 (defun trailing-zeros-regex-f-1 (s)
401 (pregexp:pregexp-match-positions
'#.
(pregexp::pregexp
"^(.*\\.0)00*$")
403 (defun trailing-zeros-regex-e-0 (s)
404 (pregexp:pregexp-match-positions
'#.
(pregexp:pregexp
"^(.*\\.[0-9]*[1-9])00*([^0-9][+-][0-9]*)$")
406 (defun trailing-zeros-regex-e-1 (s)
407 (pregexp:pregexp-match-positions
'#.
(pregexp:pregexp
"^(.*\\.0)00*([^0-9][+-][0-9]*)$")
410 ;; Return S with trailing zero digits stripped off, or NIL if there are none.
411 (defun strip-float-zeros (s)
414 ((setq matches
(or (trailing-zeros-regex-f-0 s
) (trailing-zeros-regex-f-1 s
)))
416 ((group1 (elt matches
1)))
417 (subseq s
(car group1
) (cdr group1
))))
418 ((setq matches
(or (trailing-zeros-regex-e-0 s
) (trailing-zeros-regex-e-1 s
)))
420 ((group1 (elt matches
1))
421 (s1 (subseq s
(car group1
) (cdr group1
)))
422 (group2 (elt matches
2))
423 (s2 (subseq s
(car group2
) (cdr group2
))))
424 (concatenate 'string s1 s2
)))
427 (defun explodec (symb) ;is called for symbols and numbers
428 (loop for v in
(coerce (print-invert-case symb
) 'list
)
429 collect
(intern (string v
))))
431 ;;; If the 'string is all the same case, invert the case. Otherwise,
434 (defun maybe-invert-string-case (string)
437 (length (length string
)))
439 (let ((ch (char string i
)))
440 (when (both-case-p ch
)
441 (if (upper-case-p ch
)
443 (setq all-upper nil
)))))
445 (string-downcase string
))
447 (string-upcase string
))
452 (defun maybe-invert-string-case (string)
453 (cond (#+scl
(eq ext
:*case-mode
* :lower
)
454 #+allegro
(eq excl
:*current-case-mode
* :case-sensitive-lower
)
459 (length (length string
)))
461 (let ((ch (aref string i
)))
462 (when (both-case-p ch
)
463 (if (upper-case-p ch
)
465 (setq all-upper nil
)))))
467 (string-downcase string
))
469 (string-upcase string
))
473 (defun intern-invert-case (string)
474 ;; Like read-from-string with readtable-case :invert
475 ;; Supply package argument in case this function is called
476 ;; from outside the :maxima package.
477 (intern (maybe-invert-string-case string
) :maxima
))
480 #-
(or gcl scl allegro
)
481 (let ((local-table (copy-readtable nil
)))
482 (setf (readtable-case local-table
) :invert
)
483 (defun print-invert-case (sym)
484 (let ((*readtable
* local-table
)
485 (*print-case
* :upcase
))
486 (princ-to-string sym
))))
489 (let ((local-table (copy-readtable nil
)))
490 (unless #+scl
(eq ext
:*case-mode
* :lower
)
491 #+allegro
(eq excl
:*current-case-mode
* :case-sensitive-lower
)
492 (setf (readtable-case local-table
) :invert
))
493 (defun print-invert-case (sym)
494 (cond (#+scl
(eq ext
:*case-mode
* :lower
)
495 #+allegro
(eq excl
:*current-case-mode
* :case-sensitive-lower
)
496 (let ((*readtable
* local-table
)
497 (*print-case
* :downcase
))
498 (princ-to-string sym
)))
500 (let ((*readtable
* local-table
)
501 (*print-case
* :upcase
))
502 (princ-to-string sym
))))))
505 (defun print-invert-case (sym)
507 (let* ((str (princ-to-string sym
))
511 (map 'string
(lambda (c)
512 (cond ((upper-case-p c
)
520 (if (and have-upper have-lower
)
523 (t (princ-to-string sym
))))
525 (defun implode (list)
526 (declare (optimize (speed 3)))
527 (intern-invert-case (map 'string
#'(lambda (v)
530 (symbol (char (symbol-name v
) 0))
531 (integer (code-char v
))))
534 ;; Note: symb can also be a number, not just a symbol.
535 (defun explode (symb)
536 (declare (optimize (speed 3)))
537 (map 'list
#'(lambda (v) (intern (string v
))) (format nil
"~s" symb
)))
539 ;;; return the first character of the name of a symbol or a string or char
540 (defun get-first-char (symb)
541 (declare (optimize (speed 3)))
542 (char (string symb
) 0))
544 (defun getchar (symb i
)
545 (let ((str (string symb
)))
546 (if (<= 1 i
(length str
))
547 (intern (string (char str
(1- i
))))
556 collecting
(char (symbol-name v
) 0) into tem
559 collecting v into tem
560 else do
(maxima-error "bad entry")
562 (return (make-symbol (maybe-invert-string-case (coerce tem
'string
))))))
564 ;;for those window labels etc. that are wrong type.
565 ;; is not only called for symbols, but also on numbers
567 (length (explodec sym
)))
569 (defun flatsize (sym &aux
(*print-circle
* t
))
570 (length (exploden sym
)))
572 (defmacro safe-zerop
(x)
574 `(and (numberp ,x
) (zerop ,x
))
576 (and (numberp .x.
) (zerop .x.
)))))
578 (defmacro signp
(sym x
)
587 (n `(not (zerop ,x
))))))
588 `(and (numberp ,x
) ,test
)))
592 (defvar *prompt-on-read-hang
* nil
)
593 (defvar *read-hang-prompt
* "")
595 (defun tyi-raw (&optional
(stream *standard-input
*) eof-option
)
596 ;; Adding this extra EOF test, because the testsuite generates
597 ;; unexpected end of input-stream with Windows XP and GCL 2.6.8.
599 (when (eql (peek-char nil stream nil eof-option
) eof-option
)
600 (return-from tyi-raw eof-option
))
602 (let ((ch (read-char-no-hang stream nil eof-option
)))
606 (when (and *prompt-on-read-hang
* *read-hang-prompt
*)
607 (princ *read-hang-prompt
*)
608 (finish-output *standard-output
*))
609 (read-char stream nil eof-option
)))))
611 (defun tyi (&optional
(stream *standard-input
*) eof-option
)
612 (let ((ch (tyi-raw stream eof-option
)))
613 (if (eql ch eof-option
)
615 (backslash-check ch stream eof-option
))))
617 ; The sequences of characters
618 ; <anything-except-backslash>
619 ; (<backslash> <newline> | <backslash> <return> | <backslash> <return> <newline>)+
621 ; are reduced to <anything-except-backslash> <anything> .
622 ; Note that this has no effect on <backslash> <anything-but-newline-or-return> .
624 (let ((previous-tyi #\a))
625 (defun backslash-check (ch stream eof-option
)
626 (if (eql previous-tyi
#\\ )
627 (progn (setq previous-tyi
#\a) ch
)
630 (let ((next-char (peek-char nil stream nil eof-option
)))
631 (if (or (eql next-char
#\newline
) (eql next-char
#\return
))
632 (eat-continuations ch stream eof-option
)
635 ; We have just read <backslash> and we know the next character is <newline> or <return>.
636 ; Eat line continuations until we come to something which doesn't match, or we reach eof.
637 (defun eat-continuations (ch stream eof-option
)
638 (setq ch
(tyi-raw stream eof-option
))
639 (do () ((not (or (eql ch
#\newline
) (eql ch
#\return
))))
640 (let ((next-char (peek-char nil stream nil eof-option
)))
641 (if (and (eql ch
#\return
) (eql next-char
#\newline
))
642 (tyi-raw stream eof-option
)))
643 (setq ch
(tyi-raw stream eof-option
))
644 (let ((next-char (peek-char nil stream nil eof-option
)))
645 (if (and (eql ch
#\\ ) (or (eql next-char
#\return
) (eql next-char
#\newline
)))
646 (setq ch
(tyi-raw stream eof-option
))
647 (return-from eat-continuations ch
))))
650 (defmfun $timedate
(&optional
(time (get-universal-time)) tz
)
652 ((and (consp tz
) (eq (caar tz
) 'rat
))
653 (setq tz
(/ (second tz
) (third tz
))))
655 (setq tz
(rationalize tz
))))
656 (if tz
(setq tz
(/ (round tz
1/60) 60)))
658 ((time-integer (mfuncall '$floor time
))
659 (time-fraction (sub time time-integer
))
660 (time-millis (mfuncall '$round
(mul 1000 time-fraction
))))
661 (when (= time-millis
1000)
662 (setq time-integer
(1+ time-integer
))
663 (setq time-millis
0))
665 (second minute hour date month year day-of-week dst-p tz
)
666 ;; Some Lisps allow TZ to be null but CLHS doesn't explicitly allow it,
667 ;; so work around null TZ here.
668 (if tz
(decode-universal-time time-integer
(- tz
))
669 (decode-universal-time time-integer
))
670 (declare (ignore day-of-week
#+gcl dst-p
))
671 ;; DECODE-UNIVERSAL-TIME might return a timezone offset
672 ;; which is a multiple of 1/3600 but not 1/60.
673 ;; We need a multiple of 1/60 because our formatted
674 ;; timezone offset has only minutes and seconds.
675 (if (/= (mod tz
1/60) 0)
676 ($timedate time-integer
(/ (round (- tz
) 1/60) 60))
678 #-gcl
(if dst-p
(- 1 tz
) (- tz
))
679 #+gcl
(- tz
) ; bug in gcl https://savannah.gnu.org/bugs/?50570
682 (tz-hours tz-hour-fraction
)
685 ((tz-sign (if (<= 0 tz-offset
) #\
+ #\-
)))
686 (if (= time-millis
0)
687 (format nil
"~4,'0d-~2,'0d-~2,'0d ~2,'0d:~2,'0d:~2,'0d~a~2,'0d:~2,'0d"
688 year month date hour minute second tz-sign
(abs tz-hours
) (floor (* 60 (abs tz-hour-fraction
))))
689 (format nil
"~4,'0d-~2,'0d-~2,'0d ~2,'0d:~2,'0d:~2,'0d.~3,'0d~a~2,'0d:~2,'0d"
690 year month date hour minute second time-millis tz-sign
(abs tz-hours
) (floor (* 60 (abs tz-hour-fraction
))))))))))))
692 ;; Parse date/time strings in these formats (and only these):
694 ;; YYYY-MM-DD([ T]hh:mm:ss)?([,.]n+)?([+-]hh:mm)?
695 ;; YYYY-MM-DD([ T]hh:mm:ss)?([,.]n+)?([+-]hhmm)?
696 ;; YYYY-MM-DD([ T]hh:mm:ss)?([,.]n+)?([+-]hh)?
697 ;; YYYY-MM-DD([ T]hh:mm:ss)?([,.]n+)?[Z]?
699 ;; where (...)? indicates an optional group (occurs zero or one times)
700 ;; ...+ indicates one or more instances of ...,
701 ;; and [...] indicates literal character alternatives.
703 ;; Trailing unparsed stuff causes the parser to fail (return NIL).
705 ;; Originally, these functions all looked like
707 ;; (defun match-date-yyyy-mm-dd (s)
708 ;; (pregexp:pregexp-match-positions
709 ;; '#.(pregexp:pregexp "^([0-9][0-9][0-9][0-9])-([0-9][0-9])-([0-9][0-9])")
712 ;; However, sbcl produces incorrect results for this. For example,
714 ;; (match-date-yyyy-mm-dd "1900-01-01 16:00:00-08:00")
716 ;; returns ((0 . 10) (0 . 4) (8 . 10) NIL). But the correct answer is
717 ;; ((0 . 10) (0 . 4) (5 . 7) (8 . 10)).
719 ;; But if you replace the '#.(pregexp:pregexp ...) with
720 ;; (pregexp:pregexp ...), sbcl works. But then we end up compiling
721 ;; the the regexp on every call. So we use a closure so the regexp is
722 ;; compiled only once.
723 (let ((pat (pregexp:pregexp
"^([0-9][0-9][0-9][0-9])-([0-9][0-9])-([0-9][0-9])")))
724 (defun match-date-yyyy-mm-dd (s)
725 (pregexp:pregexp-match-positions
729 (let ((pat (pregexp:pregexp
"^[ T]([0-9][0-9]):([0-9][0-9]):([0-9][0-9])")))
730 (defun match-time-hh-mm-ss (s)
731 (pregexp:pregexp-match-positions
735 (let ((pat (pregexp:pregexp
"^[,.]([0-9][0-9]*)")))
736 (defun match-fraction-nnn (s)
737 (pregexp:pregexp-match-positions
742 (let ((pat (pregexp:pregexp
"^([+-])([0-9][0-9]):([0-9][0-9])$")))
743 (defun match-tz-hh-mm (s)
744 (pregexp:pregexp-match-positions
748 (let ((pat (pregexp:pregexp
"^([+-])([0-9][0-9])([0-9][0-9])$")))
749 (defun match-tz-hhmm (s)
750 (pregexp:pregexp-match-positions
754 (let ((pat (pregexp:pregexp
"^([+-])([0-9][0-9])$")))
755 (defun match-tz-hh (s)
756 (pregexp:pregexp-match-positions
760 (let ((pat (pregexp:pregexp
"^Z$")))
761 (defun match-tz-Z (s)
762 (pregexp:pregexp-match-positions
766 (defmfun $parse_timedate
(s)
767 (setq s
(string-trim '(#\Space
#\Tab
#\Newline
#\Return
) s
))
770 (hours 0) (minutes 0) (seconds 0)
771 (seconds-fraction 0) seconds-fraction-numerator tz
)
772 (if (setq matches
(match-date-yyyy-mm-dd s
))
774 (multiple-value-setq (year month day
)
775 (pregexp-extract-groups-integers matches s
))
776 (setq s
(subseq s
(cdr (elt matches
0)))))
777 (return-from $parse_timedate nil
))
778 (when (setq matches
(match-time-hh-mm-ss s
))
779 (multiple-value-setq (hours minutes seconds
)
780 (pregexp-extract-groups-integers matches s
))
781 (setq s
(subseq s
(cdr (elt matches
0)))))
782 (when (setq matches
(match-fraction-nnn s
))
783 (multiple-value-setq (seconds-fraction-numerator)
784 (pregexp-extract-groups-integers matches s
))
785 (let ((group1 (elt matches
1)))
786 (setq seconds-fraction
(div seconds-fraction-numerator
(expt 10 (- (cdr group1
) (car group1
))))))
787 (setq s
(subseq s
(cdr (elt matches
0)))))
789 ((setq matches
(match-tz-hh-mm s
))
790 (multiple-value-bind (tz-sign tz-hours tz-minutes
)
791 (pregexp-extract-groups-integers matches s
)
792 (setq tz
(* tz-sign
(+ tz-hours
(/ tz-minutes
60))))))
793 ((setq matches
(match-tz-hhmm s
))
794 (multiple-value-bind (tz-sign tz-hours tz-minutes
)
795 (pregexp-extract-groups-integers matches s
)
796 (setq tz
(* tz-sign
(+ tz-hours
(/ tz-minutes
60))))))
797 ((setq matches
(match-tz-hh s
))
798 (multiple-value-bind (tz-sign tz-hours
)
799 (pregexp-extract-groups-integers matches s
)
800 (setq tz
(* tz-sign tz-hours
))))
801 ((setq matches
(match-tz-Z s
))
805 (return-from $parse_timedate nil
))))
807 (encode-time-with-all-parts year month day hours minutes seconds seconds-fraction
(if tz
(- tz
)))))
809 (defun pregexp-extract-groups-integers (matches s
)
810 (values-list (mapcar #'parse-integer-or-sign
811 (mapcar #'(lambda (ab)
812 (subseq s
(car ab
) (cdr ab
)))
815 (defun parse-integer-or-sign (s)
819 (t (parse-integer s
))))
821 ; Clisp (2.49) / Windows does have a problem with dates before 1970-01-01,
822 ; therefore add 400 years in that case and subtract 12622780800
823 ; (= parse_timedate("2300-01-01Z") (Lisp starts with 1900-01-01) in timezone
825 ; see discussion on mailing list circa 2015-04-21: "parse_timedate error"
827 ; Nota bene that this approach is correct only if the daylight saving time flag
828 ; is the same for the given date and date + 400 years. That is true for
829 ; dates before 1970-01-01 and after 2038-01-18, for Clisp at least,
830 ; which ignores daylight saving time for all dates in those ranges,
831 ; effectively making them all standard time.
834 (defun encode-time-with-all-parts (year month day hours minutes seconds-integer seconds-fraction tz
)
835 ;; Experimenting with Clisp 2.49 for Windows seems to show that the bug
836 ;; is triggered when local time zone is east of UTC, for times before
837 ;; 1970-01-01 00:00:00 UTC + the number of hours of the time zone.
838 ;; So apply the bug workaround to all times < 1970-01-02.
839 (if (or (< year
1970) (and (= year
1970) (= day
1)))
840 (sub (encode-time-with-all-parts (add year
400) month day hours minutes seconds-integer seconds-fraction tz
) 12622780800)
841 (add seconds-fraction
842 ;; Some Lisps allow TZ to be null but CLHS doesn't explicitly allow it,
843 ;; so work around null TZ here.
845 (encode-universal-time seconds-integer minutes hours day month year tz
)
846 (encode-universal-time seconds-integer minutes hours day month year
)))))
849 (defun encode-time-with-all-parts (year month day hours minutes seconds-integer seconds-fraction tz
)
850 (add seconds-fraction
851 ;; Some Lisps allow TZ to be null but CLHS doesn't explicitly allow it,
852 ;; so work around null TZ here.
854 (encode-universal-time seconds-integer minutes hours day month year tz
)
855 (encode-universal-time seconds-integer minutes hours day month year
))))
857 (defmfun $encode_time
(year month day hours minutes seconds
&optional tz-offset
)
859 (setq tz-offset
(sub 0 tz-offset
))
861 ((and (consp tz-offset
) (eq (caar tz-offset
) 'rat
))
862 (setq tz-offset
(/ (second tz-offset
) (third tz-offset
))))
864 (setq tz-offset
(rationalize tz-offset
))))
865 (setq tz-offset
(/ (round tz-offset
1/3600) 3600)))
867 ((seconds-integer (mfuncall '$floor seconds
))
868 (seconds-fraction (sub seconds seconds-integer
)))
869 (encode-time-with-all-parts year month day hours minutes seconds-integer seconds-fraction tz-offset
)))
871 (defmfun $decode_time
(seconds &optional tz
)
873 ((and (consp tz
) (eq (caar tz
) 'rat
))
874 (setq tz
(/ (second tz
) (third tz
))))
876 (setq tz
(rationalize tz
))))
877 (if tz
(setq tz
(/ (round tz
1/3600) 3600)))
879 ((seconds-integer (mfuncall '$floor seconds
))
880 (seconds-fraction (sub seconds seconds-integer
)))
882 (seconds minutes hours day month year day-of-week dst-p tz
)
883 ;; Some Lisps allow TZ to be null but CLHS doesn't explicitly allow it,
884 ;; so work around null TZ here.
885 (if tz
(decode-universal-time seconds-integer
(- tz
))
886 (decode-universal-time seconds-integer
))
887 (declare (ignore day-of-week
#+gcl dst-p
))
888 ;; HMM, CAN DECODE-UNIVERSAL-TIME RETURN TZ = NIL ??
890 #-gcl
(if dst-p
(- 1 tz
) (- tz
))
891 #+gcl
(- tz
) ; bug in gcl https://savannah.gnu.org/bugs/?50570
893 (list '(mlist) year month day hours minutes
(add seconds seconds-fraction
) ($ratsimp tz-offset
))))))
895 ;;Some systems make everything functionp including macros:
898 (and (not (macro-function x
))
902 ;; These symbols are shadowed because we use them also as special
904 (deff break
#'cl
:break
)
907 #+(and sbcl sb-package-locks
)
908 (defun makunbound (sym)
909 (sb-ext:without-package-locks
910 (cl:makunbound sym
)))