-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathlist-structure.lisp
150 lines (142 loc) · 5.98 KB
/
list-structure.lisp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
(cl:in-package #:ecclesia)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; Tools for checking for list structure.
;;; For any object, return its structure as a list as two values: the
;;; first value contains the number of unique CONS cells in the list,
;;; and the second value is one of the keywords :proper, :dotted, and
;;; :circular. For an atom, 0 and :dotted is returned.
;;;
;;; This function is useful for processing code because lists
;;; representing code are not often very long, so the method used is
;;; fast and appropriate, and because we often need to check that such
;;; lists are proper, but the simple method would go into an infinite
;;; computation if the list is circular, whereas we would like to give
;;; an error message in that case.
(defun list-structure (object)
;; First we attempt to just traverse the list as usual,
;; assuming that it is fairly short. If we reach the end,
;; then that's great, and we return the result.
(loop for remaining = object then (cdr remaining)
for count from 0 to 100
while (consp remaining)
finally (when (atom remaining)
(return-from list-structure
(values count
(if (null remaining)
:proper
:dotted)))))
;; Come here if the list has more than a few CONS cells. We
;; traverse it again, this time entering each CONS cell in a hash
;; table. Stop when we reach the end of the list, or when we see
;; the same CONS cell twice.
(let ((table (make-hash-table :test #'eq)))
(loop for remaining = object then (cdr remaining)
while (consp remaining)
until (gethash remaining table)
do (setf (gethash remaining table) t)
finally (return (values (hash-table-count table)
(if (null remaining)
:proper
(if (atom remaining)
:dotted
:circular)))))))
;;; Check that an object is a proper list. Return true if the object
;;; is a proper list. Return false if the object is an atom other
;;; than NIL or if the list is dotted or circular.
;;;
;;; If LIST-LENGTH is given a proper list, then it returns the length
;;; of that list, which is a number. If LIST-LENGTH is given a
;;; circular list, it returns NIL which is not a number. If
;;; LIST-LENGTH is given anything other than a proper list or a
;;; circular list, it signals an error, so then IGNORE-ERRORS returns
;;; NIL as its first value, which again is not a number.
(defun proper-list-p (object)
(numberp (ignore-errors (list-length object))))
;;; Check that an object is a proper list, and if so, return the
;;; number of cons cells in the list. Return false if the object is
;;; an atom other than NIL or if the list is dotted or circular. If
;;; the object is NIL, 0 is returned.
(defun proper-list-length (object)
(values (ignore-errors (list-length object))))
;;; Return true if and only if OBJECT is a circular list.
;;;
;;; This definition relies on the fact that LIST-LENGTH returns a
;;; single value when it does not signal an error. So in that case,
;;; IGNORE-ERRORS returns a single value, and the value of the
;;; variable SECOND is thus NIL. And if OBJECT is a circular list,
;;; then the only value returned by LIST-LENGTH is NIL, so that is
;;; also the value of FIRST. If LIST-LENGTH does not signal an error
;;; and does not return NIL, then OBJECT is a proper list, so
;;; CIRCULAR-LIST-P returns NIL. If LIST-LENGTH is given anything
;;; other than a circular list or a proper list, it signals an error,
;;; so then IGNORE-ERRORS returns two values, NIL and a condition. In
;;; this case, SECOND is not NIL, so NIL is returned from
;;; CIRCULAR-LIST-P.
(defun circular-list-p (object)
(and (consp object)
(multiple-value-bind (first second)
(ignore-errors (list-length object))
(and (null first) (null second)))))
;;; This function returns true if the object is an atom other than NIL
;;; (the degenerate case of a dotted list) or if the list is
;;; terminated by some atom other than NIL. It returns false if the
;;; object is NIL, if the object is a list terminated by NIL, or of
;;; the object is a circular list.
(defun dotted-list-p (object)
(and (not (proper-list-p object))
(not (circular-list-p object))))
;;; Check that an object is a dotted list, and if so, return the
;;; number of cons cells in the list. Return false if the object is
;;; NIL, if the object is a list terminated by NIL, or of the object
;;; is a circular list. Return 0 if the object is an atom other than
;;; NIL (the degenerate case of a dotted list).
(defun dotted-list-length (object)
(cond ((null object) nil)
((atom object) 0)
(t (let ((slow object)
(fast (cdr object))
(count 1))
(declare (type cons slow))
;; We assume that the implementation is such that a
;; fixnum is able to hold the maximum number of CONS
;; cells possible in the heap.
(declare (type fixnum count))
(tagbody
again
(unless (consp fast)
(return-from dotted-list-length
(if (null fast) nil count)))
(when (eq fast slow)
(return-from dotted-list-length nil))
(setq fast (cdr fast))
(unless (consp fast)
(return-from dotted-list-length
(if (null fast) nil (1+ count))))
(setq fast (cdr fast))
(setq slow (cdr slow))
(incf count 2)
(go again))))))
(defun proper-or-dotted-list-length (object)
(cond ((atom object) 0)
(t (let ((slow object)
(fast (cdr object))
(count 1))
(declare (type cons slow))
;; We assume that the implementation is such that a
;; fixnum is able to hold the maximum number of CONS
;; cells possible in the heap.
(declare (type fixnum count))
(tagbody
again
(unless (consp fast)
(return-from proper-or-dotted-list-length count))
(when (eq fast slow)
(return-from proper-or-dotted-list-length nil))
(setq fast (cdr fast))
(unless (consp fast)
(return-from proper-or-dotted-list-length (1+ count)))
(setq fast (cdr fast))
(setq slow (cdr slow))
(incf count 2)
(go again))))))