1 /* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
\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
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
13 * The Original Code is Rhino code, released
\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
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
39 package org.mozilla.javascript;
\r
41 import java.util.Hashtable;
\r
44 * This class implements the Function native object.
\r
46 * @author Norris Boyd
\r
48 public class NativeFunction extends BaseFunction {
\r
50 private boolean nextIs(int i, int token) {
\r
51 if (i + 1 < source.length())
\r
52 return source.charAt(i + 1) == token;
\r
56 // how much to indent
\r
57 private final static int OFFSET = 4;
\r
59 // less how much for case labels
\r
60 private final static int SETBACK = 2;
\r
62 // whether to do a debug print of the source information, when
\r
64 private static final boolean printSource = false;
\r
67 * Decompile the source information associated with this js
\r
68 * function/script back into a string. For the most part, this
\r
69 * just means translating tokens back to their string
\r
70 * representations; there's a little bit of lookahead logic to
\r
71 * decide the proper spacing/indentation. Most of the work in
\r
72 * mapping the original source to the prettyprinted decompiled
\r
73 * version is done by the parser.
\r
75 * Note that support for Context.decompileFunctionBody is hacked
\r
76 * on through special cases; I suspect that js makes a distinction
\r
77 * between function header and function body that rhino
\r
78 * decompilation does not.
\r
80 * @param cx Current context
\r
82 * @param indent How much to indent the decompiled result
\r
84 * @param justbody Whether the decompilation should omit the
\r
85 * function header and trailing brace.
\r
88 public String decompile(Context cx, int indent, boolean justbody) {
\r
89 StringBuffer result = new StringBuffer();
\r
90 decompile(indent, true, justbody, result);
\r
91 return result.toString();
\r
95 private void decompile(int indent, boolean toplevel, boolean justbody,
\r
96 StringBuffer result)
\r
98 if (source == null) {
\r
100 result.append("function ");
\r
101 result.append(getFunctionName());
\r
102 result.append("() {\n\t");
\r
104 result.append("[native code]\n");
\r
106 result.append("}\n");
\r
111 // Spew tokens in source, for debugging.
\r
112 // as TYPE number char
\r
114 System.err.println("length:" + source.length());
\r
115 for (int i = 0; i < source.length(); i++) {
\r
116 // Note that tokenToName will fail unless Context.printTrees
\r
118 String tokenname = TokenStream.tokenToName(source.charAt(i));
\r
119 if (tokenname == null)
\r
121 String pad = tokenname.length() > 7
\r
126 + pad + (int)source.charAt(i)
\r
127 + "\t'" + ScriptRuntime.escapeString
\r
128 (source.substring(i, i+1))
\r
131 System.err.println();
\r
136 if (source.length() > 0) {
\r
137 /* special-case FUNCTION as the first token; if it is,
\r
138 * (and it's not followed by a NAME or LP) then we're
\r
139 * decompiling a function (and not the toplevel script.)
\r
141 * FUNCTION appearing elsewhere is an escape that means we'll
\r
142 * need to call toString of the given function (object).
\r
144 * If not at the top level, don't add an initial indent;
\r
145 * let the caller do it, so functions as expressions look
\r
149 // add an initial newline to exactly match js.
\r
151 result.append('\n');
\r
152 for (int j = 0; j < indent; j++)
\r
153 result.append(' ');
\r
156 if (source.charAt(0) == TokenStream.FUNCTION
\r
157 // make sure it's not a script that begins with a
\r
158 // reference to a function definition.
\r
159 && source.length() > 1
\r
160 && (source.charAt(1) == TokenStream.NAME
\r
161 || source.charAt(1) == TokenStream.LP))
\r
164 result.append("function ");
\r
166 /* version != 1.2 Function constructor behavior - if
\r
167 * there's no function name in the source info, and
\r
168 * the names[0] entry is the empty string, then it must
\r
169 * have been created by the Function constructor;
\r
170 * print 'anonymous' as the function name if the
\r
171 * version (under which the function was compiled) is
\r
172 * less than 1.2... or if it's greater than 1.2, because
\r
173 * we need to be closer to ECMA. (ToSource, please?)
\r
175 if (nextIs(i, TokenStream.LP)
\r
176 && this.version != Context.VERSION_1_2
\r
177 && this.functionName != null
\r
178 && this.functionName.equals("anonymous"))
\r
179 result.append("anonymous");
\r
182 /* Skip past the entire function header to the next EOL.
\r
183 * Depends on how NAMEs are encoded.
\r
185 while (i < source.length()
\r
186 && (source.charAt(i) != TokenStream.EOL
\r
187 // the length char of a NAME sequence
\r
188 // can look like an EOL.
\r
190 && source.charAt(i-1) == TokenStream.NAME)))
\r
194 // Skip past the EOL, too.
\r
200 while (i < source.length()) {
\r
202 switch(source.charAt(i)) {
\r
203 case TokenStream.NAME:
\r
204 case TokenStream.OBJECT: // re-wrapped in '/'s in parser...
\r
205 /* NAMEs are encoded as NAME, (char) length, string...
\r
206 * Note that lookahead for detecting labels depends on
\r
207 * this encoding; change there if this changes.
\r
209 * Also change function-header skipping code above,
\r
210 * used when decompling under decompileFunctionBody.
\r
213 stop = i + (int)source.charAt(i);
\r
214 result.append(source.substring(i + 1, stop + 1));
\r
218 case TokenStream.NUMBER:
\r
221 switch(source.charAt(i)) {
\r
224 result.append((int)source.charAt(i));
\r
229 lbits |= (long)source.charAt(i++) << 48;
\r
230 lbits |= (long)source.charAt(i++) << 32;
\r
231 lbits |= (long)source.charAt(i++) << 16;
\r
232 lbits |= (long)source.charAt(i);
\r
234 result.append(lbits);
\r
239 lbits |= (long)source.charAt(i++) << 48;
\r
240 lbits |= (long)source.charAt(i++) << 32;
\r
241 lbits |= (long)source.charAt(i++) << 16;
\r
242 lbits |= (long)source.charAt(i);
\r
244 double dval = Double.longBitsToDouble(lbits);
\r
245 result.append(ScriptRuntime.numberToString(dval, 10));
\r
250 case TokenStream.STRING:
\r
252 stop = i + (int)source.charAt(i);
\r
253 result.append('"');
\r
254 result.append(ScriptRuntime.escapeString
\r
255 (source.substring(i + 1, stop + 1)));
\r
256 result.append('"');
\r
260 case TokenStream.PRIMARY:
\r
262 switch(source.charAt(i)) {
\r
263 case TokenStream.TRUE:
\r
264 result.append("true");
\r
267 case TokenStream.FALSE:
\r
268 result.append("false");
\r
271 case TokenStream.NULL:
\r
272 result.append("null");
\r
275 case TokenStream.THIS:
\r
276 result.append("this");
\r
279 case TokenStream.TYPEOF:
\r
280 result.append("typeof");
\r
283 case TokenStream.VOID:
\r
284 result.append("void");
\r
287 case TokenStream.UNDEFINED:
\r
288 result.append("undefined");
\r
293 case TokenStream.FUNCTION: {
\r
294 /* decompile a FUNCTION token as an escape; call
\r
295 * toString on the nth enclosed nested function,
\r
296 * where n is given by the byte that follows.
\r
300 int functionNumber = source.charAt(i);
\r
301 if (nestedFunctions == null
\r
302 || functionNumber > nestedFunctions.length)
\r
305 if (functionName != null && functionName.length() > 0) {
\r
306 message = Context.getMessage2
\r
307 ("msg.no.function.ref.found.in",
\r
308 new Integer((int)source.charAt(i)), functionName);
\r
310 message = Context.getMessage1
\r
311 ("msg.no.function.ref.found",
\r
312 new Integer((int)source.charAt(i)));
\r
314 throw Context.reportRuntimeError(message);
\r
316 nestedFunctions[functionNumber].
\r
317 decompile(indent, false, false, result);
\r
320 case TokenStream.COMMA:
\r
321 result.append(", ");
\r
324 case TokenStream.LC:
\r
325 if (nextIs(i, TokenStream.EOL))
\r
327 result.append('{');
\r
330 case TokenStream.RC:
\r
331 /* don't print the closing RC if it closes the
\r
332 * toplevel function and we're called from
\r
333 * decompileFunctionBody.
\r
335 if (justbody && toplevel && i + 1 == source.length())
\r
338 if (nextIs(i, TokenStream.EOL))
\r
340 if (nextIs(i, TokenStream.WHILE)
\r
341 || nextIs(i, TokenStream.ELSE)) {
\r
343 result.append("} ");
\r
346 result.append('}');
\r
349 case TokenStream.LP:
\r
350 result.append('(');
\r
353 case TokenStream.RP:
\r
354 if (nextIs(i, TokenStream.LC))
\r
355 result.append(") ");
\r
357 result.append(')');
\r
360 case TokenStream.LB:
\r
361 result.append('[');
\r
364 case TokenStream.RB:
\r
365 result.append(']');
\r
368 case TokenStream.EOL:
\r
369 result.append('\n');
\r
371 /* add indent if any tokens remain,
\r
372 * less setback if next token is
\r
373 * a label, case or default.
\r
375 if (i + 1 < source.length()) {
\r
377 if (nextIs(i, TokenStream.CASE)
\r
378 || nextIs(i, TokenStream.DEFAULT))
\r
380 else if (nextIs(i, TokenStream.RC))
\r
383 /* elaborate check against label... skip past a
\r
384 * following inlined NAME and look for a COLON.
\r
385 * Depends on how NAME is encoded.
\r
387 else if (nextIs(i, TokenStream.NAME)) {
\r
388 int skip = source.charAt(i + 2);
\r
389 if (source.charAt(i + skip + 3) == TokenStream.COLON)
\r
393 for (; less < indent; less++)
\r
394 result.append(' ');
\r
398 case TokenStream.DOT:
\r
399 result.append('.');
\r
402 case TokenStream.NEW:
\r
403 result.append("new ");
\r
406 case TokenStream.DELPROP:
\r
407 result.append("delete ");
\r
410 case TokenStream.IF:
\r
411 result.append("if ");
\r
414 case TokenStream.ELSE:
\r
415 result.append("else ");
\r
418 case TokenStream.FOR:
\r
419 result.append("for ");
\r
422 case TokenStream.IN:
\r
423 result.append(" in ");
\r
426 case TokenStream.WITH:
\r
427 result.append("with ");
\r
430 case TokenStream.WHILE:
\r
431 result.append("while ");
\r
434 case TokenStream.DO:
\r
435 result.append("do ");
\r
438 case TokenStream.TRY:
\r
439 result.append("try ");
\r
442 case TokenStream.CATCH:
\r
443 result.append("catch ");
\r
446 case TokenStream.FINALLY:
\r
447 result.append("finally ");
\r
450 case TokenStream.THROW:
\r
451 result.append("throw ");
\r
454 case TokenStream.SWITCH:
\r
455 result.append("switch ");
\r
458 case TokenStream.BREAK:
\r
459 if (nextIs(i, TokenStream.NAME))
\r
460 result.append("break ");
\r
462 result.append("break");
\r
465 case TokenStream.CONTINUE:
\r
466 if (nextIs(i, TokenStream.NAME))
\r
467 result.append("continue ");
\r
469 result.append("continue");
\r
472 case TokenStream.CASE:
\r
473 result.append("case ");
\r
476 case TokenStream.DEFAULT:
\r
477 result.append("default");
\r
480 case TokenStream.RETURN:
\r
481 if (nextIs(i, TokenStream.SEMI))
\r
482 result.append("return");
\r
484 result.append("return ");
\r
487 case TokenStream.VAR:
\r
488 result.append("var ");
\r
491 case TokenStream.SEMI:
\r
492 if (nextIs(i, TokenStream.EOL))
\r
493 // statement termination
\r
494 result.append(';');
\r
496 // separators in FOR
\r
497 result.append("; ");
\r
500 case TokenStream.ASSIGN:
\r
502 switch(source.charAt(i)) {
\r
503 case TokenStream.NOP:
\r
504 result.append(" = ");
\r
507 case TokenStream.ADD:
\r
508 result.append(" += ");
\r
511 case TokenStream.SUB:
\r
512 result.append(" -= ");
\r
515 case TokenStream.MUL:
\r
516 result.append(" *= ");
\r
519 case TokenStream.DIV:
\r
520 result.append(" /= ");
\r
523 case TokenStream.MOD:
\r
524 result.append(" %= ");
\r
527 case TokenStream.BITOR:
\r
528 result.append(" |= ");
\r
531 case TokenStream.BITXOR:
\r
532 result.append(" ^= ");
\r
535 case TokenStream.BITAND:
\r
536 result.append(" &= ");
\r
539 case TokenStream.LSH:
\r
540 result.append(" <<= ");
\r
543 case TokenStream.RSH:
\r
544 result.append(" >>= ");
\r
547 case TokenStream.URSH:
\r
548 result.append(" >>>= ");
\r
553 case TokenStream.HOOK:
\r
554 result.append(" ? ");
\r
557 case TokenStream.OBJLIT:
\r
558 // pun OBJLIT to mean colon in objlit property initialization.
\r
559 // this needs to be distinct from COLON in the general case
\r
560 // to distinguish from the colon in a ternary... which needs
\r
561 // different spacing.
\r
562 result.append(':');
\r
565 case TokenStream.COLON:
\r
566 if (nextIs(i, TokenStream.EOL))
\r
567 // it's the end of a label
\r
568 result.append(':');
\r
570 // it's the middle part of a ternary
\r
571 result.append(" : ");
\r
574 case TokenStream.OR:
\r
575 result.append(" || ");
\r
578 case TokenStream.AND:
\r
579 result.append(" && ");
\r
582 case TokenStream.BITOR:
\r
583 result.append(" | ");
\r
586 case TokenStream.BITXOR:
\r
587 result.append(" ^ ");
\r
590 case TokenStream.BITAND:
\r
591 result.append(" & ");
\r
594 case TokenStream.EQOP:
\r
596 switch(source.charAt(i)) {
\r
597 case TokenStream.SHEQ:
\r
599 * Emulate the C engine; if we're under version
\r
600 * 1.2, then the == operator behaves like the ===
\r
601 * operator (and the source is generated by
\r
602 * decompiling a === opcode), so print the ===
\r
605 result.append(this.version == Context.VERSION_1_2 ? " == "
\r
609 case TokenStream.SHNE:
\r
610 result.append(this.version == Context.VERSION_1_2 ? " != "
\r
614 case TokenStream.EQ:
\r
615 result.append(" == ");
\r
618 case TokenStream.NE:
\r
619 result.append(" != ");
\r
624 case TokenStream.RELOP:
\r
626 switch(source.charAt(i)) {
\r
627 case TokenStream.LE:
\r
628 result.append(" <= ");
\r
631 case TokenStream.LT:
\r
632 result.append(" < ");
\r
635 case TokenStream.GE:
\r
636 result.append(" >= ");
\r
639 case TokenStream.GT:
\r
640 result.append(" > ");
\r
643 case TokenStream.INSTANCEOF:
\r
644 result.append(" instanceof ");
\r
649 case TokenStream.SHOP:
\r
651 switch(source.charAt(i)) {
\r
652 case TokenStream.LSH:
\r
653 result.append(" << ");
\r
656 case TokenStream.RSH:
\r
657 result.append(" >> ");
\r
660 case TokenStream.URSH:
\r
661 result.append(" >>> ");
\r
666 case TokenStream.UNARYOP:
\r
668 switch(source.charAt(i)) {
\r
669 case TokenStream.TYPEOF:
\r
670 result.append("typeof ");
\r
673 case TokenStream.VOID:
\r
674 result.append("void ");
\r
677 case TokenStream.NOT:
\r
678 result.append('!');
\r
681 case TokenStream.BITNOT:
\r
682 result.append('~');
\r
685 case TokenStream.ADD:
\r
686 result.append('+');
\r
689 case TokenStream.SUB:
\r
690 result.append('-');
\r
695 case TokenStream.INC:
\r
696 result.append("++");
\r
699 case TokenStream.DEC:
\r
700 result.append("--");
\r
703 case TokenStream.ADD:
\r
704 result.append(" + ");
\r
707 case TokenStream.SUB:
\r
708 result.append(" - ");
\r
711 case TokenStream.MUL:
\r
712 result.append(" * ");
\r
715 case TokenStream.DIV:
\r
716 result.append(" / ");
\r
719 case TokenStream.MOD:
\r
720 result.append(" % ");
\r
724 // If we don't know how to decompile it, raise an exception.
\r
725 throw new RuntimeException("Unknown token " +
\r
731 // add that trailing newline if it's an outermost function.
\r
732 if (toplevel && !justbody)
\r
733 result.append('\n');
\r
736 public int getLength() {
\r
737 Context cx = Context.getContext();
\r
738 if (cx != null && cx.getLanguageVersion() != Context.VERSION_1_2)
\r
740 NativeCall activation = getActivation(cx);
\r
741 if (activation == null)
\r
743 return activation.getOriginalArguments().length;
\r
746 public int getArity() {
\r
750 public String getFunctionName() {
\r
751 if (functionName == null)
\r
753 if (functionName.equals("anonymous")) {
\r
754 Context cx = Context.getCurrentContext();
\r
755 if (cx != null && cx.getLanguageVersion() == Context.VERSION_1_2)
\r
758 return functionName;
\r
762 * For backwards compatibility keep an old method name used by
\r
763 * Batik and possibly others.
\r
765 public String jsGet_name() {
\r
766 return getFunctionName();
\r
770 * The "argsNames" array has the following information:
\r
771 * argNames[0] through argNames[argCount - 1]: the names of the parameters
\r
772 * argNames[argCount] through argNames[args.length-1]: the names of the
\r
773 * variables declared in var statements
\r
775 protected String[] argNames;
\r
776 protected short argCount;
\r
777 protected short version;
\r
780 * An encoded representation of the function source, for
\r
781 * decompiling. Needs to be visible (only) to generated
\r
782 * subclasses of NativeFunction.
\r
784 protected String source;
\r
787 * An array of NativeFunction values for each nested function.
\r
788 * Used internally, and also for decompiling nested functions.
\r
790 public NativeFunction[] nestedFunctions;
\r
792 // For all generated subclass objects debug_level is set to 0 or higher.
\r
793 // So, if debug_level remains -1 in some object, then that object is
\r
794 // known to have not been generated.
\r
795 public int debug_level = -1;
\r
796 public String debug_srcName;
\r