forked from dylan-lang/slot-visitor
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathslot-visitor.dylan
168 lines (145 loc) · 5.66 KB
/
slot-visitor.dylan
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
module: slot-visitor
// See README.txt for documentation.
define macro slot-visitor-definer
{ define collection-recursive slot-visitor ?:name ?classes end }
=> {
define slot-visitor ?name ?classes end;
define method ?name
(col :: <collection>, action :: <function>, #rest keys,
#key setter, visited :: <table> = make(limited(<table>, of: <boolean>)),
#all-keys)
=> ()
// Recurse into collection elements
let pruned-keys = my-remove-property(keys, #[#"setter", #"visited"]);
if (instance?(col, <mutable-collection>))
for (o keyed-by i in col)
apply(?name, o, action, setter:, rcurry(my-element-setter, col, i),
visited:, visited, pruned-keys)
end for
else
for (o keyed-by i in col)
apply(?name, o, action, setter:, #f, visited:, visited, pruned-keys)
end for
end if
end method;
define method ?name ## "-slots"
(col :: <collection>, action :: <function>, #key, #all-keys)
=> ()
// No slots in a collection
end method
}
{ define slot-visitor ?:name ?classes:* end }
=> {
define generic ?name
(o :: <object>, f :: <function>, #key, #all-keys)
=> ();
define generic ?name ## "-slots"
(o :: <object>, f :: <function>, #key, #all-keys)
=> ();
class-visitors(?name; ?classes)
}
classes:
// If user specifies a visitor for <object>, ignore it; we'll be doing our
// own regardless.
{ <object>, ?slots:* ; ... } => { ... }
{ ?class-name:name, ?slots:* ; ... } => { ?class-name, ?slots ; ... }
{ } => { }
end macro;
define macro class-visitors
{ class-visitors(?:name; ?class-name:name, ?slots; ?more:*) }
=> {
define method ?name
(object :: ?class-name, action :: <function>, #rest keys,
#key setter, visited :: <table> = make(limited(<table>, of: <boolean>)),
#all-keys)
=> ()
unless (element(visited, object, default: #f))
visited[object] := #t;
let pruned-keys = my-remove-property(keys, #[#"setter", #"visited"]);
let skip-slots? =
if (instance?(object, action.function-specializers.first))
~ apply(action, object, setter:, setter, visited:, visited,
pruned-keys)
end if;
unless (skip-slots?)
apply(?name ## "-slots", object, action, visited:, visited, pruned-keys)
end unless;
end unless;
end method;
define method ?name ## "-slots"
(object :: ?class-name, action :: <function>, #next next-method,
#rest keys, #key, #all-keys)
=> ()
for (getter in getters-vector(?slots), setter in setters-vector(?slots))
apply(?name, object.getter, action, setter:, setter & rcurry(setter, object),
keys)
end for;
next-method() // Visit slots of superclasses
end method;
class-visitors(?name; ?more)
}
// When done with user-specified visitors, ensure there is also visitor on
// <object> since the visitor is a generic method and might encounter one.
// This won't visit any slots, but will perform the action if applicable.
{ class-visitors(?:name) }
=> {
define method ?name
(object :: <object>, action :: <function>, #rest keys,
#key setter, visited :: <table> = make(limited(<table>, of: <boolean>)),
#all-keys)
=> ()
unless (element(visited, object, default: #f))
visited[object] := #t;
let pruned-keys = my-remove-property(keys, #[#"setter", #"visited"]);
if (instance?(object, action.function-specializers.first))
apply(action, object, setter:, setter, visited:, visited,
pruned-keys)
end if;
end unless;
end method;
define method ?name ## "-slots"
(object :: <object>, action :: <function>, #next next-method,
#rest keys, #key, #all-keys)
=> ()
// Do nothing.
end method;
}
slots:
{ constant ?:name, ... } => { constant ?name, ... }
{ ?:name, ... } => { ?name, ... }
{ } => { }
end macro;
define macro setters-vector
{ setters-vector(?slots) } => { vector(?slots) }
slots:
{ constant ?:name, ... } => { #f, ... }
{ ?:name, ... } => { ?name ## "-setter", ... }
{ } => { }
end macro;
define macro getters-vector
{ getters-vector(?slots) } => { vector(?slots) }
slots:
{ constant ?:name, ... } => { ?name, ... }
{ ?:name, ... } => { ?name, ... }
{ } => { }
end macro;
// BUGFIX: This pass-through function works around issue #424.
define function my-element-setter (v, c :: <mutable-collection>, k)
element-setter(v, c, k)
end function;
// BUGFIX: This function works around issue #443, which is that
// collections:plists:remove-property! does not work.
define function my-remove-property (key-value-pairs :: <sequence>, unwanted :: <collection>)
=> (new-keys :: <sequence>)
let new-pairs = make(<stretchy-vector>);
let old-pairs = as(<vector>, key-value-pairs);
for (i from 0 below old-pairs.size by 2)
let key = old-pairs[i];
let val = old-pairs[i + 1];
if (~member?(key, unwanted))
add!(new-pairs, key);
add!(new-pairs, val);
end if;
end for;
new-pairs
end function;