-
Notifications
You must be signed in to change notification settings - Fork 0
/
read.lisp
executable file
·425 lines (383 loc) · 15.7 KB
/
read.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
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
;;; Copyright 2015 Nick Patrick
(in-package #:cl-dot)
(declaim (optimize debug))
;;; Utility rules
(defmacro with-reader-macros (assignments &body body)
(let ((originals (gensym)))
`(let ((,originals (list ,@(mapcar (lambda (assignment)
`(get-macro-character ,(car assignment)))
assignments))))
(unwind-protect
(progn
,@(mapcar (lambda (assignment)
`(set-macro-character ,@assignment))
assignments)
,@body)
(mapcar (lambda (char original)
(set-macro-character char original))
',(mapcar #'car assignments)
,originals)))))
;;; Comment
;;This breaks character entry!!! Figure it out later.
(defmacro with-dot-comment-reading (&body body)
`(with-reader-macros ((#\# (get-macro-character #\;)))
,@body))
;;; End of statement
(defparameter end-of-statement :eos)
(defparameter the-equal-flag :equal)
(defun end-of-statement (stream char)
"TODO: Something here instead probably"
(declare (ignore char stream))
end-of-statement)
(defun the-equal-flag (stream char)
"TODO: Something here instead probably"
(declare (ignore char stream))
the-equal-flag)
(defun nothing (stream char)
"TODO: Something here instead probably"
(declare (ignore char stream))
(values))
(defmacro with-nothing-reading (chars &body body)
`(with-reader-macros (,@(mapcar (lambda (char)
`(,char #'nothing))
chars))
,@body))
;; (defmacro with-eos-reading (char &body body)
;; `(with-reader-macros ((,char #'end-of-statement))
;; ,@body))
;; (defmacro with-equal-reading (&body body)
;; `(with-reader-macros ((#\= #'the-equal-flag))
;; ,@body))
(defvar *graph* nil
"The current subgraph being constructed.")
(defun read-char+ (stream)
(prog1 (read-char stream)
(remove-whitespace stream)))
(defun read-attribute-list (stream)
"[id1 = something; id2=else] => '((id1 . something) (id2 . else))"
(when (char/= (read-char+ stream) #\[)
(error "read-attribute-list isn't reading an attribute list..."))
(do ((result ()))
((char= (peek-char nil stream) #\])
(read-char+ stream)
result)
(let ((attr (read+ stream))
(eq? (read+ stream)))
(if (eq eq? the-equal-flag)
(push (cons attr (read+ stream))
result)
(error "Unexpected result reading attribute list ~A" eq?)))))
(define-constant +whitespace+
'(#\Space #\Return #\Newline #\Vt #\Page #\Tab)
:test #'equalp)
(defun remove-whitespace (stream)
"Remove whitespace from the stream, returning t when at least one
newline has been encountered."
(do* ((char (peek-char nil stream) (peek-char nil stream))
(newline? (char= char '#\Newline) (or newline?
(char= char '#\Newline))))
((not (some (lambda (ws) (char= char ws))
+whitespace+))
newline?)
(read-char stream t nil t)))
(defun read-dot-string (stream open-char)
(let ((buf (make-array 10
:element-type 'character
:adjustable t
:fill-pointer 0)))
(loop (let ((char (read-char stream)))
(if (eq char open-char)
(return (coerce buf 'string))
(vector-push-extend char buf 10))))))
(defun read-attribute-lists (stream)
"read one or more attribution lists. This is started by a macro
character on #\[, so at least one a-list is present. "
;; Read the first alist, then subsequent alists
(with-reader-macros ((#\" #'read-dot-string))
(do ((alist (read-attribute-list stream)
(append alist (read-attribute-list stream)))
;; As long as the next character after the last alist
(next-char (peek-char nil stream t nil t) (peek-char nil stream t nil t)))
;; is still regarding an alist.
((char/= next-char #\[)
;(format t "In read-attribute-lists, got ~A~%" alist)
;; Then return the set of concatenated alists.
alist)
;; (format t "In read-attribute-lists, got ~A, reading again when next-char == ~A~%"
;; alist next-char)
)))
;; (defmacro with-square-list-reading (&body body)
;; `(with-reader-macros ((#\[ #'read-attribute-lists)
;; (#\] (get-macro-character #\))))
;; ,@body))
(defmacro with-dot-readtable (&body body)
;; TODO: Make this a static readtable
`(with-reader-macros ((#\{ #'curly-reader)
(#\} (get-macro-character #\)))
(#\; #'nothing)
(#\, #'nothing)
(#\[ #'nothing)
(#\] #'nothing)
(#\= #'the-equal-flag)
(#\- #'read-edge)
(#\# (get-macro-character #\;)))
,@body))
(defun read-edge (stream char)
(declare (ignore char))
(let ((next (read-char+ stream)))
(ecase next
(#\> '->)
(#\- '--))))
;;; Debug...
(defmacro lispmode (&body body)
`(let ((*readtable* (copy-readtable nil)))
,@body))
(defparameter *dot-readtable*
(named-readtables:defreadtable dot-readtable
(:merge :standard)
(:case :preserve))
"A plain jane readtable, same as the standard one...except with case preserved.")
(defun lispify-symbol (symb)
(if (is-valid-id (symbol-name symb))
(id->symbol (symbol-name symb))
(error "Invalid id ~A" (symbol-name symb))))
(defparameter *env.graph* () "The global graph environment.")
(defparameter *env.node* () "The global node environment.")
(defparameter *env.edge* () "The global edge environment.")
;;; read subgraphs as a list of contents
(defun curly-reader (stream char)
"This function is only invoked when reading anonymous subgraphs."
(declare (ignore char) (optimize debug))
(assert *graph*)
(let* ((*graph* (make-instance
'subgraph
:node.env (node.env *graph*)
:edge.env (edge.env *graph*)
:graph.env (graph.env *graph*))))
(declare (special *graph*))
;; Set the current subgraph to that of the one being created.
(setf (graph.env *graph*) (extend (graph.env *graph*) 'subgraph *graph*)
(contents *graph*) (read-statement-list *graph* stream))
*graph*))
(define-condition stmt-read-failed (condition)
((next :reader next :initarg :next :initform nil)))
(defun read+ (stream)
(prog1
(read stream)
(remove-whitespace stream)))
(defgeneric get-nodes (element))
(defmethod get-nodes ((sg subgraph))
(subgraph-nodes sg))
(defmethod get-nodes ((node node))
(list node))
(defun read-edge-stmt (subgraph stream LHS)
"Given the current subgraph, a stream and the left-hand-side
of an edge statement, read the rest."
;; Can be node-id or subgraph
(let ((beginning-of-edge (peek-char nil stream)))
(case beginning-of-edge
(#\-
(let ((edge-op (read+ stream))
(RHS (read+ stream)))
(when (symbolp RHS)
(setf RHS (lookup-or-create-node RHS (graph.env subgraph))))
(if (eq edge-op (connector-style subgraph))
(let ((edges (make-edges subgraph
(get-nodes LHS)
(get-nodes RHS))))
(if (char/= (peek-char nil stream) #\;)
(multiple-value-bind (rest props) (read-edge-stmt subgraph stream RHS)
(when props
(mapc (lambda (edge)
(setf (specific.env edge) props))
edges))
(values (cons RHS rest)
props))
(values (cons RHS nil) nil)))
(error "Bad edge op ~A" edge-op))))
(#\[
;; properties
(values nil (read-attribute-lists stream)))
(t (values nil nil)))))
(defun read-edge-statements (subgraph stream LHS)
"Let read-edge-stmt handle constructing the edges...this function adds
edge properties to the returned list."
(multiple-value-bind (edges props) (read-edge-stmt subgraph stream LHS)
(make-instance 'edge-set
:edges (cons LHS edges)
:attributes props
:style (connector-style subgraph))))
(defun read-possibly-identified-subgraph (subgraph stream)
;; This could be named...let's check
(let* ((maybe-id (when (char/= (peek-char nil stream) #\{)
(read+ stream)))
(*graph* (make-instance
'subgraph
:id (when (symbolp maybe-id) maybe-id)
:node.env (node.env subgraph)
:edge.env (edge.env subgraph)
:graph.env (graph.env subgraph))))
(declare (special *graph*))
;; Get rid of the #\{
(read-char+ stream)
(setf (graph.env *graph*) (extend (graph.env *graph*) 'subgraph *graph*)
(contents *graph*) (read-statement-list *graph* stream))
*graph*))
(defun read-statement (subgraph stream)
"This function does most of the work for reading dot statements."
(remove-whitespace stream)
;; Check if we're prematurely done.
(when (member (peek-char nil stream) '(#\} #\;) :test #'char=)
(return-from read-statement (read-char stream)))
;; Get things started
(let ((first-things (read+ stream)))
(if (symbolp first-things)
(cond
((member first-things '("graph" "node" "edge")
:test #'string=)
(if (char= (peek-char nil stream) #\[)
(let ((result (read-attribute-lists stream)))
(cond
((string= first-things "graph") (setf (graph.env subgraph)
(extend (graph.env subgraph)
(mapcar #'car result)
(mapcar #'cdr result))))
((string= first-things "node") (setf (node.env subgraph)
(extend (node.env subgraph)
(mapcar #'car result)
(mapcar #'cdr result))))
((string= first-things "edge") (setf (edge.env subgraph)
(extend (edge.env subgraph)
(mapcar #'car result)
(mapcar #'cdr result)))))
(list first-things result))
(error "Expecting #\[ at position ~d"
(file-position stream))))
((string= first-things "SUBGRAPH")
(read-possibly-identified-subgraph subgraph stream))
(t ;; It's an ID for sure...but what kind of ID?
(if (char= (peek-char nil stream) #\=)
;; Return an ID '=' ID statement
(let ((eq (read-char+ stream)) ;eliminate the #\=
(value (read+ stream)))
(declare (ignore eq))
(setf (graph.env subgraph)
(extend (graph.env subgraph)
first-things
value))
(cons first-things value))
;; It's a node ID, so could be a node statement or edge statement.
;; Check if it's an edge...
(let ((node (lookup-or-create-node first-things (graph.env subgraph))))
(if (char= (peek-char nil stream) #\-)
;; It's got to be an edge!
(read-edge-statements subgraph stream node)
;; It's got to be a node statement!
(progn
(when (char= (peek-char nil stream) #\[)
(setf (specific.env node) (read-attribute-lists stream)))
node))))))
;; It must be a subgraph!, read the edge if present.
(if (char= (peek-char nil stream) #\-)
;; It's got to be an edge initiated by a subgraph!
(cons first-things (read-edge-statements subgraph stream first-things))
first-things))))
(defun read-statement-list (subgraph stream)
"Given all but the initial #\{, read a subgraph definition
and return a list of the contents."
;; TODO: handler-bind end-of-file to signal the condition
(accum stmt-list
(do ((statement (read-statement subgraph stream)
(read-statement subgraph stream)))
((and (characterp statement) (char= statement #\})))
(unless (and (characterp statement) (char= statement #\;))
(stmt-list statement)))))
(defun read-graph (stream)
"Read and verify that the stream contains a graph type"
;; Get strictness and graphiness
(let ((exp (read+ stream)))
(let* ((strictp (string= (symbol-name exp) "STRICT"))
(graph-type (if strictp
;; Need to read the graph type
(lispify-symbol (read+ stream))
(lispify-symbol exp))))
(let* ((graph-id (when (char/= (peek-char nil stream) #\{)
(read+ stream)))
(*graph* (make-instance
graph-type
:strict strictp
:id graph-id
:node.env *env.node*
:edge.env *env.edge*)))
;; Make sure we get the dynamic *graph* variable for use in tying back to
;; the real thing in reader macros. Is there a non-dynamic variable method
;; for passing lexical data to reader macros???
(declare (special *graph*))
(setf (graph.env *graph*)
(extend *env.graph*
;; Both subgraph and graph are
;; things. Graph should never be
;; rebound, but subgraph will always
;; hold the current subgraph.
'(subgraph graph nodes)
(list *graph* *graph* nil)))
;; Set the contents of the graph to everything in the thing.
(let ((next-char (read-char+ stream)))
(if (char= next-char #\{)
(setf (contents *graph*)
(read-statement-list *graph* stream))
(error "Unexpected character ~A when reading graph." next-char)))
*graph*))))
(defun read-dot (stream)
(let ((original-readtable *readtable*))
(unwind-protect
(progn
(setf *readtable* *dot-readtable*)
(with-dot-readtable
(read-graph stream)))
(setf *readtable* original-readtable))))
(defun read-dot-from-string (string)
(read-dot (make-string-input-stream string)))
(defun read-dot-from-file (file)
(with-open-file (in file)
(read-dot in)))
(defun dot-reader-macro (stream sub-char numarg)
(declare (ignore sub-char numarg))
(read-dot stream))
(defun set-dot-reader-macro (&optional (char #\D))
"Set the specified (defaults to #\D) # dispatch character
to read a graph from the stream.
e.g.
CL-DOT> #d digraph {foo->bar;}
digraph
{
foo->bar;
}
CL-DOT> #dgraph {foo -- bar -- baz}
graph
{
foo--bar--baz;
}"
(set-dispatch-macro-character #\# char #'dot-reader-macro))
#|
(cl-dot:read-dot-from-string "
digraph {
foo[shape=oval];
}")
(cl-dot:read-dot-from-string "
digraph {
subgraph {
node[border=dashed];
foo[shape=box][style=filled];
bar[shape=oval];
};
subgraph {
edge[style=dashed];
foo->bar;
}
subgraph {
edge[style=solid];
bar->foo;
}
}")
|#