1 | #include "jsi.h" |
2 | #include "jsvalue.h" |
3 | |
4 | /* |
5 | Use an AA-tree to quickly look up properties in objects: |
6 | |
7 | The level of every leaf node is one. |
8 | The level of every left child is one less than its parent. |
9 | The level of every right child is equal or one less than its parent. |
10 | The level of every right grandchild is less than its grandparent. |
11 | Every node of level greater than one has two children. |
12 | |
13 | A link where the child's level is equal to that of its parent is called a horizontal link. |
14 | Individual right horizontal links are allowed, but consecutive ones are forbidden. |
15 | Left horizontal links are forbidden. |
16 | |
17 | skew() fixes left horizontal links. |
18 | split() fixes consecutive right horizontal links. |
19 | */ |
20 | |
21 | static js_Property sentinel = { |
22 | "" , |
23 | &sentinel, &sentinel, |
24 | 0, 0, |
25 | { {0}, {0}, JS_TUNDEFINED }, |
26 | NULL, NULL |
27 | }; |
28 | |
29 | static js_Property *newproperty(js_State *J, js_Object *obj, const char *name) |
30 | { |
31 | js_Property *node = js_malloc(J, sizeof *node); |
32 | node->name = js_intern(J, name); |
33 | node->left = node->right = &sentinel; |
34 | node->level = 1; |
35 | node->atts = 0; |
36 | node->value.type = JS_TUNDEFINED; |
37 | node->value.u.number = 0; |
38 | node->getter = NULL; |
39 | node->setter = NULL; |
40 | ++obj->count; |
41 | return node; |
42 | } |
43 | |
44 | static js_Property *lookup(js_Property *node, const char *name) |
45 | { |
46 | while (node != &sentinel) { |
47 | int c = strcmp(name, node->name); |
48 | if (c == 0) |
49 | return node; |
50 | else if (c < 0) |
51 | node = node->left; |
52 | else |
53 | node = node->right; |
54 | } |
55 | return NULL; |
56 | } |
57 | |
58 | static js_Property *skew(js_Property *node) |
59 | { |
60 | if (node->left->level == node->level) { |
61 | js_Property *temp = node; |
62 | node = node->left; |
63 | temp->left = node->right; |
64 | node->right = temp; |
65 | } |
66 | return node; |
67 | } |
68 | |
69 | static js_Property *split(js_Property *node) |
70 | { |
71 | if (node->right->right->level == node->level) { |
72 | js_Property *temp = node; |
73 | node = node->right; |
74 | temp->right = node->left; |
75 | node->left = temp; |
76 | ++node->level; |
77 | } |
78 | return node; |
79 | } |
80 | |
81 | static js_Property *insert(js_State *J, js_Object *obj, js_Property *node, const char *name, js_Property **result) |
82 | { |
83 | if (node != &sentinel) { |
84 | int c = strcmp(name, node->name); |
85 | if (c < 0) |
86 | node->left = insert(J, obj, node->left, name, result); |
87 | else if (c > 0) |
88 | node->right = insert(J, obj, node->right, name, result); |
89 | else |
90 | return *result = node; |
91 | node = skew(node); |
92 | node = split(node); |
93 | return node; |
94 | } |
95 | return *result = newproperty(J, obj, name); |
96 | } |
97 | |
98 | static void freeproperty(js_State *J, js_Object *obj, js_Property *node) |
99 | { |
100 | js_free(J, node); |
101 | --obj->count; |
102 | } |
103 | |
104 | static js_Property *delete(js_State *J, js_Object *obj, js_Property *node, const char *name) |
105 | { |
106 | js_Property *temp, *succ; |
107 | |
108 | if (node != &sentinel) { |
109 | int c = strcmp(name, node->name); |
110 | if (c < 0) { |
111 | node->left = delete(J, obj, node->left, name); |
112 | } else if (c > 0) { |
113 | node->right = delete(J, obj, node->right, name); |
114 | } else { |
115 | if (node->left == &sentinel) { |
116 | temp = node; |
117 | node = node->right; |
118 | freeproperty(J, obj, temp); |
119 | } else if (node->right == &sentinel) { |
120 | temp = node; |
121 | node = node->left; |
122 | freeproperty(J, obj, temp); |
123 | } else { |
124 | succ = node->right; |
125 | while (succ->left != &sentinel) |
126 | succ = succ->left; |
127 | node->name = succ->name; |
128 | node->atts = succ->atts; |
129 | node->value = succ->value; |
130 | node->right = delete(J, obj, node->right, succ->name); |
131 | } |
132 | } |
133 | |
134 | if (node->left->level < node->level - 1 || |
135 | node->right->level < node->level - 1) |
136 | { |
137 | if (node->right->level > --node->level) |
138 | node->right->level = node->level; |
139 | node = skew(node); |
140 | node->right = skew(node->right); |
141 | node->right->right = skew(node->right->right); |
142 | node = split(node); |
143 | node->right = split(node->right); |
144 | } |
145 | } |
146 | return node; |
147 | } |
148 | |
149 | js_Object *jsV_newobject(js_State *J, enum js_Class type, js_Object *prototype) |
150 | { |
151 | js_Object *obj = js_malloc(J, sizeof *obj); |
152 | memset(obj, 0, sizeof *obj); |
153 | obj->gcmark = 0; |
154 | obj->gcnext = J->gcobj; |
155 | J->gcobj = obj; |
156 | ++J->gccounter; |
157 | |
158 | obj->type = type; |
159 | obj->properties = &sentinel; |
160 | obj->prototype = prototype; |
161 | obj->extensible = 1; |
162 | return obj; |
163 | } |
164 | |
165 | js_Property *jsV_getownproperty(js_State *J, js_Object *obj, const char *name) |
166 | { |
167 | return lookup(obj->properties, name); |
168 | } |
169 | |
170 | js_Property *jsV_getpropertyx(js_State *J, js_Object *obj, const char *name, int *own) |
171 | { |
172 | *own = 1; |
173 | do { |
174 | js_Property *ref = lookup(obj->properties, name); |
175 | if (ref) |
176 | return ref; |
177 | obj = obj->prototype; |
178 | *own = 0; |
179 | } while (obj); |
180 | return NULL; |
181 | } |
182 | |
183 | js_Property *jsV_getproperty(js_State *J, js_Object *obj, const char *name) |
184 | { |
185 | do { |
186 | js_Property *ref = lookup(obj->properties, name); |
187 | if (ref) |
188 | return ref; |
189 | obj = obj->prototype; |
190 | } while (obj); |
191 | return NULL; |
192 | } |
193 | |
194 | static js_Property *jsV_getenumproperty(js_State *J, js_Object *obj, const char *name) |
195 | { |
196 | do { |
197 | js_Property *ref = lookup(obj->properties, name); |
198 | if (ref && !(ref->atts & JS_DONTENUM)) |
199 | return ref; |
200 | obj = obj->prototype; |
201 | } while (obj); |
202 | return NULL; |
203 | } |
204 | |
205 | js_Property *jsV_setproperty(js_State *J, js_Object *obj, const char *name) |
206 | { |
207 | js_Property *result; |
208 | |
209 | if (!obj->extensible) { |
210 | result = lookup(obj->properties, name); |
211 | if (J->strict && !result) |
212 | js_typeerror(J, "object is non-extensible" ); |
213 | return result; |
214 | } |
215 | |
216 | obj->properties = insert(J, obj, obj->properties, name, &result); |
217 | |
218 | return result; |
219 | } |
220 | |
221 | void jsV_delproperty(js_State *J, js_Object *obj, const char *name) |
222 | { |
223 | obj->properties = delete(J, obj, obj->properties, name); |
224 | } |
225 | |
226 | /* Flatten hierarchy of enumerable properties into an iterator object */ |
227 | |
228 | static js_Iterator *itwalk(js_State *J, js_Iterator *iter, js_Property *prop, js_Object *seen) |
229 | { |
230 | if (prop->right != &sentinel) |
231 | iter = itwalk(J, iter, prop->right, seen); |
232 | if (!(prop->atts & JS_DONTENUM)) { |
233 | if (!seen || !jsV_getenumproperty(J, seen, prop->name)) { |
234 | js_Iterator *head = js_malloc(J, sizeof *head); |
235 | head->name = prop->name; |
236 | head->next = iter; |
237 | iter = head; |
238 | } |
239 | } |
240 | if (prop->left != &sentinel) |
241 | iter = itwalk(J, iter, prop->left, seen); |
242 | return iter; |
243 | } |
244 | |
245 | static js_Iterator *itflatten(js_State *J, js_Object *obj) |
246 | { |
247 | js_Iterator *iter = NULL; |
248 | if (obj->prototype) |
249 | iter = itflatten(J, obj->prototype); |
250 | if (obj->properties != &sentinel) |
251 | iter = itwalk(J, iter, obj->properties, obj->prototype); |
252 | return iter; |
253 | } |
254 | |
255 | js_Object *jsV_newiterator(js_State *J, js_Object *obj, int own) |
256 | { |
257 | char buf[32]; |
258 | int k; |
259 | js_Object *io = jsV_newobject(J, JS_CITERATOR, NULL); |
260 | io->u.iter.target = obj; |
261 | if (own) { |
262 | io->u.iter.head = NULL; |
263 | if (obj->properties != &sentinel) |
264 | io->u.iter.head = itwalk(J, io->u.iter.head, obj->properties, NULL); |
265 | } else { |
266 | io->u.iter.head = itflatten(J, obj); |
267 | } |
268 | if (obj->type == JS_CSTRING) { |
269 | js_Iterator *tail = io->u.iter.head; |
270 | if (tail) |
271 | while (tail->next) |
272 | tail = tail->next; |
273 | for (k = 0; k < obj->u.s.length; ++k) { |
274 | js_itoa(buf, k); |
275 | if (!jsV_getenumproperty(J, obj, buf)) { |
276 | js_Iterator *node = js_malloc(J, sizeof *node); |
277 | node->name = js_intern(J, js_itoa(buf, k)); |
278 | node->next = NULL; |
279 | if (!tail) |
280 | io->u.iter.head = tail = node; |
281 | else { |
282 | tail->next = node; |
283 | tail = node; |
284 | } |
285 | } |
286 | } |
287 | } |
288 | return io; |
289 | } |
290 | |
291 | const char *jsV_nextiterator(js_State *J, js_Object *io) |
292 | { |
293 | int k; |
294 | if (io->type != JS_CITERATOR) |
295 | js_typeerror(J, "not an iterator" ); |
296 | while (io->u.iter.head) { |
297 | js_Iterator *next = io->u.iter.head->next; |
298 | const char *name = io->u.iter.head->name; |
299 | js_free(J, io->u.iter.head); |
300 | io->u.iter.head = next; |
301 | if (jsV_getproperty(J, io->u.iter.target, name)) |
302 | return name; |
303 | if (io->u.iter.target->type == JS_CSTRING) |
304 | if (js_isarrayindex(J, name, &k) && k < io->u.iter.target->u.s.length) |
305 | return name; |
306 | } |
307 | return NULL; |
308 | } |
309 | |
310 | /* Walk all the properties and delete them one by one for arrays */ |
311 | |
312 | void jsV_resizearray(js_State *J, js_Object *obj, int newlen) |
313 | { |
314 | char buf[32]; |
315 | const char *s; |
316 | int k; |
317 | if (newlen < obj->u.a.length) { |
318 | if (obj->u.a.length > obj->count * 2) { |
319 | js_Object *it = jsV_newiterator(J, obj, 1); |
320 | while ((s = jsV_nextiterator(J, it))) { |
321 | k = jsV_numbertointeger(jsV_stringtonumber(J, s)); |
322 | if (k >= newlen && !strcmp(s, jsV_numbertostring(J, buf, k))) |
323 | jsV_delproperty(J, obj, s); |
324 | } |
325 | } else { |
326 | for (k = newlen; k < obj->u.a.length; ++k) { |
327 | jsV_delproperty(J, obj, js_itoa(buf, k)); |
328 | } |
329 | } |
330 | } |
331 | obj->u.a.length = newlen; |
332 | } |
333 | |