;;;;
;;;; ACL-COMPAT - EXCL
;;;;
;;;; This is a modified version of Chris Doubles ACL excl wrapper library
;;;; As stated in the changelogs of his original this file includes the 
;;;; IF* macro placed in the public domain by John Foderaro. 
;;;; See: http://www.franz.com/~jkf/ifstar.txt
;;;;

;;;; This file was made by Rudi Schlatte to gather
;;;; not-implementation-specific parts of acl-compat in one place.

;;;; This is the header of Chris Doubles original file. (but without Changelog)
;;;;
;;;; ACL excl wrapper library for Corman Lisp - Version 1.1
;;;;
;;;; Copyright (C) 2000 Christopher Double. All Rights Reserved.
;;;; 
;;;; License
;;;; =======
;;;; This software is provided 'as-is', without any express or implied
;;;; warranty. In no event will the author be held liable for any damages
;;;; arising from the use of this software.
;;;;
;;;; Permission is granted to anyone to use this software for any purpose,
;;;; including commercial applications, and to alter it and redistribute
;;;; it freely, subject to the following restrictions:
;;;;
;;;; 1. The origin of this software must not be misrepresented; you must
;;;;    not claim that you wrote the original software. If you use this
;;;;    software in a product, an acknowledgment in the product documentation
;;;;    would be appreciated but is not required.
;;;;
;;;; 2. Altered source versions must be plainly marked as such, and must
;;;;    not be misrepresented as being the original software.
;;;;
;;;; 3. This notice may not be removed or altered from any source 
;;;;    distribution.
;;;;

(in-package :acl-compat.excl)

(defvar if*-keyword-list '("then" "thenret" "else" "elseif"))

;this is not used in aserve, but is needed to use the franz xmlutils package with acl-compat
(defvar *current-case-mode* :case-insensitive-upper)

(defmacro if* (&rest args)
   (do ((xx (reverse args) (cdr xx))
	(state :init)
	(elseseen nil)
	(totalcol nil)
	(lookat nil nil)
	(col nil))
       ((null xx)
	(cond ((eq state :compl)
	       `(cond ,@totalcol))
	      (t (error "if*: illegal form ~s" args))))
       (cond ((and (symbolp (car xx))
		   (member (symbol-name (car xx))
			   if*-keyword-list
			   :test #'string-equal))
	      (setq lookat (symbol-name (car xx)))))

       (cond ((eq state :init)
	      (cond (lookat (cond ((string-equal lookat "thenret")
				   (setq col nil
					 state :then))
				  (t (error
				      "if*: bad keyword ~a" lookat))))
		    (t (setq state :col
			     col nil)
		       (push (car xx) col))))
	     ((eq state :col)
	      (cond (lookat
		     (cond ((string-equal lookat "else")
			    (cond (elseseen
				   (error
				    "if*: multiples elses")))
			    (setq elseseen t)
			    (setq state :init)
			    (push `(t ,@col) totalcol))
			   ((string-equal lookat "then")
			    (setq state :then))
			   (t (error "if*: bad keyword ~s"
					      lookat))))
		    (t (push (car xx) col))))
	     ((eq state :then)
	      (cond (lookat
		     (error
		      "if*: keyword ~s at the wrong place " (car xx)))
		    (t (setq state :compl)
		       (push `(,(car xx) ,@col) totalcol))))
	     ((eq state :compl)
	      (cond ((not (string-equal lookat "elseif"))
		     (error "if*: missing elseif clause ")))
	      (setq state :init)))))

(defvar *initial-terminal-io* *terminal-io*)
(defvar *cl-default-special-bindings* nil)

(defun filesys-size (stream)
  (file-length stream))

(defun filesys-write-date (stream)
  (file-write-date stream))

(defun acl-compat.system:make-temp-file-name (name)
  (cl-fad:with-open-temporary-file (s)
    (pathname s)))

(defun frob-regexp (regexp)
  "This converts from ACL regexps to Perl regexps.  The escape
  status of (, ) and | is toggled."
  (let ((escapees '(#\) #\( #\| )))
    (with-input-from-string (in regexp)
      (with-output-to-string (out)
        (loop for c = (read-char in nil nil nil)
             while c
             do (cond ((and (char= c #\\)
                            (member (peek-char nil in nil nil nil) escapees))
                       (setf c (read-char in)))
                      ((member c escapees)
                       (princ #\\ out)))
             (princ c out))))))

;; TODO: a compiler macro for constant string regexps would be nice,
;; so that the create-scanner call at runtime can be evaded.
(defun match-regexp (string-or-regexp string-to-match
                     &key newlines-special case-fold (return :string)
                     (start 0) end shortest)
  "Note: if a regexp compiled with compile-regexp is passed, the
  options newlines-special and case-fold shouldn't be used, since
  the underlying engine uses them when generating the scanner,
  not when executing it."
  (when shortest (error "match-regexp: shortest option not supported yet."))
  (unless end (setf end (length string-to-match)))
  (let ((scanner (cl-ppcre:create-scanner (frob-regexp string-or-regexp)
                                          :case-insensitive-mode case-fold
                                          :single-line-mode newlines-special)))
      (ecase return
        (:string                        ; return t, list of strings
         (multiple-value-bind (match regs)
             (cl-ppcre:scan-to-strings scanner string-to-match
                                       :start start :end end)
           (if match
               (apply #'values t match (coerce regs 'list))
               nil)))
        (:index                         ; return (cons start end)
         (multiple-value-bind (start end reg-starts reg-ends)
             (cl-ppcre:scan scanner string-to-match :start start :end end)
           (and start (apply #'values t (cons start end)
                             (map 'list #'cons reg-starts reg-ends)))))
        ((nil)                          ; return t
         (not (not (cl-ppcre:scan scanner string-to-match
                                  :start start :end end)))))))


;; Caution Incompatible APIs!  cl-ppcre has options case-insensitive,
;; single-line for create-scanner, ACL has it in match-regexp.
(defun compile-regexp (regexp)
  "Note: Take care when using scanners compiled with this option
  to not depend on options case-fold and newlines-special in match-regexp."
  (cl-ppcre:create-scanner (frob-regexp regexp)))

(defvar *current-case-mode* :case-insensitive-upper)

(defun intern* (s len package)
  (intern (subseq s 0 len) package))

(defmacro errorset (form &optional (announce nil) (catch-breaks nil))
  "This macro is incomplete.  It was hacked to get AllegroServe
running, but the announce and catch-breaks arguments are ignored.  See
documentation at
http://franz.com/support/documentation/6.1/doc/pages/operators/excl/errorset.htm
An implementation of the catch-breaks argument will necessarily be
implementation-dependent, since Ansi does not allow any
program-controlled interception of a break."
  (declare (ignore announce catch-breaks))
  `(let* ((ok nil)
          (results (ignore-errors
                     (prog1 (multiple-value-list ,form)
                       (setq ok t)))))
     (if ok
         (apply #'values t results)
         nil)))

(defmacro fast (&body forms)
  `(locally (declare (optimize (speed 3) (safety 0) (debug 0)))
	    ,@forms))

#-cmu
(defun write-vector (sequence stream &key (start 0) end endian-swap)
  (declare (ignore endian-swap))
  (check-type sequence (or string (array (unsigned-byte 8) 1)
                           (array (signed-byte 8) 1)))
  (write-sequence sequence stream :start start :end end))

;;; MD5 support for client.cl, using the Ironclad library (available via
;;; QuickLisp)

(defun md5-init ()
  (ironclad:make-digest :md5))

(defun md5-update (context data &rest args &key start end external-format)
  (apply #'ironclad:update-digest context data args))

(defun md5-final (context &key (return :integer))
  ;; return is one of :integer, :usb8, :hex
  (let ((result (ironclad:produce-digest context)))
    (ecase return
      (:usb8 result)
      (:integer (ironclad:octets-to-integer result))
      (:hex (ironclad:byte-array-to-hex-string result)))))

