1 define: #Path &slots: {#names -> ExtensibleArray new}.
2 "A Path represents a sequence of slot-name traversals which can be used to
3 reach a destination. Nil represents impossible path."
5 s@(Sequence traits) as: path@(Path traits)
6 [path clone `setting: #{#names} to: {s as: path names}].
8 Path traits compareAndHashUsingSlots: #{#names}.
10 path@(Path traits) hash
11 [path names hash bitXor: path traits identityHash].
13 path@(Path traits) from: root into: names foundBlock: foundBlock
14 &filterBlock: filterBlock
15 "Core of the path-searching functionality, contains the algorithm for
16 breadth-first search of path between objects through slots.
18 'root' is the object where the search begins.
19 'names' is WriteStream into which are symbols specifying found path added.
20 'foundBlock' - predicate block for termination on {source. name. target}.
21 'filterBlock' - predicate block to pre-filter the queue on {src. name. tgt}.
23 Answers the target object where the found path leads to, or Nil if requested
25 [| pathLookupQueue seen |
26 (foundBlock apply*, root, Nil, root) ifTrue: [^ root].
27 seen := IdentitySet newWith: root.
28 filterBlock `defaultsTo: [| :src :name :tgt | True].
30 (ExtensibleArray newWith: ExtensibleArray new -> root).
31 [pathLookupQueue isEmpty] whileFalse:
32 [| pathLookup lookupObject |
33 pathLookup := pathLookupQueue removeFirst.
34 lookupObject := pathLookup value.
36 lookupObject slotNames do:
37 [| :slotName slotValue |
38 slotValue := lookupObject atSlotNamed: slotName.
39 [(seen accepts: slotValue) /\ [(seen includes: slotValue) not]
42 seen include: slotValue.
43 newPath := pathLookup key copy.
44 newPath addLast: slotName.
45 (foundBlock apply*, lookupObject, slotName, slotValue)
46 ifTrue: [names ; newPath. ^ slotValue].
47 (filterBlock apply*, lookupObject, slotName, slotValue)
48 ifTrue: [pathLookupQueue addLast: newPath -> slotValue]]
49 ] breakOn: MethodNotFound].
50 "Check indexed slots."
51 lookupObject hasIndexedSlots ifTrue:
54 slotValue := lookupObject at: index.
55 (seen accepts: slotValue) /\ [(seen includes: slotValue) not]
58 seen include: slotValue.
59 newPath := pathLookup key copy.
60 newPath addLast: index.
61 (foundBlock apply*, lookupObject, index, slotValue)
62 ifTrue: [names ; newPath. ^ slotValue].
63 (filterBlock apply*, lookupObject, index, slotValue)
64 ifTrue: [pathLookupQueue addLast: newPath -> slotValue]]]]].
68 path@(Path traits) from: root to: target
69 "Answer a Path between the given objects, using a raw breadth-first search
70 through all slot paths, avoiding duplicate visits. Answers Nil when no path
73 names := path names new writer.
74 (path from: root into: names foundBlock: [| :_ :_ :value | value == target])
75 ifNotNil: [names contents as: RootedPath]
78 path@(Path traits) to: obj
79 "Answer a path from the current namespace to the given object."
80 [path from: here to: obj].
82 path1@(Path traits) ; path2@(Path traits)
84 path1 names ; path2 names as: path1
87 path@(Path traits) childNamed: name
89 (path expanded names copyWith: name) as: path
92 path@(Path traits) parent
94 path names allButLast as: path
97 path1@(Path traits) isPrefixOf: path2@(Path traits)
99 path1 names isPrefixOf: path2 names
102 path@(Path traits) targetFrom: root
103 "Answer the target object that the Path points to, starting from the given
104 object and following each name/index."
106 [path names inject: root into:
108 "Is the name for a named or indexed slot?"
109 (eachName isSameAs: Symbol)
110 ifTrue: [obj atSlotNamed: eachName]
111 ifFalse: [obj at: eachName]]]
112 on: SlotNotFound do: [| :c | ^ Nil]
115 path@(Path traits) target
116 "Assume that the the current namespace is the root of the path."
117 [path targetFrom: here].
119 path@(Path traits) printOn: s &root: root
121 s ; '("' ; path printName ; '" '.
123 do: [| :slotName | s ; slotName printString]
124 separatedBy: [s nextPut: $\s].
125 root ifNotNil: [s ; ' root: '. root printOn: s].
129 define: #RootedPath &parents: {Path} &slots: {#root -> lobby}.
130 "A Path that is based on a particular root object."
132 rooted@(RootedPath traits) as: path@(Path traits)
133 "Answer a path with the root object forgotten."
134 [rooted names as: path].
136 p1@(RootedPath traits) = p2@(RootedPath traits)
137 [(p1 root = p2 root) /\ [p1 names = p2 names]].
139 path@(RootedPath traits) unrooted
142 path@(RootedPath traits) from: root to: target
144 resend ifNotNilDo: [| :newP | newP names as: path &root: root]
147 s@(Sequence traits) as: path@(RootedPath traits) &root: root
149 path clone `setting: #{#names. #root}
150 to: {(s collect: [| :each | (each is: Integer)
151 ifTrue: [each] ifFalse: [each intern]])
156 m@(nodes UnaryMessage traits) as: path@(Path traits) &root: root
158 names := ExtensibleArray new.
160 [names addFirst: node selector.
161 node := node argument.
162 node isSameAs: m] whileTrue.
163 names as: path &root: root
166 m@(nodes ImplicitArgument traits) as: path@(Path traits) &root: root
167 [path from: (root ifNil: [here]) to: here].
169 m@(nodes Parenthesis traits) as: path@(Path traits) &root: root
170 [m statements size = 1 ifTrue: [m statements first as: path &root: root]].
172 path@(Path traits) as: _@(nodes Node traits)
174 path expanded names inject: nodes ImplicitArgument into:
177 ifTrue: [nodes KeywordMessage sending: #at: to:
178 {result. nodes Literal for: each}]
179 ifFalse: [nodes UnaryMessage sending: each intern to: {result}]]
182 path@(RootedPath traits) unrooted [path as: Path].
184 path@(RootedPath traits) target
186 path expanded targetFrom: path root
189 path@(RootedPath traits) isDefined
190 "Answer whether following the path from the root yields something."
191 [path target isNotNil].
193 path@(RootedPath traits) isOneSlotFromDefinition
194 "Answer whether the path is not defined but the immediate parent is."
195 [path isDefined not /\ [path parent isDefined]].
197 path@(RootedPath traits) reduced
198 "Answer a path consisting of the minimum set of accessor method lookups to
199 reach the same object."
201 names := path names new writer.
206 ifTrue: [this hasIndexedSlots /\ [this acceptsKey: each]
207 ifTrue: [names nextPut: each]
208 ifFalse: [each keyNotFoundOn: this]]
210 [(this hasSlotNamed: each)
211 ifTrue: [(this hasDelegateNamed: each)
212 ifFalse: [names nextPut: each].
213 this := this atSlotNamed: each]
214 ifFalse: [this slotNotFoundNamed: each]]].
215 names contents isEmpty /\ [path names isEmpty not]
216 ifTrue: [names nextPut: path names last].
217 names contents as: path
220 path@(RootedPath traits) expanded
221 "Answer a path consisting of the full set (through all delegations) of
222 accessor method lookups to reach the same object. This performs a search and
223 returns the shortest delegation path compatible with the original path's slot
224 names. If the path doesn't work, then those lookups are added unconditionally
225 (we can't be smart about error-handling here - let the client deal with the
228 names := path names new writer.
232 (path from: this into: names
233 foundBlock: [| :_ :name :_ | name == each]
234 &filterBlock: [| :obj :name :_ | obj hasDelegateNamed: name])
235 ifNil: [names nextPut: each]
236 ifNotNilDo: [| :target | this := target]].
237 names contents as: path &root: path root
240 path@(RootedPath traits) printOn: s &root: root
242 root `defaultsTo: path root.
246 x@(Root traits) knows: obj
247 [(Path from: x to: obj) isNotNil].
249 x@(Root traits) isWellKnown
252 x@(Root traits) whereIs: obj
253 "Answers a path from the first argument to the second."
254 [RootedPath from: x to: obj].
256 define: #PathFinder &parents: {ReadStream} &slots:
257 {#source -> (TraversalStream newOn: lobby &childrenVia: #slotValues `er).
258 #filterBlock -> [| :src :name :tgt | True]. "predicate for avoidance on {source. name. target}."
259 #foundBlock -> [| :src :name :tgt | x == Nil] "predicate for selection on {source. name. target}."}.
261 pf@(PathFinder traits) on: obj
262 [pf source := pf source newOn: obj].
264 pf@(PathFinder traits) newFrom: root selecting: foundBlock &filterBlock: filterBlock
266 newPF := pf newOn: root.
267 foundBlock ifNotNil: [newPF foundBlock := foundBlock].
268 filterBlock ifNotNil: [newPF filterBlock := filterBlock].
272 pf@(PathFinder traits) reset
275 pf@(PathFinder traits) next