1 | // -*- c-basic-offset: 2 -*- |
2 | /* |
3 | * This file is part of the KDE libraries |
4 | * Copyright (C) 1999-2000 Harri Porten (porten@kde.org) |
5 | * Copyright (C) 2003 Apple Computer, Inc. |
6 | * |
7 | * This library is free software; you can redistribute it and/or |
8 | * modify it under the terms of the GNU Lesser General Public |
9 | * License as published by the Free Software Foundation; either |
10 | * version 2 of the License, or (at your option) any later version. |
11 | * |
12 | * This library is distributed in the hope that it will be useful, |
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
15 | * Lesser General Public License for more details. |
16 | * |
17 | * You should have received a copy of the GNU Lesser General Public |
18 | * License along with this library; if not, write to the Free Software |
19 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
20 | * |
21 | */ |
22 | |
23 | #ifndef _KJSLOOKUP_H_ |
24 | #define _KJSLOOKUP_H_ |
25 | |
26 | #include "JSVariableObject.h" |
27 | #include "interpreter.h" |
28 | #include "identifier.h" |
29 | #include "object.h" |
30 | #include <stdio.h> |
31 | |
32 | namespace KJS { |
33 | class FunctionPrototype; |
34 | |
35 | /** |
36 | * An entry in a hash table. |
37 | */ |
38 | struct KJS_EXPORT HashEntry { |
39 | /** |
40 | * s is the key (e.g. a property name) |
41 | */ |
42 | const char *s; |
43 | |
44 | /** |
45 | * value is the result value (usually an enum value) |
46 | */ |
47 | int value; |
48 | /** |
49 | * attr is a set for flags (e.g. the property flags, see object.h) |
50 | */ |
51 | short int attr; |
52 | /** |
53 | * params is another number. For property hashtables, it is used to |
54 | * denote the number of argument of the function |
55 | */ |
56 | short int params; |
57 | /** |
58 | * next is the pointer to the next entry for the same hash value |
59 | */ |
60 | const HashEntry *next; |
61 | }; |
62 | |
63 | /** |
64 | * A hash table |
65 | * Usually the hashtable is generated by the create_hash_table script, from a .table file. |
66 | * |
67 | * The implementation uses an array of entries, "size" is the total size of that array. |
68 | * The entries between 0 and hashSize-1 are the entry points |
69 | * for each hash value, and the entries between hashSize and size-1 |
70 | * are the overflow entries for the hash values that need one. |
71 | * The "next" pointer of the entry links entry points to overflow entries, |
72 | * and links overflow entries between them. |
73 | */ |
74 | struct HashTable { |
75 | /** |
76 | * type is a version number. Currently always 2 |
77 | */ |
78 | int type; |
79 | /** |
80 | * size is the total number of entries in the hashtable, including the null entries, |
81 | * i.e. the size of the "entries" array. |
82 | * Used to iterate over all entries in the table |
83 | */ |
84 | int size; |
85 | /** |
86 | * pointer to the array of entries |
87 | * Mind that some entries in the array are null (0,0,0,0). |
88 | */ |
89 | const HashEntry *entries; |
90 | /** |
91 | * the maximum value for the hash. Always smaller than size. |
92 | */ |
93 | int hashSize; |
94 | }; |
95 | |
96 | /** |
97 | * @short Fast keyword lookup. |
98 | */ |
99 | class KJS_EXPORT Lookup { |
100 | public: |
101 | /** |
102 | * Find an entry in the table, and return its value (i.e. the value field of HashEntry) |
103 | */ |
104 | static int find(const struct HashTable *table, const Identifier &s); |
105 | static int find(const struct HashTable *table, |
106 | const UChar *c, unsigned int len); |
107 | |
108 | |
109 | /** |
110 | * Find an entry in the table, and return the entry |
111 | * This variant gives access to the other attributes of the entry, |
112 | * especially the attr field. |
113 | */ |
114 | static const HashEntry* findEntry(const struct HashTable *table, |
115 | const Identifier &s); |
116 | |
117 | }; |
118 | |
119 | class ExecState; |
120 | /** |
121 | * @internal |
122 | * Helper for getStaticFunctionSlot and getStaticPropertySlot |
123 | */ |
124 | template <class FuncImp> |
125 | inline JSValue *staticFunctionGetter(ExecState *exec, JSObject * /*originalObject*/, const Identifier& propertyName, const PropertySlot& slot) |
126 | { |
127 | // Look for cached value in dynamic map of properties (in JSObject) |
128 | JSObject *thisObj = slot.slotBase(); |
129 | JSValue *cachedVal = thisObj->getDirect(propertyName); |
130 | if (cachedVal) |
131 | return cachedVal; |
132 | |
133 | const HashEntry *entry = slot.staticEntry(); |
134 | JSValue *val = new FuncImp(exec, entry->value, entry->params, propertyName); |
135 | thisObj->putDirect(propertyName, val, entry->attr); |
136 | return val; |
137 | } |
138 | |
139 | /** |
140 | * @internal |
141 | * Helper for getStaticValueSlot and getStaticPropertySlot |
142 | */ |
143 | template <class ThisImp> |
144 | inline JSValue *staticValueGetter(ExecState *exec, JSObject*, const Identifier&, const PropertySlot& slot) |
145 | { |
146 | ThisImp* thisObj = static_cast<ThisImp*>(slot.slotBase()); |
147 | const HashEntry* entry = slot.staticEntry(); |
148 | return thisObj->getValueProperty(exec, entry->value); |
149 | } |
150 | |
151 | /** |
152 | * Helper method for property lookups |
153 | * |
154 | * This method does it all (looking in the hashtable, checking for function |
155 | * overrides, creating the function or retrieving from cache, calling |
156 | * getValueProperty in case of a non-function property, forwarding to parent if |
157 | * unknown property). |
158 | * |
159 | * Template arguments: |
160 | * @param FuncImp the class which implements this object's functions |
161 | * @param ThisImp the class of "this". It must implement the getValueProperty(exec,token) method, |
162 | * for non-function properties. |
163 | * @param ParentImp the class of the parent, to propagate the lookup. |
164 | * |
165 | * Method arguments: |
166 | * @param exec execution state, as usual |
167 | * @param propertyName the property we're looking for |
168 | * @param table the static hashtable for this class |
169 | * @param thisObj "this" |
170 | */ |
171 | template <class FuncImp, class ThisImp, class ParentImp> |
172 | inline bool getStaticPropertySlot(ExecState *exec, const HashTable* table, |
173 | ThisImp* thisObj, const Identifier& propertyName, PropertySlot& slot) |
174 | { |
175 | const HashEntry* entry = Lookup::findEntry(table, propertyName); |
176 | |
177 | if (!entry) // not found, forward to parent |
178 | return thisObj->ParentImp::getOwnPropertySlot(exec, propertyName, slot); |
179 | |
180 | if (entry->attr & Function) |
181 | slot.setStaticEntry(thisObj, entry, staticFunctionGetter<FuncImp>); |
182 | else |
183 | slot.setStaticEntry(thisObj, entry, staticValueGetter<ThisImp>); |
184 | |
185 | return true; |
186 | } |
187 | |
188 | /** |
189 | * Simplified version of getStaticPropertySlot in case there are only functions. |
190 | * Using this instead of getStaticPropertySlot allows 'this' to avoid implementing |
191 | * a dummy getValueProperty. |
192 | */ |
193 | template <class FuncImp, class ParentImp> |
194 | inline bool getStaticFunctionSlot(ExecState *exec, const HashTable *table, |
195 | JSObject* thisObj, const Identifier& propertyName, PropertySlot& slot) |
196 | { |
197 | const HashEntry* entry = Lookup::findEntry(table, propertyName); |
198 | |
199 | if (!entry) // not found, forward to parent |
200 | return static_cast<ParentImp *>(thisObj)->ParentImp::getOwnPropertySlot(exec, propertyName, slot); |
201 | |
202 | assert(entry->attr & Function); |
203 | |
204 | slot.setStaticEntry(thisObj, entry, staticFunctionGetter<FuncImp>); |
205 | return true; |
206 | } |
207 | |
208 | /** |
209 | * Simplified version of getStaticPropertySlot in case there are no functions, only "values". |
210 | * Using this instead of getStaticPropertySlot removes the need for a FuncImp class. |
211 | */ |
212 | template <class ThisImp, class ParentImp> |
213 | inline bool getStaticValueSlot(ExecState *exec, const HashTable* table, |
214 | ThisImp* thisObj, const Identifier &propertyName, PropertySlot& slot) |
215 | { |
216 | const HashEntry* entry = Lookup::findEntry(table, propertyName); |
217 | |
218 | if (!entry) // not found, forward to parent |
219 | return thisObj->ParentImp::getOwnPropertySlot(exec, propertyName, slot); |
220 | |
221 | assert(!(entry->attr & Function)); |
222 | |
223 | slot.setStaticEntry(thisObj, entry, staticValueGetter<ThisImp>); |
224 | return true; |
225 | } |
226 | |
227 | /** |
228 | * This one is for "put". |
229 | * It looks up a hash entry for the property to be set. If an entry |
230 | * is found it sets the value and returns true, else it returns false. |
231 | */ |
232 | template <class ThisImp> |
233 | inline bool lookupPut(ExecState* exec, const Identifier &propertyName, |
234 | JSValue* value, int attr, |
235 | const HashTable* table, ThisImp* thisObj) |
236 | { |
237 | const HashEntry* entry = Lookup::findEntry(table, propertyName); |
238 | if (!entry) |
239 | return false; |
240 | |
241 | if (entry->attr & Function) // function: put as override property |
242 | thisObj->JSObject::put(exec, propertyName, value, attr); |
243 | else if (entry->attr & ReadOnly) // readonly! Can't put! |
244 | #ifdef KJS_VERBOSE |
245 | fprintf(stderr,"WARNING: Attempt to change value of readonly property '%s'\n" ,propertyName.ascii()); |
246 | #else |
247 | ; // do nothing |
248 | #endif |
249 | else |
250 | thisObj->putValueProperty(exec, entry->value, value, attr); |
251 | |
252 | return true; |
253 | } |
254 | |
255 | /** |
256 | * This one is for "put". |
257 | * It calls lookupPut<ThisImp>() to set the value. If that call |
258 | * returns false (meaning no entry in the hash table was found), |
259 | * then it calls put() on the ParentImp class. |
260 | */ |
261 | template <class ThisImp, class ParentImp> |
262 | inline void lookupPut(ExecState* exec, const Identifier &propertyName, |
263 | JSValue* value, int attr, |
264 | const HashTable* table, ThisImp* thisObj) |
265 | { |
266 | if (!lookupPut<ThisImp>(exec, propertyName, value, attr, table, thisObj)) |
267 | thisObj->ParentImp::put(exec, propertyName, value, attr); // not found: forward to parent |
268 | } |
269 | } // namespace |
270 | |
271 | #if COMPILER(GCC) |
272 | // Work around a bug in GCC 4.1. The original code was |
273 | // #if !COMPILER(GCC) |
274 | // #define KJS_GCC_ROOT_NS_HACK :: |
275 | // #else |
276 | // #define KJS_GCC_ROOT_NS_HACK |
277 | // #endif |
278 | // We separate use and declaration here; the define KJS_OBJECTCAHE_IN_KJS |
279 | // distinguishes if the cache is in KJS (value 1) or not (value 0). |
280 | #define KJS_OBJECTCACHE_IN_KJS (0) |
281 | #define KJS_CACHEGLOBALOBJECT_NS |
282 | #define KJS_CACHEGLOBALOBJECT_NS_USE :: |
283 | #else |
284 | #if COMPILER(SUNPRO) |
285 | // SunPro puts the whole thing in namespace KJS::, no linking problems. |
286 | #define KJS_OBJECTCACHE_IN_KJS (1) |
287 | #define KJS_CACHEGLOBALOBJECT_NS KJS:: |
288 | #define KJS_CACHEGLOBALOBJECT_NS_USE KJS:: |
289 | #else |
290 | // All other non-Studio, non-GCC compilers are forced to put the |
291 | // object cache outside the KJS namespace, and don't use the GCC |
292 | // hack to do so. |
293 | #define KJS_OBJECTCACHE_IN_KJS (0) |
294 | #define KJS_CACHEGLOBALOBJECT_NS :: |
295 | #define KJS_CACHEGLOBALOBJECT_NS_USE :: |
296 | #endif |
297 | #endif |
298 | |
299 | #if KJS_OBJECTCACHE_IN_KJS |
300 | namespace KJS { |
301 | #endif |
302 | /* |
303 | * The template method below can't be in the KJS namespace because it's used in |
304 | * KJS_DEFINE_PROPERTY which can be used outside of the KJS namespace. It can |
305 | * be moved back when a gcc with a fix for |
306 | * http://gcc.gnu.org/bugzilla/show_bug.cgi?id=8355 |
307 | * is mainstream enough. |
308 | * |
309 | * This note applies only to GCC and other non-Studio12 compilers. Studio12 |
310 | * does support having this template in namespace KJS. The macro |
311 | * KJS_OBJECTCACHE_IN_KJS expands to 1 when it is safe to put the template |
312 | * in the KJS namespace. |
313 | */ |
314 | |
315 | /** |
316 | * This template method retrieves or create an object that is unique |
317 | * (for a given interpreter) The first time this is called (for a given |
318 | * property name), the Object will be constructed, and set as a property |
319 | * of the interpreter's global object. Later calls will simply retrieve |
320 | * that cached object. Note that the object constructor must take 1 argument, exec. |
321 | */ |
322 | template <class ClassCtor> |
323 | inline KJS::JSObject *cacheGlobalObject(KJS::ExecState *exec, const KJS::Identifier &propertyName) |
324 | { |
325 | KJS::JSObject *globalObject = static_cast<KJS::JSObject *>(exec->lexicalInterpreter()->globalObject()); |
326 | KJS::JSValue *obj = globalObject->getDirect(propertyName); |
327 | if (obj) { |
328 | assert(obj->isObject()); |
329 | return static_cast<KJS::JSObject *>(obj); |
330 | } |
331 | KJS::JSObject *newObject = new ClassCtor(exec); |
332 | globalObject->put(exec, propertyName, newObject, KJS::Internal | KJS::DontEnum); |
333 | return newObject; |
334 | } |
335 | #if KJS_OBJECTCACHE_IN_KJS |
336 | } |
337 | #endif |
338 | |
339 | /** |
340 | * Helpers to define prototype objects (each of which simply implements |
341 | * the functions for a type of objects). |
342 | * Sorry for this not being very readable, but it actually saves much copy-n-paste. |
343 | * ParentProto is not our base class, it's the object we use as fallback. |
344 | * The reason for this is that there should only be ONE DOMNode.hasAttributes (e.g.), |
345 | * not one in each derived class. So we link the (unique) prototypes between them. |
346 | * |
347 | * Using those macros is very simple: define the hashtable (e.g. "DOMNodeProtoTable"), then |
348 | * KJS_DEFINE_PROTOTYPE(DOMNodeProto) |
349 | * KJS_IMPLEMENT_PROTOFUNC(DOMNodeProtoFunc) |
350 | * KJS_IMPLEMENT_PROTOTYPE("DOMNode", DOMNodeProto,DOMNodeProtoFunc) |
351 | * and use DOMNodeProto::self(exec) as prototype in the DOMNode constructor. |
352 | * If the prototype has a "parent prototype", e.g. DOMElementProto falls back on DOMNodeProto, |
353 | * then the first line will use KJS_DEFINE_PROTOTYPE_WITH_PROTOTYPE, with DOMNodeProto as the second argument. |
354 | */ |
355 | |
356 | // These macros assume that a prototype's only properties are functions |
357 | #define KJS_DEFINE_PROTOTYPE(ClassProto) \ |
358 | class ClassProto : public KJS::JSObject { \ |
359 | friend KJS::JSObject* KJS_CACHEGLOBALOBJECT_NS cacheGlobalObject<ClassProto>(KJS::ExecState *exec, const KJS::Identifier &propertyName); \ |
360 | public: \ |
361 | static KJS::JSObject *self(KJS::ExecState *exec); \ |
362 | virtual const KJS::ClassInfo *classInfo() const { return &info; } \ |
363 | static const KJS::ClassInfo info; \ |
364 | bool getOwnPropertySlot(KJS::ExecState *, const KJS::Identifier&, KJS::PropertySlot&); \ |
365 | using JSObject::getOwnPropertySlot; \ |
366 | protected: \ |
367 | ClassProto(KJS::ExecState *exec);\ |
368 | static KJS::Identifier* s_name; \ |
369 | static KJS::Identifier* name(); \ |
370 | }; |
371 | |
372 | #define KJS_IMPLEMENT_PROTOTYPE_IMP(ClassName,ClassProto,ClassFunc,ClassProtoProto) \ |
373 | const KJS::ClassInfo ClassProto::info = { ClassName, 0, &ClassProto##Table, 0 }; \ |
374 | KJS::Identifier* ClassProto::s_name = 0; \ |
375 | KJS::JSObject *ClassProto::self(KJS::ExecState *exec) \ |
376 | { \ |
377 | return KJS_CACHEGLOBALOBJECT_NS cacheGlobalObject<ClassProto>(exec, *name()); \ |
378 | } \ |
379 | bool ClassProto::getOwnPropertySlot(KJS::ExecState *exec, const KJS::Identifier& propertyName, KJS::PropertySlot& slot) \ |
380 | { \ |
381 | return KJS::getStaticFunctionSlot<ClassFunc, KJS::JSObject>(exec, &ClassProto##Table, this, propertyName, slot); \ |
382 | } \ |
383 | KJS::Identifier* ClassProto::name() \ |
384 | { \ |
385 | if (!s_name) s_name = new KJS::Identifier("[[" ClassName ".prototype]]"); \ |
386 | return s_name; \ |
387 | }\ |
388 | ClassProto::ClassProto(KJS::ExecState *exec): KJS::JSObject(ClassProtoProto::self(exec)) |
389 | |
390 | #define KJS_IMPLEMENT_PROTOTYPE(ClassName,ClassProto,ClassFunc,ClassProtoProto) \ |
391 | KJS_IMPLEMENT_PROTOTYPE_IMP(ClassName,ClassProto,ClassFunc,ClassProtoProto) {} |
392 | |
393 | #define KJS_IMPLEMENT_PROTOFUNC(ClassFunc) \ |
394 | class ClassFunc : public KJS::InternalFunctionImp { \ |
395 | public: \ |
396 | ClassFunc(KJS::ExecState* exec, int i, int len, const KJS::Identifier& name) \ |
397 | : InternalFunctionImp(static_cast<KJS::FunctionPrototype*>(exec->lexicalInterpreter()->builtinFunctionPrototype()), name) \ |
398 | , id(i) \ |
399 | { \ |
400 | put(exec, exec->propertyNames().length, KJS::jsNumber(len), KJS::DontDelete|KJS::ReadOnly|KJS::DontEnum); \ |
401 | } \ |
402 | /* Macro user needs to implement the callAsFunction function. */ \ |
403 | virtual KJS::JSValue *callAsFunction(KJS::ExecState *exec, KJS::JSObject *thisObj, const KJS::List &args); \ |
404 | private: \ |
405 | int id; \ |
406 | }; |
407 | |
408 | /* |
409 | * List of things to do when porting an objectimp to the 'static hashtable' mechanism: |
410 | * - write the hashtable source, between @begin and @end |
411 | * - add a rule to build the .lut.h |
412 | * - include the .lut.h |
413 | * - mention the table in the classinfo (add a classinfo if necessary) |
414 | * - write/update the class enum (for the tokens) |
415 | * - turn get() into getValueProperty(), put() into putValueProperty(), using a switch and removing funcs |
416 | * - write get() and/or put() using a template method |
417 | * - cleanup old stuff (e.g. hasProperty) |
418 | * - compile, test, commit ;) |
419 | */ |
420 | |
421 | |
422 | #endif |
423 | |