Coverage report: /home/ellis/comp/core/lib/dat/dot.lisp
Kind | Covered | All | % |
expression | 0 | 333 | 0.0 |
branch | 0 | 32 | 0.0 |
Key
Not instrumented
Conditionalized out
Executed
Not executed
Both branches taken
One branch taken
Neither branch taken
1
;;; dot.lisp --- Graphviz DOT format
7
;; adapted from eschulte's graph library
9
;; ref: https://github.com/eschulte/graph/blob/master/dot.lisp
14
(in-readtable :std) ;; uses curry macros
18
"The information needed to specify a DOT rank statement. VALUE
19
expects a string and NODE-LIST expects a list."
24
"Returns a string containing a DOT rank statement. R is a RANK structure."
26
(with-output-to-string (out)
27
(when (and (rank-value r) (rank-node-list r))
28
(format out "{rank=~a;" (rank-value r))
30
(format out " ~s;" n))
32
(format out " }~%"))))
35
"The information needed to specify a DOT subgraph. NODE-ATTRIBUTES,
36
EDGE-ATTRIBUTES, and ATTRIBUTES expect assoc lists, and NODE-LIST
44
(defun subgraph-print (s)
45
"Returns a string containing a DOT subgraph statement. S is a
48
(with-output-to-string (out)
49
(format out "subgraph ~a {~%" (string (gensym "cluster_")))
50
(when (subgraph-node-attributes s)
51
(format out " node [~a];~%"
53
(format out "~a=~a, " (car pair) (cdr pair)))
54
(subgraph-node-attributes s))))
55
(when (subgraph-edge-attributes s)
56
(format out " edge [~a];~%"
58
(format out "~a=~a, " (car pair) (cdr pair)))
59
(subgraph-edge-attributes s))))
60
(when (subgraph-attributes s)
62
(format out " ~a=\"~a\";~%" (car pair) (cdr pair)))
63
(subgraph-attributes s)))
64
(when (subgraph-ranks s)
65
(mapcar #'rank-print (subgraph-ranks s)))
66
(when (subgraph-node-list s)
68
(format out " ~a;~%" n))
69
(subgraph-node-list s)))
70
(format out " }~%"))))
72
(defun edge-to-dot (edge graph attrs &optional stream)
73
(format stream " \"~a\" ~a \"~a\" ~{~a~^ ~};~%"
80
(destructuring-bind (attr . fn) l
81
(let ((val (funcall fn edge)))
83
(if (search "URL" (string attr))
87
:end (- (length (string attr)) 3))
89
(format nil "[~(~a~)=~a]" attr val)) ""))))
92
(defun node-to-dot (node attrs &optional stream)
93
(format stream " \"~a\" ~{~a~^ ~};~%" node
94
(mapcar (lambda (l) (destructuring-bind (attr . fn) l
95
(let ((val (funcall fn node)))
96
(if val (if (search "URL" (string attr))
97
(format nil "[~a=~a]" attr val)
98
(format nil "[~(~a~)=~a]" attr val)) ""))))
101
(defun graph-to-dot (graph
102
&key stream attributes node-attrs edge-attrs
104
"Print the dot code representing GRAPH. The keyword
105
argument ATTRIBUTES takes an assoc list with DOT graph attribute (name
106
. value) pairs. NODE-ATTRS and EDGE-ATTRS also take assoc lists of DOT graph
107
attributes and functions taking nodes or edges respectively and returning
108
values. The DOT graph, node, and edge attributes are described at
109
http://www.graphviz.org/doc/info/attrs.html. SUBGRAPHS is a list of SUBGRAPH
110
structures. RANKS is a list of RANK structures."
111
;; by default edges are labeled with their values
112
(declare (graph graph))
113
(unless (assoc :label edge-attrs)
116
(let ((value (edge-value graph edge)))
118
(format nil "\"~A\"" value)))))
120
(format stream "~a to_dot {~%~{~a~}}~%"
122
(directed-graph "digraph")
126
(destructuring-bind (a . b) l
127
(if (search "URL" (string a))
128
(format nil " ~a=~a;~%" a b)
129
(format nil " ~(~a~)=~a;~%" a b))))
131
(mapcar {node-to-dot _ node-attrs} (hash-table-keys (nodes graph)))
132
(mapcar {edge-to-dot _ graph edge-attrs} (hash-table-keys (edges graph)))
133
(mapcar #'subgraph-print subgraphs)
134
(mapcar #'rank-print ranks))))
136
(defun graph-to-dot-file (graph path
137
&key attributes node-attrs edge-attrs
139
"Write a dot representation of GRAPH to PATH."
140
(with-open-file (out path :direction :output :if-exists :supersede)
141
(graph-to-dot graph :stream out :attributes attributes :node-attrs node-attrs
142
:edge-attrs edge-attrs :subgraphs subgraphs :ranks ranks)))
144
(defun graph-from-dot (dot-string)
145
"Parse the DOT format string DOT-STRING into a graph.
146
More robust behavior may be achieved through parsing the output of the
148
(flet ((string->symbol (string) (intern (string-upcase string))))
149
(let* ((graph-type-re "^ *((di)?graph)")
150
(spec-re "[\\s]*(\\[([^]]+)\\])?;")
151
(node-name-re "[\\s]*\"?([a-zA-Z0-9_]+)\"?")
152
(node-spec-re (concatenate 'string node-name-re spec-re))
153
(edge-spec-re (concatenate 'string
154
node-name-re "[\\s]+([->]+)" node-name-re spec-re))
155
(label-name-re "label=(\"([^\"]+)\"|([^, ]+))[,\\]]")
156
(number-re "[0-9.\/e]+")
157
(graph (multiple-value-bind (string matches)
158
(cl-ppcre:scan-to-strings graph-type-re dot-string)
159
(declare (ignorable string))
160
(make-instance (string->symbol (aref matches 0))))))
162
(cl-ppcre:do-register-groups (node spec) (node-spec-re dot-string)
163
(declare (ignorable spec))
164
(unless (member node '("node" "graph") :test 'string=)
165
(add-node graph (symbolicate node))))
167
(cl-ppcre:do-register-groups (left arrow right spec) (edge-spec-re dot-string)
168
(declare (ignorable arrow))
169
(multiple-value-bind (matchp regs) (cl-ppcre:scan-to-strings label-name-re spec)
171
(mapcar #'symbolicate (list left right))
173
(if (cl-ppcre:scan number-re (aref regs 1))
174
(read-from-string (aref regs 1)))))))
178
(defmethod serialize ((self graph) (fmt (eql :dot))
179
&key stream path attributes node-attrs edge-attrs
181
(declare (ignore fmt))
183
((and stream path) (error "passed both STREAM and PATH - pick one"))
184
(stream (graph-to-dot self :stream stream :attributes attributes :node-attrs node-attrs
185
:edge-attrs edge-attrs :subgraphs subgraphs :ranks ranks))
186
(path (graph-to-dot-file self path :attributes attributes :node-attrs node-attrs
187
:edge-attrs edge-attrs :subgraphs subgraphs :ranks ranks))))
189
(defmethod deserialize ((from string) (fmt (eql :dot)) &key)
190
(declare (ignore fmt))
191
(graph-from-dot from))
193
;; (defun write-dot-stream (object stream)
194
;; "Write OBJECT to STREAM in Graphviz DOT format.")
196
;; (defun write-dot-file (path object)
197
;; "Write OBJECT to file PATH in Graphviz DOT format.")
199
;; (defun read-dot-stream (stream)
200
;; "Read from STREAM in Graphviz DOT format.")
202
;; (defun read-dot-file (path)
203
;; "Read from file PATH in Graphviz DOT format.")