-
-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathnodes.lisp
190 lines (150 loc) · 6.51 KB
/
nodes.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
(in-package #:org.shirakumo.flow)
(defclass unit ()
((attributes :initform (make-hash-table :test 'eql) :accessor attributes)))
(defmethod attribute ((unit unit) name &optional default)
(gethash name (attributes unit) default))
(defmethod (setf attribute) (value (unit unit) name)
(setf (gethash name (attributes unit)) value))
(defmethod remove-attribute ((unit unit) name)
(remhash name (attributes unit)))
(defmacro with-attributes (attributes unit &body body)
(let ((unitg (gensym "UNIT")))
`(let ((,unitg ,unit))
(symbol-macrolet ,(loop for attribute in attributes
collect `(,attribute (attribute ,unitg ',attribute)))
,@body))))
(defclass connection (unit)
((left :initarg :left :accessor left)
(right :initarg :right :accessor right)))
(defmethod print-object ((connection connection) stream)
(print-unreadable-object (connection stream :type T)
(format stream "~a <-> ~a"
(left connection)
(right connection))))
(defmethod connection= (a b)
(or (and (eql (left a) (left b))
(eql (right a) (right b)))
(and (eql (left a) (right b))
(eql (right a) (left b)))))
(defmethod sever ((connection connection))
(remove-connection connection (left connection))
(remove-connection connection (right connection))
connection)
(defclass directed-connection (connection)
())
(defmethod print-object ((connection directed-connection) stream)
(print-unreadable-object (connection stream :type T)
(format stream "~a --> ~a"
(left connection)
(right connection))))
(defmethod connection= ((a directed-connection) (b directed-connection))
(or (and (eql (left a) (left b))
(eql (right a) (right b)))))
(defclass port (unit)
((connections :initarg :connections :initform () :accessor connections)
(node :initarg :node :initform NIL :accessor node)
(name :initarg :name :initform NIL :accessor name)))
(defmethod print-object ((port port) stream)
(print-unreadable-object (port stream :type T)
(format stream "~a/~a" (node port) (name port))))
(defmethod describe-object :after ((port port) stream)
(format stream "~&~%Connections:~%")
(dolist (connection (connections port))
(let ((other (if (eq port (left connection)) (right connection) (left connection))))
(cond ((not (typep connection 'directed-connection))
(format stream " --- "))
((eq port (left connection))
(format stream " --> "))
(T
(format stream " <-- ")))
(format stream "~a ~a~%" (name other) (node other)))))
(defmethod connect ((left port) (right port) &optional (connection-type 'connection) &rest initargs)
(let ((connection (apply #'make-instance connection-type :left left :right right initargs)))
(check-connection-accepted connection left)
(check-connection-accepted connection right)
(push connection (connections left))
(push connection (connections right))
connection))
(defmethod disconnect ((left port) (right port))
(let ((connection (make-instance 'directed-connection :left left :right right)))
(remove-connection connection left :test #'connection=)
(remove-connection connection right :test #'connection=)
NIL))
(defmethod remove-connection (connection (port port) &key (test #'eql))
(setf (connections port) (remove connection (connections port) :test test)))
(defgeneric check-connection-accepted (connection port)
(:method-combination progn))
(defmethod check-connection-accepted progn (new-connection (port port))
(loop for connection in (connections port)
do (when (connection= connection new-connection)
(error 'connection-already-exists
:new-connection new-connection
:old-connection connection))))
(defmethod sever ((port port))
(mapc #'sever (connections port)))
(defclass n-port (port)
())
(defclass 1-port (port)
())
(defmethod check-connection-accepted progn (connection (port 1-port))
(when (connections port)
(error 'connection-already-exists
:new-connection connection
:old-connection (first (connections port)))))
(defclass in-port (port)
())
(defmethod check-connection-accepted progn ((connection directed-connection) (port in-port))
(unless (eql port (right connection))
(error 'illegal-connection :connection connection :message "Only incoming connections are allowed.")))
(defclass out-port (port)
())
(defmethod check-connection-accepted progn ((connection directed-connection) (port out-port))
(unless (eql port (left connection))
(error 'illegal-connection :connection connection :message "Only outgoing connections are allowed.")))
(defclass node (unit)
())
(defmethod describe-object :after ((node node) stream)
(format stream "~&~%")
(flet ((filter-ports (type)
(loop for port in (ports node)
when (typep port type)
collect (if (slot-boundp node (name port))
(cons (name port) (slot-value node (name port)))
(name port)))))
(org.shirakumo.text-draw:node
(filter-ports 'in-port) (filter-ports 'out-port) :stream stream)))
(defmethod sever ((node node))
(mapc #'sever (ports node)))
(defmethod connections ((node node))
(reduce #'append (ports node) :key #'connections))
(defmethod remove-connection (connection (node node) &key (test #'eql))
(dolist (port (ports node))
(remove-connection connection port :test test))
connection)
(defmethod disconnect ((node node) (port port))
(dolist (other-port (ports node))
(disconnect other-port port)))
(defmethod disconnect ((port port) (node node))
(dolist (other-port (ports node))
(disconnect port other-port)))
(defmethod disconnect ((a node) (b node))
(dolist (a-port (ports a))
(dolist (b-port (ports b))
(disconnect a-port b-port))))
(defclass dynamic-node (node)
((ports :initarg :ports :initform () :accessor ports)))
(defmethod port ((node dynamic-node) (name symbol))
(or (find name (ports node) :key #'name)
(error 'designator-not-a-port :port-name name :node node)))
(defun other-node (node connection)
(let ((right (flow:node (flow:right connection))))
(if (eq right node)
(flow:node (flow:left connection))
right)))
(defun target-node (node connection)
(let ((left (flow:node (flow:left connection))))
(if (eq left node)
(flow:node (flow:right connection))
(if (typep connection 'directed-connection)
NIL
left))))