2003/05/12 05:10:30
[org.ibex.core.git] / src / org / mozilla / javascript / BaseFunction.java
1 /* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-\r
2  *\r
3  * The contents of this file are subject to the Netscape Public\r
4  * License Version 1.1 (the "License"); you may not use this file\r
5  * except in compliance with the License. You may obtain a copy of\r
6  * the License at http://www.mozilla.org/NPL/\r
7  *\r
8  * Software distributed under the License is distributed on an "AS\r
9  * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express oqr\r
10  * implied. See the License for the specific language governing\r
11  * rights and limitations under the License.\r
12  *\r
13  * The Original Code is Rhino code, released\r
14  * May 6, 1999.\r
15  *\r
16  * The Initial Developer of the Original Code is Netscape\r
17  * Communications Corporation.  Portions created by Netscape are\r
18  * Copyright (C) 1997-1999 Netscape Communications Corporation. All\r
19  * Rights Reserved.\r
20  *\r
21  * Contributor(s): \r
22  * Norris Boyd\r
23  * Igor Bukanov\r
24  * Roger Lawrence\r
25  * Mike McCabe\r
26  *\r
27  * Alternatively, the contents of this file may be used under the\r
28  * terms of the GNU Public License (the "GPL"), in which case the\r
29  * provisions of the GPL are applicable instead of those above.\r
30  * If you wish to allow use of your version of this file only\r
31  * under the terms of the GPL and not to allow others to use your\r
32  * version of this file under the NPL, indicate your decision by\r
33  * deleting the provisions above and replace them with the notice\r
34  * and other provisions required by the GPL.  If you do not delete\r
35  * the provisions above, a recipient may use your version of this\r
36  * file under either the NPL or the GPL.\r
37  */\r
38 \r
39 package org.mozilla.javascript;\r
40 \r
41 /**\r
42  * The base class for Function objects\r
43  * See ECMA 15.3.\r
44  * @author Norris Boyd\r
45  */\r
46 public class BaseFunction extends IdScriptable implements Function {\r
47 \r
48     static void init(Context cx, Scriptable scope, boolean sealed) {\r
49         BaseFunction obj = new BaseFunction();\r
50         obj.prototypeFlag = true;\r
51         obj.functionName = "";\r
52         obj.prototypePropertyAttrs = DONTENUM | READONLY | PERMANENT;\r
53         obj.addAsPrototype(MAX_PROTOTYPE_ID, cx, scope, sealed);\r
54     }\r
55     \r
56     protected void fillConstructorProperties\r
57         (Context cx, IdFunction ctor, boolean sealed)\r
58     {\r
59         // Fix up bootstrapping problem: getPrototype of the IdFunction \r
60         // can not return Function.prototype because Function object is not\r
61         // yet defined.\r
62         ctor.setPrototype(this);\r
63     }\r
64 \r
65     public String getClassName() {\r
66         return "Function";\r
67     }\r
68 \r
69     /**\r
70      * Implements the instanceof operator for JavaScript Function objects.\r
71      * <p>\r
72      * <code>\r
73      * foo = new Foo();<br>\r
74      * foo instanceof Foo;  // true<br>\r
75      * </code>\r
76      *\r
77      * @param instance The value that appeared on the LHS of the instanceof\r
78      *              operator\r
79      * @return true if the "prototype" property of "this" appears in\r
80      *              value's prototype chain\r
81      *\r
82      */\r
83     public boolean hasInstance(Scriptable instance) {\r
84         Object protoProp = ScriptableObject.getProperty(this, "prototype");\r
85         if (protoProp instanceof Scriptable && protoProp != Undefined.instance)\r
86         {\r
87             return ScriptRuntime.jsDelegatesTo(instance, (Scriptable)protoProp);\r
88         }\r
89         throw NativeGlobal.typeError1\r
90             ("msg.instanceof.bad.prototype", functionName, instance);\r
91     }\r
92 \r
93     protected int getIdDefaultAttributes(int id) {\r
94         switch (id) {\r
95             case Id_length:\r
96             case Id_arity:\r
97             case Id_name:\r
98                 return DONTENUM | READONLY | PERMANENT;\r
99             case Id_prototype:\r
100                 return prototypePropertyAttrs;\r
101             case Id_arguments:\r
102                 return EMPTY;\r
103         }\r
104         return super.getIdDefaultAttributes(id);\r
105     }\r
106     \r
107     protected boolean hasIdValue(int id) {\r
108         if (id == Id_prototype) {\r
109             return prototypeProperty != NOT_FOUND;\r
110         }\r
111         else if (id == Id_arguments) {\r
112             // Should after delete Function.arguments its activation still\r
113             // be available during Function call? \r
114             // This code assumes it should not: after default set/deleteIdValue\r
115             // hasIdValue/getIdValue would not be called again\r
116             // To handle the opposite case, set/deleteIdValue should be \r
117             // overwritten as well\r
118             return null != getActivation(Context.getContext());\r
119         }\r
120         return super.hasIdValue(id);\r
121     }\r
122     \r
123     protected Object getIdValue(int id) {\r
124         switch (id) {\r
125             case Id_length:    return wrap_int(getLength());\r
126             case Id_arity:     return wrap_int(getArity());\r
127             case Id_name:      return getFunctionName();\r
128             case Id_prototype: return getPrototypeProperty();\r
129             case Id_arguments: return getArguments();\r
130         }\r
131         return super.getIdValue(id);\r
132     }\r
133     \r
134     protected void setIdValue(int id, Object value) {\r
135         if (id == Id_prototype) {\r
136             prototypeProperty = (value != null) ? value : NULL_TAG;\r
137             return;\r
138         }\r
139         super.setIdValue(id, value);\r
140     }\r
141 \r
142     protected void deleteIdValue(int id) {\r
143         if (id == Id_prototype) {\r
144             prototypeProperty = NOT_FOUND;\r
145             return;\r
146         }\r
147         super.deleteIdValue(id);\r
148     }\r
149 \r
150     public int methodArity(int methodId) {\r
151         if (prototypeFlag) {\r
152             switch (methodId) {\r
153                 case Id_constructor:\r
154                 case Id_toString:\r
155                 case Id_apply:\r
156                 case Id_call:\r
157                     return 1;\r
158             }\r
159         }\r
160         return super.methodArity(methodId);\r
161     }\r
162 \r
163     public Object execMethod(int methodId, IdFunction f, Context cx,\r
164                              Scriptable scope, Scriptable thisObj, \r
165                              Object[] args)\r
166         throws JavaScriptException\r
167     {\r
168         if (prototypeFlag) {\r
169             switch (methodId) {\r
170                 case Id_constructor:\r
171                     return jsConstructor(cx, scope, args);\r
172 \r
173                 case Id_toString:\r
174                     return jsFunction_toString(cx, thisObj, args);\r
175 \r
176                 case Id_apply:\r
177                     return jsFunction_apply(cx, scope, thisObj, args);\r
178 \r
179                 case Id_call:\r
180                     return jsFunction_call(cx, scope, thisObj, args);\r
181             }\r
182         }\r
183         return super.execMethod(methodId, f, cx, scope, thisObj, args);\r
184     }\r
185 \r
186     /**\r
187      * Make value as DontEnum, DontDelete, ReadOnly\r
188      * prototype property of this Function object \r
189      */\r
190     public void setImmunePrototypeProperty(Object value) {\r
191         prototypeProperty = (value != null) ? value : NULL_TAG;\r
192         prototypePropertyAttrs = DONTENUM | READONLY | PERMANENT;\r
193     }\r
194 \r
195     protected Scriptable getClassPrototype() {\r
196         Object protoVal = getPrototypeProperty();\r
197         if (protoVal == null \r
198                     || !(protoVal instanceof Scriptable)\r
199                     || (protoVal == Undefined.instance))\r
200             protoVal = getClassPrototype(this, "Object");\r
201         return (Scriptable) protoVal;\r
202     }\r
203 \r
204     /**\r
205      * Should be overridden.\r
206      */\r
207     public Object call(Context cx, Scriptable scope, Scriptable thisObj,\r
208                        Object[] args)\r
209         throws JavaScriptException\r
210     {\r
211         return Undefined.instance;\r
212     }\r
213 \r
214     public Scriptable construct(Context cx, Scriptable scope, Object[] args)\r
215         throws JavaScriptException\r
216     {\r
217         Scriptable newInstance = new NativeObject();\r
218 \r
219         newInstance.setPrototype(getClassPrototype());\r
220         newInstance.setParentScope(getParentScope());\r
221 \r
222         Object val = call(cx, scope, newInstance, args);\r
223         if (val instanceof Scriptable && val != Undefined.instance) {\r
224             return (Scriptable) val;\r
225         }\r
226         return newInstance;\r
227     }\r
228 \r
229     /**\r
230      * Decompile the source information associated with this js\r
231      * function/script back into a string.\r
232      *\r
233      * @param cx Current context\r
234      *\r
235      * @param indent How much to indent the decompiled result\r
236      *\r
237      * @param justbody Whether the decompilation should omit the\r
238      * function header and trailing brace.\r
239      */\r
240 \r
241     public String decompile(Context cx, int indent, boolean justbody) {\r
242         StringBuffer sb = new StringBuffer();\r
243         if (!justbody) {\r
244             sb.append("function ");\r
245             sb.append(getFunctionName());\r
246             sb.append("() {\n\t");\r
247         }\r
248         sb.append("[native code, arity=");\r
249         sb.append(getArity());\r
250         sb.append("]\n");\r
251         if (!justbody) {\r
252             sb.append("}\n");\r
253         }\r
254         return sb.toString();\r
255     }\r
256 \r
257     public int getArity() { return 0; }\r
258 \r
259     public int getLength() { return 0; }\r
260 \r
261     public String getFunctionName() {\r
262         if (functionName == null)\r
263             return "";\r
264         if (functionName.equals("anonymous")) {\r
265             Context cx = Context.getCurrentContext();\r
266             if (cx != null && cx.getLanguageVersion() == Context.VERSION_1_2)\r
267                 return "";\r
268         }\r
269         return functionName;\r
270     }\r
271 \r
272     private Object getPrototypeProperty() {\r
273         Object result = prototypeProperty;\r
274         if (result == null) { \r
275             synchronized (this) {\r
276                 result = prototypeProperty;\r
277                 if (result == null) {\r
278                     setupDefaultPrototype();\r
279                     result = prototypeProperty;\r
280                 }\r
281             }\r
282         }\r
283         else if (result == NULL_TAG) { result = null; }\r
284         return result;\r
285     }\r
286 \r
287     private void setupDefaultPrototype() {\r
288         NativeObject obj = new NativeObject();\r
289         final int attr = ScriptableObject.DONTENUM |\r
290                          ScriptableObject.READONLY |\r
291                          ScriptableObject.PERMANENT;\r
292         obj.defineProperty("constructor", this, attr);\r
293         // put the prototype property into the object now, then in the\r
294         // wacky case of a user defining a function Object(), we don't\r
295         // get an infinite loop trying to find the prototype.\r
296         prototypeProperty = obj;\r
297         Scriptable proto = getObjectPrototype(this);            \r
298         if (proto != obj) {\r
299             // not the one we just made, it must remain grounded\r
300             obj.setPrototype(proto);\r
301         }\r
302     }\r
303 \r
304     private Object getArguments() {\r
305         // <Function name>.arguments is deprecated, so we use a slow\r
306         // way of getting it that doesn't add to the invocation cost.\r
307         // TODO: add warning, error based on version\r
308         NativeCall activation = getActivation(Context.getContext());\r
309         return activation == null \r
310                ? null \r
311                : activation.get("arguments", activation);\r
312     }\r
313     \r
314     NativeCall getActivation(Context cx) {\r
315         NativeCall activation = cx.currentActivation;\r
316         while (activation != null) {\r
317             if (activation.getFunctionObject() == this) \r
318                 return activation;\r
319             activation = activation.caller;\r
320         }\r
321         return null;\r
322     }\r
323         \r
324     private static Object jsConstructor(Context cx, Scriptable scope, \r
325                                         Object[] args)\r
326     {\r
327         int arglen = args.length;\r
328         StringBuffer funArgs = new StringBuffer();\r
329 \r
330         /* Collect the arguments into a string.  */\r
331 \r
332         int i;\r
333         for (i = 0; i < arglen - 1; i++) {\r
334             if (i > 0)\r
335                 funArgs.append(',');\r
336             funArgs.append(ScriptRuntime.toString(args[i]));\r
337         }\r
338         String funBody = arglen == 0 ? "" : ScriptRuntime.toString(args[i]);\r
339 \r
340         String source = "function (" + funArgs.toString() + ") {" +\r
341                         funBody + "}";\r
342         int[] linep = { 0 };\r
343         String filename = Context.getSourcePositionFromStack(linep);\r
344         if (filename == null) {\r
345             filename = "<eval'ed string>";\r
346             linep[0] = 1;\r
347         }\r
348         Object securityDomain = cx.getSecurityDomainForStackDepth(4);\r
349         Scriptable global = ScriptableObject.getTopLevelScope(scope);\r
350         \r
351         // Compile the function with opt level of -1 to force interpreter\r
352         // mode.\r
353         int oldOptLevel = cx.getOptimizationLevel();\r
354         cx.setOptimizationLevel(-1);\r
355         NativeFunction fn;\r
356         try {\r
357             fn = (NativeFunction) cx.compileFunction(global, source,\r
358                                                      filename, linep[0], \r
359                                                      securityDomain);\r
360         }\r
361         finally { cx.setOptimizationLevel(oldOptLevel); }\r
362 \r
363         fn.functionName = "anonymous";\r
364         fn.setPrototype(getFunctionPrototype(global));\r
365         fn.setParentScope(global);\r
366 \r
367         return fn;\r
368     }\r
369 \r
370     private static Object jsFunction_toString(Context cx, Scriptable thisObj,\r
371                                               Object[] args)\r
372     {\r
373         int indent = ScriptRuntime.toInt32(args, 0);\r
374         Object val = thisObj.getDefaultValue(ScriptRuntime.FunctionClass);\r
375         if (val instanceof BaseFunction) {\r
376             return ((BaseFunction)val).decompile(cx, indent, false);\r
377         }\r
378         throw NativeGlobal.typeError1("msg.incompat.call", "toString", thisObj);\r
379     }\r
380 \r
381     /**\r
382      * Function.prototype.apply\r
383      *\r
384      * A proposed ECMA extension for round 2.\r
385      */\r
386     private static Object jsFunction_apply(Context cx, Scriptable scope,\r
387                                            Scriptable thisObj, Object[] args)\r
388         throws JavaScriptException\r
389     {\r
390         if (args.length != 2)\r
391             return jsFunction_call(cx, scope, thisObj, args);\r
392         Object val = thisObj.getDefaultValue(ScriptRuntime.FunctionClass);\r
393         Scriptable newThis = args[0] == null\r
394                              ? ScriptableObject.getTopLevelScope(thisObj)\r
395                              : ScriptRuntime.toObject(scope, args[0]);\r
396         Object[] newArgs;\r
397         if (args.length > 1) {\r
398             if ((args[1] instanceof NativeArray) \r
399                     || (args[1] instanceof Arguments))\r
400                 newArgs = cx.getElements((Scriptable) args[1]);\r
401             else\r
402                 throw NativeGlobal.typeError0("msg.arg.isnt.array", thisObj);            \r
403         }\r
404         else\r
405             newArgs = ScriptRuntime.emptyArgs;\r
406         return ScriptRuntime.call(cx, val, newThis, newArgs, newThis);\r
407     }\r
408 \r
409     /**\r
410      * Function.prototype.call\r
411      *\r
412      * A proposed ECMA extension for round 2.\r
413      */\r
414     private static Object jsFunction_call(Context cx, Scriptable scope,\r
415                                           Scriptable thisObj, Object[] args)\r
416         throws JavaScriptException\r
417     {\r
418         Object val = thisObj.getDefaultValue(ScriptRuntime.FunctionClass);\r
419         if (args.length == 0) {\r
420             Scriptable s = ScriptRuntime.toObject(scope, val);\r
421             Scriptable topScope = s.getParentScope();\r
422             return ScriptRuntime.call(cx, val, \r
423                                       topScope, ScriptRuntime.emptyArgs, \r
424                                       topScope);\r
425         } else {\r
426             Scriptable newThis = args[0] == null\r
427                                  ? ScriptableObject.getTopLevelScope(thisObj)\r
428                                  : ScriptRuntime.toObject(scope, args[0]);\r
429 \r
430             Object[] newArgs = new Object[args.length - 1];\r
431             System.arraycopy(args, 1, newArgs, 0, newArgs.length);\r
432             return ScriptRuntime.call(cx, val, newThis, newArgs, newThis);\r
433         }\r
434     }\r
435 \r
436     protected int maxInstanceId() { return MAX_INSTANCE_ID; }\r
437 \r
438     protected String getIdName(int id) {\r
439         switch (id) {\r
440             case Id_length:       return "length";\r
441             case Id_arity:        return "arity";\r
442             case Id_name:         return "name";\r
443             case Id_prototype:    return "prototype";\r
444             case Id_arguments:    return "arguments";\r
445         }\r
446         \r
447         if (prototypeFlag) {\r
448             switch (id) {\r
449                 case Id_constructor:  return "constructor";\r
450                 case Id_toString:     return "toString";\r
451                 case Id_apply:        return "apply";\r
452                 case Id_call:         return "call";\r
453             }\r
454         }\r
455         return null;\r
456     }\r
457 \r
458 // #string_id_map#\r
459 \r
460     private static final int\r
461         Id_length       = 1,\r
462         Id_arity        = 2,\r
463         Id_name         = 3,\r
464         Id_prototype    = 4,\r
465         Id_arguments    = 5,\r
466         \r
467         MAX_INSTANCE_ID = 5;\r
468 \r
469     protected int mapNameToId(String s) {\r
470         int id;\r
471 // #generated# Last update: 2001-05-20 00:12:12 GMT+02:00\r
472         L0: { id = 0; String X = null; int c;\r
473             L: switch (s.length()) {\r
474             case 4: X="name";id=Id_name; break L;\r
475             case 5: X="arity";id=Id_arity; break L;\r
476             case 6: X="length";id=Id_length; break L;\r
477             case 9: c=s.charAt(0);\r
478                 if (c=='a') { X="arguments";id=Id_arguments; }\r
479                 else if (c=='p') { X="prototype";id=Id_prototype; }\r
480                 break L;\r
481             }\r
482             if (X!=null && X!=s && !X.equals(s)) id = 0;\r
483         }\r
484 // #/generated#\r
485 // #/string_id_map#\r
486 \r
487         if (id != 0 || !prototypeFlag) { return id; }\r
488 \r
489 // #string_id_map#\r
490 // #generated# Last update: 2001-05-20 00:12:12 GMT+02:00\r
491         L0: { id = 0; String X = null;\r
492             L: switch (s.length()) {\r
493             case 4: X="call";id=Id_call; break L;\r
494             case 5: X="apply";id=Id_apply; break L;\r
495             case 8: X="toString";id=Id_toString; break L;\r
496             case 11: X="constructor";id=Id_constructor; break L;\r
497             }\r
498             if (X!=null && X!=s && !X.equals(s)) id = 0;\r
499         }\r
500 // #/generated#\r
501         return id;\r
502     }\r
503 \r
504     private static final int\r
505         Id_constructor    = MAX_INSTANCE_ID + 1,\r
506         Id_toString       = MAX_INSTANCE_ID + 2,\r
507         Id_apply          = MAX_INSTANCE_ID + 3,\r
508         Id_call           = MAX_INSTANCE_ID + 4,\r
509         \r
510         MAX_PROTOTYPE_ID  = MAX_INSTANCE_ID + 4;\r
511 \r
512 // #/string_id_map#\r
513         \r
514     protected String functionName;\r
515 \r
516     private Object prototypeProperty; \r
517     private int prototypePropertyAttrs = DONTENUM;\r
518 \r
519     private boolean prototypeFlag;\r
520 }\r
521 \r