r5347: *** empty log message ***
[puri.git] / src.lisp
1 ;; -*- mode: common-lisp; package: puri -*-
2 ;; Support for URIs in Allegro.
3 ;; For general URI information see RFC2396.
4 ;;
5 ;; copyright (c) 1999-2001 Franz Inc, Berkeley, CA - All rights reserved.
6 ;; copyright (c) 2003 Kevin Rosenberg (porting changes)
7 ;;
8 ;; The software, data and information contained herein are proprietary
9 ;; to, and comprise valuable trade secrets of, Franz, Inc.  They are
10 ;; given in confidence by Franz, Inc. pursuant to a written license
11 ;; agreement, and may be stored and used only in accordance with the terms
12 ;; of such license.
13 ;;
14 ;; Restricted Rights Legend
15 ;; ------------------------
16 ;; Use, duplication, and disclosure of the software, data and information
17 ;; contained herein by any agency, department or entity of the U.S.
18 ;; Government are subject to restrictions of Restricted Rights for
19 ;; Commercial Software developed at private expense as specified in
20 ;; DOD FAR Supplement 52.227-7013 (c) (1) (ii), as applicable.
21 ;;
22 ;; Original version from ACL 6.1:
23 ;; uri.cl,v 2.3.6.4.2.1 2001/08/09 17:42:39 layer
24 ;;
25 ;; $Id: src.lisp,v 1.9 2003/07/20 18:51:48 kevin Exp $
26
27 (defpackage #:puri
28   (:use #:cl)
29   (:export
30    #:uri                                ; the type and a function
31    #:uri-p
32    #:copy-uri
33
34    #:uri-scheme                         ; and slots
35    #:uri-host #:uri-port
36    #:uri-path
37    #:uri-query
38    #:uri-fragment
39    #:uri-plist
40    #:uri-authority                      ; pseudo-slot accessor
41
42    #:urn                                ; class
43    #:urn-nid                            ; pseudo-slot accessor
44    #:urn-nss                            ; pseudo-slot accessor
45    
46    #:*strict-parse*
47    #:parse-uri
48    #:merge-uris
49    #:enough-uri
50    #:uri-parsed-path
51    #:render-uri
52
53    #:make-uri-space                     ; interning...
54    #:uri-space
55    #:uri=
56    #:intern-uri
57    #:unintern-uri
58    #:do-all-uris
59
60    #:uri-parse-error ;; Added by KMR
61    ))
62
63 (in-package #:puri)
64
65 (eval-when (:compile-toplevel) (declaim (optimize (speed 3))))
66
67
68 #-allegro
69 (defun parse-body (forms &optional env)
70   "Parses a body, returns (VALUES docstring declarations forms)"
71   (declare (ignore env))
72   ;; fixme -- need to add parsing of multiple declarations
73   (let (docstring declarations)
74     (when (stringp (car forms))
75       (setq docstring (car forms))
76       (setq forms (cdr forms)))
77     (when (and (listp (car forms))
78                (symbolp (caar forms))
79                (string-equal (symbol-name '#:declare)
80                              (symbol-name (caar forms))))
81       (setq declarations (car forms))
82       (setq forms (cdr forms)))
83     (values docstring declarations forms)))
84
85   
86 (defun shrink-vector (str size)
87   #+allegro
88   (excl::.primcall 'sys::shrink-svector str size)
89   #+sbcl
90   (sb-kernel:shrink-vector str size)
91   #+cmu
92   (lisp::shrink-vector str size)
93   #+lispworks
94   (system::shrink-vector$vector str size)
95   #+(or allegro cmu sbcl lispworks)
96   str
97   #-(or allegro cmu sbcl lispworks)
98   (subseq str 0 size))
99
100
101 ;; KMR: Added new condition to handle cross-implementation variances
102 ;; in the parse-error condition many implementations define
103
104 (define-condition uri-parse-error (parse-error)
105   ((fmt-control :initarg :fmt-control :accessor fmt-control)
106    (fmt-arguments :initarg :fmt-arguments :accessor fmt-arguments ))
107   (:report (lambda (c stream)
108              (format stream "Parse error:")
109              (apply #'format stream (fmt-control c) (fmt-arguments c)))))
110
111 (defun .parse-error (fmt &rest args)
112   (error 'uri-parse-error :fmt-control fmt :fmt-arguments args))
113
114 #-allegro
115 (defun internal-reader-error (stream fmt &rest args)
116   (apply #'format stream fmt args))
117
118 #-allegro (defvar *current-case-mode* :case-insensitive-upper)
119 #+allegro (eval-when (:compile-toplevel :load-toplevel :execute)
120             (import '(excl:*current-case-mode*
121                       excl:delimited-string-to-list
122                       excl::parse-body
123                       excl::internal-reader-error
124                       excl:if*)))
125
126 #-allegro
127 (defun position-char (char string start max)
128   (declare (optimize (speed 3) (safety 0) (space 0))
129            (fixnum start max) (simple-string string))
130   (do* ((i start (1+ i)))
131        ((= i max) nil)
132     (declare (fixnum i))
133     (when (char= char (schar string i)) (return i))))
134
135 #-allegro 
136 (defun delimited-string-to-list (string &optional (separator #\space) 
137                                  skip-terminal)
138   (declare (optimize (speed 3) (safety 0) (space 0)
139                      (compilation-speed 0))
140            (type string string)
141            (type character separator))
142   (do* ((len (length string))
143         (output '())
144         (pos 0)
145         (end (position-char separator string pos len)
146              (position-char separator string pos len)))
147        ((null end)
148         (if (< pos len)
149             (push (subseq string pos) output)
150             (when (or (not skip-terminal) (zerop len))
151               (push "" output)))
152         (nreverse output))
153     (declare (type fixnum pos len)
154              (type (or null fixnum) end))
155     (push (subseq string pos end) output)
156     (setq pos (1+ end))))
157
158 #-allegro
159 (eval-when (:compile-toplevel :load-toplevel :execute)
160   (defvar if*-keyword-list '("then" "thenret" "else" "elseif"))
161
162   (defmacro if* (&rest args)
163     (do ((xx (reverse args) (cdr xx))
164          (state :init)
165          (elseseen nil)
166          (totalcol nil)
167         (lookat nil nil)
168          (col nil))
169         ((null xx)
170          (cond ((eq state :compl)
171                 `(cond ,@totalcol))
172                (t (error "if*: illegal form ~s" args))))
173       (cond ((and (symbolp (car xx))
174                   (member (symbol-name (car xx))
175                           if*-keyword-list
176                           :test #'string-equal))
177              (setq lookat (symbol-name (car xx)))))
178
179        (cond ((eq state :init)
180               (cond (lookat (cond ((string-equal lookat "thenret")
181                                    (setq col nil
182                                          state :then))
183                                   (t (error
184                                       "if*: bad keyword ~a" lookat))))
185                     (t (setq state :col
186                              col nil)
187                        (push (car xx) col))))
188              ((eq state :col)
189               (cond (lookat
190                      (cond ((string-equal lookat "else")
191                             (cond (elseseen
192                                    (error
193                                     "if*: multiples elses")))
194                             (setq elseseen t)
195                             (setq state :init)
196                             (push `(t ,@col) totalcol))
197                            ((string-equal lookat "then")
198                             (setq state :then))
199                            (t (error "if*: bad keyword ~s"
200                                               lookat))))
201                     (t (push (car xx) col))))
202              ((eq state :then)
203               (cond (lookat
204                      (error
205                       "if*: keyword ~s at the wrong place " (car xx)))
206                     (t (setq state :compl)
207                        (push `(,(car xx) ,@col) totalcol))))
208              ((eq state :compl)
209               (cond ((not (string-equal lookat "elseif"))
210                      (error "if*: missing elseif clause ")))
211               (setq state :init))))))
212
213
214 (defclass uri ()
215   (
216 ;;;; external:
217    (scheme :initarg :scheme :initform nil :accessor uri-scheme)
218    (host :initarg :host :initform nil :accessor uri-host)
219    (port :initarg :port :initform nil :accessor uri-port)
220    (path :initarg :path :initform nil :accessor uri-path)
221    (query :initarg :query :initform nil :accessor uri-query)
222    (fragment :initarg :fragment :initform nil :accessor uri-fragment)
223    (plist :initarg :plist :initform nil :accessor uri-plist)
224
225 ;;;; internal:
226    (escaped
227     ;; used to prevent unnessary work, looking for chars to escape and
228     ;; unescape.
229     :initarg :escaped :initform nil :accessor uri-escaped)
230    (string
231     ;; the cached printable representation of the URI.  It *might* be
232     ;; different than the original string, though, because the user might
233     ;; have escaped non-reserved chars--they won't be escaped when the URI
234     ;; is printed.
235     :initarg :string :initform nil :accessor uri-string)
236    (parsed-path
237     ;; the cached parsed representation of the URI path.
238     :initarg :parsed-path
239     :initform nil
240     :accessor .uri-parsed-path)
241    (hashcode
242     ;; cached sxhash, so we don't have to compute it more than once.
243     :initarg :hashcode :initform nil :accessor uri-hashcode)))
244
245 (defclass urn (uri)
246   ((nid :initarg :nid :initform nil :accessor urn-nid)
247    (nss :initarg :nss :initform nil :accessor urn-nss)))
248
249 (eval-when (:compile-toplevel :execute)
250   (defmacro clear-caching-on-slot-change (name)
251     `(defmethod (setf ,name) :around (new-value (self uri))
252        (declare (ignore new-value))
253        (prog1 (call-next-method)
254          (setf (uri-string self) nil)
255          ,@(when (eq name 'uri-path) `((setf (.uri-parsed-path self) nil)))
256          (setf (uri-hashcode self) nil))))
257   )
258
259 (clear-caching-on-slot-change uri-scheme)
260 (clear-caching-on-slot-change uri-host)
261 (clear-caching-on-slot-change uri-port)
262 (clear-caching-on-slot-change uri-path)
263 (clear-caching-on-slot-change uri-query)
264 (clear-caching-on-slot-change uri-fragment)
265
266
267 (defmethod make-load-form ((self uri) &optional env)
268   (declare (ignore env))
269   `(make-instance ',(class-name (class-of self))
270      :scheme ,(uri-scheme self)
271      :host ,(uri-host self)
272      :port ,(uri-port self)
273      :path ',(uri-path self)
274      :query ,(uri-query self)
275      :fragment ,(uri-fragment self)
276      :plist ',(uri-plist self)
277      :string ,(uri-string self)
278      :parsed-path ',(.uri-parsed-path self)))
279
280 (defmethod uri-p ((thing uri)) t)
281 (defmethod uri-p ((thing t)) nil)
282
283 (defun copy-uri (uri
284                  &key place
285                       (scheme (when uri (uri-scheme uri)))
286                       (host (when uri (uri-host uri)))
287                       (port (when uri (uri-port uri)))
288                       (path (when uri (uri-path uri)))
289                       (parsed-path
290                        (when uri (copy-list (.uri-parsed-path uri))))
291                       (query (when uri (uri-query uri)))
292                       (fragment (when uri (uri-fragment uri)))
293                       (plist (when uri (copy-list (uri-plist uri))))
294                       (class (when uri (class-of uri)))
295                  &aux (escaped (when uri (uri-escaped uri))))
296   (if* place
297      then (setf (uri-scheme place) scheme)
298           (setf (uri-host place) host)
299           (setf (uri-port place) port)
300           (setf (uri-path place) path)
301           (setf (.uri-parsed-path place) parsed-path)
302           (setf (uri-query place) query)
303           (setf (uri-fragment place) fragment)
304           (setf (uri-plist place) plist)
305           (setf (uri-escaped place) escaped)
306           (setf (uri-string place) nil)
307           (setf (uri-hashcode place) nil)
308           place
309    elseif (eq 'uri class)
310      then ;; allow the compiler to optimize the call to make-instance:
311           (make-instance 'uri
312             :scheme scheme :host host :port port :path path
313             :parsed-path parsed-path
314             :query query :fragment fragment :plist plist
315             :escaped escaped :string nil :hashcode nil)
316      else (make-instance class
317             :scheme scheme :host host :port port :path path
318             :parsed-path parsed-path
319             :query query :fragment fragment :plist plist
320             :escaped escaped :string nil :hashcode nil)))
321
322 (defmethod uri-parsed-path ((uri uri))
323   (when (uri-path uri)
324     (when (null (.uri-parsed-path uri))
325       (setf (.uri-parsed-path uri)
326         (parse-path (uri-path uri) (uri-escaped uri))))
327     (.uri-parsed-path uri)))
328
329 (defmethod (setf uri-parsed-path) (path-list (uri uri))
330   (assert (and (consp path-list)
331                (or (member (car path-list) '(:absolute :relative)
332                            :test #'eq))))
333   (setf (uri-path uri) (render-parsed-path path-list t))
334   (setf (.uri-parsed-path uri) path-list)
335   path-list)
336
337 (defun uri-authority (uri)
338   (when (uri-host uri)
339     (let ((*print-pretty* nil))
340       (format nil "~a~@[:~a~]" (uri-host uri) (uri-port uri)))))
341
342 (defun uri-nid (uri)
343   (if* (equalp "urn" (uri-scheme uri))
344      then (uri-host uri)
345      else (error "URI is not a URN: ~s." uri)))
346
347 (defun uri-nss (uri)
348   (if* (equalp "urn" (uri-scheme uri))
349      then (uri-path uri)
350      else (error "URI is not a URN: ~s." uri)))
351
352 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
353 ;; Parsing
354
355 (defparameter *excluded-characters*
356     '(;; `delims' (except #\%, because it's handled specially):
357       #\< #\> #\" #\space #\#
358       ;; `unwise':
359       #\{ #\} #\| #\\ #\^ #\[ #\] #\`))
360
361 (defun reserved-char-vector (chars &key except)
362   (do* ((a (make-array 127 :element-type 'bit :initial-element 0))
363         (chars chars (cdr chars))
364         (c (car chars) (car chars)))
365       ((null chars) a)
366     (if* (and except (member c except :test #'char=))
367        thenret
368        else (setf (sbit a (char-int c)) 1))))
369
370 (defparameter *reserved-characters*
371     (reserved-char-vector
372      (append *excluded-characters*
373              '(#\; #\/ #\? #\: #\@ #\& #\= #\+ #\$ #\, #\%))))
374 (defparameter *reserved-authority-characters*
375     (reserved-char-vector
376      (append *excluded-characters* '(#\; #\/ #\? #\: #\@))))
377 (defparameter *reserved-path-characters*
378     (reserved-char-vector
379      (append *excluded-characters*
380              '(#\;
381 ;;;;The rfc says this should be here, but it doesn't make sense.
382                ;; #\=
383                #\/ #\?))))
384 (defparameter *reserved-path-characters2*
385     ;; These are the same characters that are in
386     ;; *reserved-path-characters*, minus #\/.  Why?  Because the parsed
387     ;; representation of the path can contain the %2f converted into a /.
388     ;; That's the whole point of having the parsed representation, so that
389     ;; lisp programs can deal with the path element data in the most
390     ;; convenient form.
391     (reserved-char-vector
392      (append *excluded-characters*
393              '(#\;
394 ;;;;The rfc says this should be here, but it doesn't make sense.
395                ;; #\=
396                #\?))))
397 (defparameter *reserved-fragment-characters*
398     (reserved-char-vector (remove #\# *excluded-characters*)))
399
400 (eval-when (:compile-toplevel :execute)
401 (defun gen-char-range-list (start end)
402   (do* ((res '())
403         (endcode (1+ (char-int end)))
404         (chcode (char-int start)
405                 (1+ chcode))
406         (hyphen nil))
407       ((= chcode endcode)
408        ;; - has to be first, otherwise it signifies a range!
409        (if* hyphen
410           then (setq res (nreverse res))
411                (push #\- res)
412                res
413           else (nreverse res)))
414     (if* (= #.(char-int #\-) chcode)
415        then (setq hyphen t)
416        else (push (code-char chcode) res))))
417 )
418
419 (defparameter *valid-nid-characters*
420     (reserved-char-vector
421      '#.(nconc (gen-char-range-list #\a #\z)
422                (gen-char-range-list #\A #\Z)
423                (gen-char-range-list #\0 #\9)
424                '(#\- #\. #\+))))
425 (defparameter *reserved-nss-characters*
426     (reserved-char-vector
427      (append *excluded-characters* '(#\& #\~ #\/ #\?))))
428
429 (defparameter *illegal-characters*
430     (reserved-char-vector (remove #\# *excluded-characters*)))
431 (defparameter *strict-illegal-query-characters*
432     (reserved-char-vector (append '(#\?) (remove #\# *excluded-characters*))))
433 (defparameter *illegal-query-characters*
434     (reserved-char-vector
435      *excluded-characters* :except '(#\^ #\| #\#)))
436
437
438 (defun parse-uri (thing &key (class 'uri) &aux escape)
439   (when (uri-p thing) (return-from parse-uri thing))
440   
441   (setq escape (escape-p thing))
442   (multiple-value-bind (scheme host port path query fragment)
443       (parse-uri-string thing)
444     (when scheme
445       (setq scheme
446         (intern (funcall
447                  (case *current-case-mode*
448                    ((:case-insensitive-upper :case-sensitive-upper)
449                     #'string-upcase)
450                    ((:case-insensitive-lower :case-sensitive-lower)
451                     #'string-downcase))
452                  (decode-escaped-encoding scheme escape))
453                 (find-package :keyword))))
454     
455     (when (and scheme (eq :urn scheme))
456       (return-from parse-uri
457         (make-instance 'urn :scheme scheme :nid host :nss path)))
458     
459     (when host (setq host (decode-escaped-encoding host escape)))
460     (when port
461       (setq port (read-from-string port))
462       (when (not (numberp port)) (error "port is not a number: ~s." port))
463       (when (not (plusp port))
464         (error "port is not a positive integer: ~d." port))
465       (when (eql port (case scheme
466                       (:http 80)
467                       (:https 443)
468                       (:ftp 21)
469                       (:telnet 23)))
470         (setq port nil)))
471     (when (or (string= "" path)
472               (and ;; we canonicalize away a reference to just /:
473                scheme
474                (member scheme '(:http :https :ftp) :test #'eq)
475                (string= "/" path)))
476       (setq path nil))
477     (when path
478       (setq path
479         (decode-escaped-encoding path escape *reserved-path-characters*)))
480     (when query (setq query (decode-escaped-encoding query escape)))
481     (when fragment
482       (setq fragment
483         (decode-escaped-encoding fragment escape
484                                  *reserved-fragment-characters*)))
485     (if* (eq 'uri class)
486        then ;; allow the compiler to optimize the make-instance call:
487             (make-instance 'uri
488               :scheme scheme
489               :host host
490               :port port
491               :path path
492               :query query
493               :fragment fragment
494               :escaped escape)
495        else ;; do it the slow way:
496             (make-instance class
497               :scheme scheme
498               :host host
499               :port port
500               :path path
501               :query query
502               :fragment fragment
503               :escaped escape))))
504
505 (defmethod uri ((thing uri))
506   thing)
507
508 (defmethod uri ((thing string))
509   (parse-uri thing))
510
511 (defmethod uri ((thing t))
512   (error "Cannot coerce ~s to a uri." thing))
513
514 (defvar *strict-parse* t)
515
516 (defun parse-uri-string (string &aux (illegal-chars *illegal-characters*))
517   (declare (optimize (speed 3)))
518   ;; Speed is important, so use a specialized state machine instead of
519   ;; regular expressions for parsing the URI string. The regexp we are
520   ;; simulating:
521   ;;  ^(([^:/?#]+):)?
522   ;;   (//([^/?#]*))?
523   ;;   ([^?#]*)
524   ;;   (\?([^#]*))?
525   ;;   (#(.*))?
526   (let* ((state 0)
527          (start 0)
528          (end (length string))
529          (tokval nil)
530          (scheme nil)
531          (host nil)
532          (port nil)
533          (path-components '())
534          (query nil)
535          (fragment nil)
536          ;; namespace identifier, for urn parsing only:
537          (nid nil))
538     (declare (fixnum state start end))
539     (flet ((read-token (kind &optional legal-chars)
540              (setq tokval nil)
541              (if* (>= start end)
542                 then :end
543                 else (let ((sindex start)
544                            (res nil)
545                            c)
546                        (declare (fixnum sindex))
547                        (setq res
548                          (loop
549                            (when (>= start end) (return nil))
550                            (setq c (schar string start))
551                            (let ((ci (char-int c)))
552                              (if* legal-chars
553                                 then (if* (and (eq :colon kind) (eq c #\:))
554                                         then (return :colon)
555                                       elseif (= 0 (sbit legal-chars ci))
556                                         then (.parse-error
557                                               "~
558 URI ~s contains illegal character ~s at position ~d."
559                                               string c start))
560                               elseif (and (< ci 128)
561                                           *strict-parse*
562                                           (= 1 (sbit illegal-chars ci)))
563                                 then (.parse-error "~
564 URI ~s contains illegal character ~s at position ~d."
565                                                          string c start)))
566                            (case kind
567                              (:path (case c
568                                       (#\? (return :question))
569                                       (#\# (return :hash))))
570                              (:query (case c (#\# (return :hash))))
571                              (:rest)
572                              (t (case c
573                                   (#\: (return :colon))
574                                   (#\? (return :question))
575                                   (#\# (return :hash))
576                                   (#\/ (return :slash)))))
577                            (incf start)))
578                        (if* (> start sindex)
579                           then ;; we found some chars
580                                ;; before we stopped the parse
581                                (setq tokval (subseq string sindex start))
582                                :string
583                           else ;; immediately stopped at a special char
584                                (incf start)
585                                res))))
586            (failure (&optional why)
587              (.parse-error "illegal URI: ~s [~d]~@[: ~a~]"
588                                  string state why))
589            (impossible ()
590              (.parse-error "impossible state: ~d [~s]" state string)))
591       (loop
592         (case state
593           (0 ;; starting to parse
594            (ecase (read-token t)
595              (:colon (failure))
596              (:question (setq state 7))
597              (:hash (setq state 8))
598              (:slash (setq state 3))
599              (:string (setq state 1))
600              (:end (setq state 9))))
601           (1 ;; seen <token><special char>
602            (let ((token tokval))
603              (ecase (read-token t)
604                (:colon (setq scheme token)
605                        (if* (equalp "urn" scheme)
606                           then (setq state 15)
607                           else (setq state 2)))
608                (:question (push token path-components)
609                           (setq state 7))
610                (:hash (push token path-components)
611                       (setq state 8))
612                (:slash (push token path-components)
613                        (push "/" path-components)
614                        (setq state 6))
615                (:string (failure))
616                (:end (push token path-components)
617                      (setq state 9)))))
618           (2 ;; seen <scheme>:
619            (ecase (read-token t)
620              (:colon (failure))
621              (:question (setq state 7))
622              (:hash (setq state 8))
623              (:slash (setq state 3))
624              (:string (setq state 10))
625              (:end (setq state 9))))
626           (10 ;; seen <scheme>:<token>
627            (let ((token tokval))
628              (ecase (read-token t)
629                (:colon (failure))
630                (:question (push token path-components)
631                           (setq state 7))
632                (:hash (push token path-components)
633                       (setq state 8))
634                (:slash (push token path-components)
635                        (setq state 6))
636                (:string (failure))
637                (:end (push token path-components)
638                      (setq state 9)))))
639           (3 ;; seen / or <scheme>:/
640            (ecase (read-token t)
641              (:colon (failure))
642              (:question (push "/" path-components)
643                         (setq state 7))
644              (:hash (push "/" path-components)
645                     (setq state 8))
646              (:slash (setq state 4))
647              (:string (push "/" path-components)
648                       (push tokval path-components)
649                       (setq state 6))
650              (:end (push "/" path-components)
651                    (setq state 9))))
652           (4 ;; seen [<scheme>:]//
653            (ecase (read-token t)
654              (:colon (failure))
655              (:question (failure))
656              (:hash (failure))
657              (:slash (failure))
658              (:string (setq host tokval)
659                       (setq state 11))
660              (:end (failure))))
661           (11 ;; seen [<scheme>:]//<host>
662            (ecase (read-token t)
663              (:colon (setq state 5))
664              (:question (setq state 7))
665              (:hash (setq state 8))
666              (:slash (push "/" path-components)
667                      (setq state 6))
668              (:string (impossible))
669              (:end (setq state 9))))
670           (5 ;; seen [<scheme>:]//<host>:
671            (ecase (read-token t)
672              (:colon (failure))
673              (:question (failure))
674              (:hash (failure))
675              (:slash (push "/" path-components)
676                      (setq state 6))
677              (:string (setq port tokval)
678                       (setq state 12))
679              (:end (failure))))
680           (12 ;; seen [<scheme>:]//<host>:[<port>]
681            (ecase (read-token t)
682              (:colon (failure))
683              (:question (setq state 7))
684              (:hash (setq state 8))
685              (:slash (push "/" path-components)
686                      (setq state 6))
687              (:string (impossible))
688              (:end (setq state 9))))
689           (6 ;; seen /
690            (ecase (read-token :path)
691              (:question (setq state 7))
692              (:hash (setq state 8))
693              (:string (push tokval path-components)
694                       (setq state 13))
695              (:end (setq state 9))))
696           (13 ;; seen path
697            (ecase (read-token :path)
698              (:question (setq state 7))
699              (:hash (setq state 8))
700              (:string (impossible))
701              (:end (setq state 9))))
702           (7 ;; seen ?
703            (setq illegal-chars
704              (if* *strict-parse*
705                 then *strict-illegal-query-characters*
706                 else *illegal-query-characters*))
707            (ecase (prog1 (read-token :query)
708                     (setq illegal-chars *illegal-characters*))
709              (:hash (setq state 8))
710              (:string (setq query tokval)
711                       (setq state 14))
712              (:end (setq state 9))))
713           (14 ;; query
714            (ecase (read-token :query)
715              (:hash (setq state 8))
716              (:string (impossible))
717              (:end (setq state 9))))
718           (8 ;; seen #
719            (ecase (read-token :rest)
720              (:string (setq fragment tokval)
721                       (setq state 9))
722              (:end (setq state 9))))
723           (9 ;; done
724            (return
725              (values
726               scheme host port
727               (apply #'concatenate 'simple-string (nreverse path-components))
728               query fragment)))
729           ;; URN parsing:
730           (15 ;; seen urn:, read nid now
731            (case (read-token :colon *valid-nid-characters*)
732              (:string (setq nid tokval)
733                       (setq state 16))
734              (t (failure "missing namespace identifier"))))
735           (16 ;; seen urn:<nid>
736            (case (read-token t)
737              (:colon (setq state 17))
738              (t (failure "missing namespace specific string"))))
739           (17 ;; seen urn:<nid>:, rest is nss
740            (return (values scheme
741                            nid
742                            nil
743                            (progn
744                              (setq illegal-chars *reserved-nss-characters*)
745                              (read-token :rest)
746                              tokval))))
747           (t (.parse-error
748               "internal error in parse engine, wrong state: ~s." state)))))))
749
750 (defun escape-p (string)
751   (declare (optimize (speed 3)))
752   (do* ((i 0 (1+ i))
753         (max (the fixnum (length string))))
754       ((= i max) nil)
755     (declare (fixnum i max))
756     (when (char= #\% (schar string i))
757       (return t))))
758
759 (defun parse-path (path-string escape)
760   (do* ((xpath-list (delimited-string-to-list path-string #\/))
761         (path-list
762          (progn
763            (if* (string= "" (car xpath-list))
764               then (setf (car xpath-list) :absolute)
765               else (push :relative xpath-list))
766            xpath-list))
767         (pl (cdr path-list) (cdr pl))
768         segments)
769       ((null pl) path-list)
770     (if* (cdr (setq segments (delimited-string-to-list (car pl) #\;)))
771        then ;; there is a param
772 ;;;         (setf (car pl) segments)
773             (setf (car pl)
774               (mapcar #'(lambda (s)
775                           (decode-escaped-encoding
776                            s escape *reserved-path-characters2*))
777                segments))
778        else ;; no param
779 ;;;         (setf (car pl) (car segments))
780             (setf (car pl)
781               (decode-escaped-encoding
782                (car segments) escape *reserved-path-characters2*)))))
783
784 (defun decode-escaped-encoding (string escape
785                                 &optional (reserved-chars
786                                            *reserved-characters*))
787   ;; Return a string with the real characters.
788   (when (null escape) (return-from decode-escaped-encoding string))
789   (do* ((i 0 (1+ i))
790         (max (length string))
791         (new-string (copy-seq string))
792         (new-i 0 (1+ new-i))
793         ch ch2 chc chc2)
794       ((= i max)
795        (shrink-vector new-string new-i))
796     (if* (char= #\% (setq ch (schar string i)))
797        then (when (> (+ i 3) max)
798               (.parse-error
799                "Unsyntactic escaped encoding in ~s." string))
800             (setq ch (schar string (incf i)))
801             (setq ch2 (schar string (incf i)))
802             (when (not (and (setq chc (digit-char-p ch 16))
803                             (setq chc2 (digit-char-p ch2 16))))
804               (.parse-error
805                "Non-hexidecimal digits after %: %c%c." ch ch2))
806             (let ((ci (+ (* 16 chc) chc2)))
807               (if* (or (null reserved-chars)
808                        (= 0 (sbit reserved-chars ci)))
809                  then ;; ok as is
810                       (setf (schar new-string new-i)
811                         (code-char ci))
812                  else (setf (schar new-string new-i) #\%)
813                       (setf (schar new-string (incf new-i)) ch)
814                       (setf (schar new-string (incf new-i)) ch2)))
815        else (setf (schar new-string new-i) ch))))
816
817 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
818 ;;;; Printing
819
820 (defun render-uri (uri stream
821                    &aux (escape (uri-escaped uri))
822                         (*print-pretty* nil))
823   (when (null (uri-string uri))
824     (setf (uri-string uri)
825       (let ((scheme (uri-scheme uri))
826             (host (uri-host uri))
827             (port (uri-port uri))
828             (path (uri-path uri))
829             (query (uri-query uri))
830             (fragment (uri-fragment uri)))
831         (concatenate 'simple-string
832           (when scheme
833             (encode-escaped-encoding
834              (string-downcase ;; for upper case lisps
835               (symbol-name scheme))
836              *reserved-characters* escape))
837           (when scheme ":")
838           (when host "//")
839           (when host
840             (encode-escaped-encoding
841              host *reserved-authority-characters* escape))
842           (when port ":")
843           (when port
844             #-allegro (format nil "~D" port)
845             #+allegro (with-output-to-string (s)
846                         (excl::maybe-print-fast s port))
847             )
848           (when path
849             (encode-escaped-encoding path
850                                      nil
851                                      ;;*reserved-path-characters*
852                                      escape))
853           (when query "?")
854           (when query (encode-escaped-encoding query nil escape))
855           (when fragment "#")
856           (when fragment (encode-escaped-encoding fragment nil escape))))))
857   (if* stream
858      then (format stream "~a" (uri-string uri))
859      else (uri-string uri)))
860
861 (defun render-parsed-path (path-list escape)
862   (do* ((res '())
863         (first (car path-list))
864         (pl (cdr path-list) (cdr pl))
865         (pe (car pl) (car pl)))
866       ((null pl)
867        (when res (apply #'concatenate 'simple-string (nreverse res))))
868     (when (or (null first)
869               (prog1 (eq :absolute first)
870                 (setq first nil)))
871       (push "/" res))
872     (if* (atom pe)
873        then (push
874              (encode-escaped-encoding pe *reserved-path-characters* escape)
875              res)
876        else ;; contains params
877             (push (encode-escaped-encoding
878                    (car pe) *reserved-path-characters* escape)
879                   res)
880             (dolist (item (cdr pe))
881               (push ";" res)
882               (push (encode-escaped-encoding
883                      item *reserved-path-characters* escape)
884                     res)))))
885
886 (defun render-urn (urn stream
887                    &aux (*print-pretty* nil))
888   (when (null (uri-string urn))
889     (setf (uri-string urn)
890       (let ((nid (urn-nid urn))
891             (nss (urn-nss urn)))
892         (concatenate 'simple-string "urn:" nid ":" nss))))
893   (if* stream
894      then (format stream "~a" (uri-string urn))
895      else (uri-string urn)))
896
897 (defparameter *escaped-encoding*
898     (vector #\0 #\1 #\2 #\3 #\4 #\5 #\6 #\7 #\8 #\9 #\a #\b #\c #\d #\e #\f))
899
900 (defun encode-escaped-encoding (string reserved-chars escape)
901   (when (null escape) (return-from encode-escaped-encoding string))
902   ;; Make a string as big as it possibly needs to be (3 times the original
903   ;; size), and truncate it at the end.
904   (do* ((max (length string))
905         (new-max (* 3 max)) ;; worst case new size
906         (new-string (make-string new-max))
907         (i 0 (1+ i))
908         (new-i -1)
909         c ci)
910       ((= i max)
911        (shrink-vector new-string (incf new-i)))
912     (setq ci (char-int (setq c (schar string i))))
913     (if* (or (null reserved-chars)
914              (> ci 127)
915              (= 0 (sbit reserved-chars ci)))
916        then ;; ok as is
917             (incf new-i)
918             (setf (schar new-string new-i) c)
919        else ;; need to escape it
920             (multiple-value-bind (q r) (truncate ci 16)
921               (setf (schar new-string (incf new-i)) #\%)
922               (setf (schar new-string (incf new-i)) (elt *escaped-encoding* q))
923               (setf (schar new-string (incf new-i))
924                 (elt *escaped-encoding* r))))))
925
926 (defmethod print-object ((uri uri) stream)
927   (if* *print-escape*
928      then (format stream "#<~a ~a>" 'uri (render-uri uri nil))
929      else (render-uri uri stream)))
930
931 (defmethod print-object ((urn urn) stream)
932   (if* *print-escape*
933      then (format stream "#<~a ~a>" 'uri (render-urn urn nil))
934      else (render-urn urn stream)))
935
936 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
937 ;; merging and unmerging
938
939 (defmethod merge-uris ((uri string) (base string) &optional place)
940   (merge-uris (parse-uri uri) (parse-uri base) place))
941
942 (defmethod merge-uris ((uri uri) (base string) &optional place)
943   (merge-uris uri (parse-uri base) place))
944
945 (defmethod merge-uris ((uri string) (base uri) &optional place)
946   (merge-uris (parse-uri uri) base place))
947
948 (defmethod merge-uris ((uri uri) (base uri) &optional place)
949   ;; The following is from
950   ;; http://info.internet.isi.edu/in-notes/rfc/files/rfc2396.txt
951   ;; and is algorithm we use to merge URIs.
952   ;;
953   ;; For more information, see section 5.2 of the RFC.
954   ;;
955   (tagbody
956 ;;;; step 2
957     (when (and (null (uri-parsed-path uri))
958                (null (uri-scheme uri))
959                (null (uri-host uri))
960                (null (uri-port uri))
961                (null (uri-query uri)))
962       (return-from merge-uris
963         (let ((new (copy-uri base :place place)))
964           (when (uri-query uri)
965             (setf (uri-query new) (uri-query uri)))
966           (when (uri-fragment uri)
967             (setf (uri-fragment new) (uri-fragment uri)))
968           new)))
969
970     (setq uri (copy-uri uri :place place))
971
972 ;;;; step 3
973     (when (uri-scheme uri)
974       (return-from merge-uris uri))
975     (setf (uri-scheme uri) (uri-scheme base))
976   
977 ;;;; step 4
978     (when (uri-host uri) (go :done))
979     (setf (uri-host uri) (uri-host base))
980     (setf (uri-port uri) (uri-port base))
981     
982 ;;;; step 5
983     (let ((p (uri-parsed-path uri)))
984       (when (and p (eq :absolute (car p)))
985         (when (equal '(:absolute "") p)
986           ;; Canonicalize the way parsing does:
987           (setf (uri-path uri) nil))
988         (go :done)))
989     
990 ;;;; step 6
991     (let* ((base-path
992             (or (uri-parsed-path base)
993                 ;; needed because we canonicalize away a path of just `/':
994                 '(:absolute "")))
995            (path (uri-parsed-path uri))
996            new-path-list)
997       (when (not (eq :absolute (car base-path)))
998         (error "Cannot merge ~a and ~a, since latter is not absolute."
999                uri base))
1000
1001       ;; steps 6a and 6b:
1002       (setq new-path-list
1003         (append (butlast base-path)
1004                 (if* path then (cdr path) else '(""))))
1005
1006       ;; steps 6c and 6d:
1007       (let ((last (last new-path-list)))
1008         (if* (atom (car last))
1009            then (when (string= "." (car last))
1010                   (setf (car last) ""))
1011            else (when (string= "." (caar last))
1012                   (setf (caar last) ""))))
1013       (setq new-path-list
1014         (delete "." new-path-list :test #'(lambda (a b)
1015                                             (if* (atom b)
1016                                                then (string= a b)
1017                                                else nil))))
1018
1019       ;; steps 6e and 6f:
1020       (let ((npl (cdr new-path-list))
1021             index tmp fix-tail)
1022         (setq fix-tail
1023           (string= ".." (let ((l (car (last npl))))
1024                           (if* (atom l)
1025                              then l
1026                              else (car l)))))
1027         (loop
1028           (setq index
1029             (position ".." npl
1030                       :test #'(lambda (a b)
1031                                 (string= a
1032                                          (if* (atom b)
1033                                             then b
1034                                             else (car b))))))
1035           (when (null index) (return))
1036           (when (= 0 index)
1037             ;; The RFC says, in 6g, "that the implementation may handle
1038             ;; this error by retaining these components in the resolved
1039             ;; path, by removing them from the resolved path, or by
1040             ;; avoiding traversal of the reference."  The examples in C.2
1041             ;; imply that we should do the first thing (retain them), so
1042             ;; that's what we'll do.
1043             (return))
1044           (if* (= 1 index)
1045              then (setq npl (cddr npl))
1046              else (setq tmp npl)
1047                   (dotimes (x (- index 2)) (setq tmp (cdr tmp)))
1048                   (setf (cdr tmp) (cdddr tmp))))
1049         (setf (cdr new-path-list) npl)
1050         (when fix-tail (setq new-path-list (nconc new-path-list '("")))))
1051
1052       ;; step 6g:
1053       ;; don't complain if new-path-list starts with `..'.  See comment
1054       ;; above about this step.
1055
1056       ;; step 6h:
1057       (when (or (equal '(:absolute "") new-path-list)
1058                 (equal '(:absolute) new-path-list))
1059         (setq new-path-list nil))
1060       (setf (uri-path uri)
1061         (render-parsed-path new-path-list
1062                             ;; don't know, so have to assume:
1063                             t)))
1064
1065 ;;;; step 7
1066    :done
1067     (return-from merge-uris uri)))
1068
1069 (defmethod enough-uri ((uri string) (base string) &optional place)
1070   (enough-uri (parse-uri uri) (parse-uri base) place))
1071
1072 (defmethod enough-uri ((uri uri) (base string) &optional place)
1073   (enough-uri uri (parse-uri base) place))
1074
1075 (defmethod enough-uri ((uri string) (base uri) &optional place)
1076   (enough-uri (parse-uri uri) base place))
1077
1078 (defmethod enough-uri ((uri uri) (base uri) &optional place)
1079   (let ((new-scheme nil)
1080         (new-host nil)
1081         (new-port nil)
1082         (new-parsed-path nil))
1083
1084     (when (or (and (uri-scheme uri)
1085                    (not (equalp (uri-scheme uri) (uri-scheme base))))
1086               (and (uri-host uri)
1087                    (not (equalp (uri-host uri) (uri-host base))))
1088               (not (equalp (uri-port uri) (uri-port base))))
1089       (return-from enough-uri uri))
1090
1091     (when (null (uri-host uri))
1092       (setq new-host (uri-host base)))
1093     (when (null (uri-port uri))
1094       (setq new-port (uri-port base)))
1095     
1096     (when (null (uri-scheme uri))
1097       (setq new-scheme (uri-scheme base)))
1098
1099     ;; Now, for the hard one, path.
1100     ;; We essentially do here what enough-namestring does.
1101     (do* ((base-path (uri-parsed-path base))
1102           (path (uri-parsed-path uri))
1103           (bp base-path (cdr bp))
1104           (p path (cdr p)))
1105         ((or (null bp) (null p))
1106          ;; If p is nil, that means we have something like
1107          ;; (enough-uri "/foo/bar" "/foo/bar/baz.htm"), so
1108          ;; new-parsed-path will be nil.
1109          (when (null bp)
1110            (setq new-parsed-path (copy-list p))
1111            (when (not (symbolp (car new-parsed-path)))
1112              (push :relative new-parsed-path))))
1113       (if* (equal (car bp) (car p))
1114          thenret ;; skip it
1115          else (setq new-parsed-path (copy-list p))
1116               (when (not (symbolp (car new-parsed-path)))
1117                 (push :relative new-parsed-path))
1118               (return)))
1119
1120     (let ((new-path 
1121            (when new-parsed-path
1122              (render-parsed-path new-parsed-path
1123                                  ;; don't know, so have to assume:
1124                                  t)))
1125           (new-query (uri-query uri))
1126           (new-fragment (uri-fragment uri))
1127           (new-plist (copy-list (uri-plist uri))))
1128       (if* (and (null new-scheme)
1129                 (null new-host)
1130                 (null new-port)
1131                 (null new-path)
1132                 (null new-parsed-path)
1133                 (null new-query)
1134                 (null new-fragment))
1135          then ;; can't have a completely empty uri!
1136               (copy-uri nil
1137                         :class (class-of uri)
1138                         :place place
1139                         :path "/"
1140                         :plist new-plist)
1141          else (copy-uri nil
1142                         :class (class-of uri)
1143                         :place place
1144                         :scheme new-scheme
1145                         :host new-host
1146                         :port new-port
1147                         :path new-path
1148                         :parsed-path new-parsed-path
1149                         :query new-query
1150                         :fragment new-fragment
1151                         :plist new-plist)))))
1152
1153 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
1154 ;; support for interning URIs
1155
1156 (defun make-uri-space (&rest keys &key (size 777) &allow-other-keys)
1157   #+allegro
1158   (apply #'make-hash-table :size size
1159          :hash-function 'uri-hash
1160          :test 'uri= :values nil keys)
1161   #-allegro
1162   (apply #'make-hash-table :size size keys))
1163
1164 (defun gethash-uri (uri table)
1165   #+allegro (gethash uri table)
1166   #-allegro 
1167   (let* ((hash (uri-hash uri))
1168          (existing (gethash hash table)))
1169     (dolist (u existing)
1170       (when (uri= u uri)
1171         (return-from gethash-uri (values u t))))
1172     (values nil nil)))
1173
1174 (defun puthash-uri (uri table)
1175   #+allegro (excl:puthash-key uri table)
1176   #-allegro 
1177   (let ((existing (gethash (uri-hash uri) table)))
1178     (dolist (u existing)
1179       (when (uri= u uri)
1180         (return-from puthash-uri u)))
1181     (setf (gethash (uri-hash uri) table)
1182       (cons uri existing))
1183     uri))
1184
1185
1186 (defun uri-hash (uri)
1187   (if* (uri-hashcode uri)
1188      thenret
1189      else (setf (uri-hashcode uri)
1190                 (sxhash
1191                  #+allegro
1192                  (render-uri uri nil)
1193                  #-allegro
1194                  (string-downcase 
1195                   (render-uri uri nil))))))
1196
1197 (defvar *uris* (make-uri-space))
1198
1199 (defun uri-space () *uris*)
1200
1201 (defun (setf uri-space) (new-val)
1202   (setq *uris* new-val))
1203
1204 ;; bootstrapping (uri= changed from function to method):
1205 (when (fboundp 'uri=) (fmakunbound 'uri=))
1206
1207 (defgeneric uri= (uri1 uri2))
1208 (defmethod uri= ((uri1 uri) (uri2 uri))
1209   (when (not (eq (uri-scheme uri1) (uri-scheme uri2)))
1210     (return-from uri= nil))
1211   ;; RFC2396 says: a URL with an explicit ":port", where the port is
1212   ;; the default for the scheme, is the equivalent to one where the
1213   ;; port is elided.  Hmmmm.  This means that this function has to be
1214   ;; scheme dependent.  Grrrr.
1215   (let ((default-port (case (uri-scheme uri1)
1216                         (:http 80)
1217                         (:https 443)
1218                         (:ftp 21)
1219                         (:telnet 23))))
1220     (and (equalp (uri-host uri1) (uri-host uri2))
1221          (eql (or (uri-port uri1) default-port)
1222               (or (uri-port uri2) default-port))
1223          (string= (uri-path uri1) (uri-path uri2))
1224          (string= (uri-query uri1) (uri-query uri2))
1225          (string= (uri-fragment uri1) (uri-fragment uri2)))))
1226
1227 (defmethod uri= ((urn1 urn) (urn2 urn))
1228   (when (not (eq (uri-scheme urn1) (uri-scheme urn2)))
1229     (return-from uri= nil))
1230   (and (equalp (urn-nid urn1) (urn-nid urn2))
1231        (urn-nss-equal (urn-nss urn1) (urn-nss urn2))))
1232
1233 (defun urn-nss-equal (nss1 nss2 &aux len)
1234   ;; Return t iff the nss values are the same.
1235   ;; %2c and %2C are equivalent.
1236   (when (or (null nss1) (null nss2)
1237             (not (= (setq len (length nss1))
1238                     (length nss2))))
1239     (return-from urn-nss-equal nil))
1240   (do* ((i 0 (1+ i))
1241         (state :char)
1242         c1 c2)
1243       ((= i len) t)
1244     (setq c1 (schar nss1 i))
1245     (setq c2 (schar nss2 i))
1246     (ecase state
1247       (:char
1248        (if* (and (char= #\% c1) (char= #\% c2))
1249           then (setq state :percent+1)
1250         elseif (char/= c1 c2)
1251           then (return nil)))
1252       (:percent+1
1253        (when (char-not-equal c1 c2) (return nil))
1254        (setq state :percent+2))
1255       (:percent+2
1256        (when (char-not-equal c1 c2) (return nil))
1257        (setq state :char)))))
1258
1259 (defmethod intern-uri ((xuri uri) &optional (uri-space *uris*))
1260   (let ((uri (gethash-uri xuri uri-space)))
1261     (if* uri
1262        thenret
1263        else (puthash-uri xuri uri-space))))
1264
1265 (defmethod intern-uri ((uri string) &optional (uri-space *uris*))
1266   (intern-uri (parse-uri uri) uri-space))
1267
1268 (defun unintern-uri (uri &optional (uri-space *uris*))
1269   (if* (eq t uri)
1270      then (clrhash uri-space)
1271    elseif (uri-p uri)
1272      then (remhash uri uri-space)
1273      else (error "bad uri: ~s." uri)))
1274
1275 (defmacro do-all-uris ((var &optional uri-space result-form)
1276                        &rest forms
1277                        &environment env)
1278   "do-all-uris (var [[uri-space] result-form])
1279                     {declaration}* {tag | statement}*
1280 Executes the forms once for each uri with var bound to the current uri"
1281   (let ((f (gensym))
1282         (g-ignore (gensym))
1283         (g-uri-space (gensym))
1284         (body (third (parse-body forms env))))
1285     `(let ((,g-uri-space (or ,uri-space *uris*)))
1286        (prog nil
1287          (flet ((,f (,var &optional ,g-ignore)
1288                   (declare (ignore-if-unused ,var ,g-ignore))
1289                   (tagbody ,@body)))
1290            (maphash #',f ,g-uri-space))
1291          (return ,result-form)))))
1292
1293 (defun sharp-u (stream chr arg)
1294   (declare (ignore chr arg))
1295   (let ((arg (read stream nil nil t)))
1296     (if *read-suppress*
1297         nil
1298       (if* (stringp arg)
1299          then (parse-uri arg)
1300          else
1301
1302          (internal-reader-error
1303           stream
1304           "#u takes a string or list argument: ~s" arg)))))
1305
1306
1307 #+allegro
1308 excl::
1309 #+allegro
1310 (locally (declare (special std-lisp-readtable))
1311   (let ((*readtable* std-lisp-readtable))
1312     (set-dispatch-macro-character #\# #\u #'puri::sharp-u)))
1313 #-allegro
1314 (set-dispatch-macro-character #\# #\u #'puri::sharp-u)
1315
1316 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
1317
1318 (provide :uri)
1319
1320 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
1321 ;; timings
1322 ;; (don't run under emacs with M-x fi:common-lisp)
1323
1324 #+allegro
1325 (eval-when (:compile-toplevel :load-toplevel :execute)
1326   (import 'excl::gc))
1327
1328 #-allegro
1329 (defun gc (&rest options)
1330   (declare (ignore options))
1331   #+sbcl (sb-ext::gc)
1332   #+cmu (ext::gc)
1333   )
1334
1335 (defun time-uri-module ()
1336   (declare (optimize (speed 3) (safety 0) (debug 0)))
1337   (let ((uri "http://www.franz.com/a/b;x;y;z/c/foo?bar=baz&xxx#foo")
1338         (uri2 "http://www.franz.com/a/b;x;y;z/c/%2ffoo?bar=baz&xxx#foo"))
1339     (gc t) (gc :tenure) (gc :tenure) (gc :tenure)
1340     (format t "~&;;; starting timing testing 1...~%")
1341     (time (dotimes (i 100000) (parse-uri uri)))
1342     
1343     (gc t) (gc :tenure) (gc :tenure) (gc :tenure)
1344     (format t "~&;;; starting timing testing 2...~%")
1345     (let ((uri (parse-uri uri)))
1346       (time (dotimes (i 100000)
1347               ;; forces no caching of the printed representation:
1348               (setf (uri-string uri) nil)
1349               (format nil "~a" uri))))
1350     
1351     (gc t) (gc :tenure) (gc :tenure) (gc :tenure)
1352     (format t "~&;;; starting timing testing 3...~%")
1353     (time
1354      (progn
1355        (dotimes (i 100000) (parse-uri uri2))
1356        (let ((uri (parse-uri uri)))
1357          (dotimes (i 100000)
1358            ;; forces no caching of the printed representation:
1359            (setf (uri-string uri) nil)
1360            (format nil "~a" uri)))))))
1361
1362 ;;******** reference output (ultra, modified 5.0.1):
1363 ;;; starting timing testing 1...
1364 ; cpu time (non-gc) 13,710 msec user, 0 msec system
1365 ; cpu time (gc)     600 msec user, 10 msec system
1366 ; cpu time (total)  14,310 msec user, 10 msec system
1367 ; real time  14,465 msec
1368 ; space allocation:
1369 ;  1,804,261 cons cells, 7 symbols, 41,628,832 other bytes, 0 static bytes
1370 ;;; starting timing testing 2...
1371 ; cpu time (non-gc) 27,500 msec user, 0 msec system
1372 ; cpu time (gc)     280 msec user, 20 msec system
1373 ; cpu time (total)  27,780 msec user, 20 msec system
1374 ; real time  27,897 msec
1375 ; space allocation:
1376 ;  1,900,463 cons cells, 0 symbols, 17,693,712 other bytes, 0 static bytes
1377 ;;; starting timing testing 3...
1378 ; cpu time (non-gc) 52,290 msec user, 10 msec system
1379 ; cpu time (gc)     1,290 msec user, 30 msec system
1380 ; cpu time (total)  53,580 msec user, 40 msec system
1381 ; real time  54,062 msec
1382 ; space allocation:
1383 ;  7,800,205 cons cells, 0 symbols, 81,697,496 other bytes, 0 static bytes
1384
1385 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
1386 ;;; after improving decode-escaped-encoding/encode-escaped-encoding:
1387
1388 ;;; starting timing testing 1...
1389 ; cpu time (non-gc) 14,520 msec user, 0 msec system
1390 ; cpu time (gc)     400 msec user, 0 msec system
1391 ; cpu time (total)  14,920 msec user, 0 msec system
1392 ; real time  15,082 msec
1393 ; space allocation:
1394 ;  1,800,270 cons cells, 0 symbols, 41,600,160 other bytes, 0 static bytes
1395 ;;; starting timing testing 2...
1396 ; cpu time (non-gc) 27,490 msec user, 10 msec system
1397 ; cpu time (gc)     300 msec user, 0 msec system
1398 ; cpu time (total)  27,790 msec user, 10 msec system
1399 ; real time  28,025 msec
1400 ; space allocation:
1401 ;  1,900,436 cons cells, 0 symbols, 17,693,712 other bytes, 0 static bytes
1402 ;;; starting timing testing 3...
1403 ; cpu time (non-gc) 47,900 msec user, 20 msec system
1404 ; cpu time (gc)     920 msec user, 10 msec system
1405 ; cpu time (total)  48,820 msec user, 30 msec system
1406 ; real time  49,188 msec
1407 ; space allocation:
1408 ;  3,700,215 cons cells, 0 symbols, 81,707,144 other bytes, 0 static bytes