From 6242c991f365dbd67eba62ecfa5df769a83fcbc6 Mon Sep 17 00:00:00 2001 From: megacz Date: Fri, 30 Jan 2004 06:45:07 +0000 Subject: [PATCH 1/1] 2002/03/21 01:19:33 darcs-hash:20040130064507-2ba56-7d21fb55b62acf96e73e8e49eb17fa8f14b05b87.gz --- src/org/mozilla/javascript/ErrorReporter.java | 103 + src/org/mozilla/javascript/EvaluatorException.java | 56 + src/org/mozilla/javascript/FlattenedObject.java | 341 +++ src/org/mozilla/javascript/Function.java | 86 + src/org/mozilla/javascript/FunctionNode.java | 103 + src/org/mozilla/javascript/FunctionObject.java | 626 +++++ src/org/mozilla/javascript/IRFactory.java | 1065 +++++++++ src/org/mozilla/javascript/IdFunction.java | 164 ++ src/org/mozilla/javascript/IdFunctionMaster.java | 54 + src/org/mozilla/javascript/IdScriptable.java | 577 +++++ src/org/mozilla/javascript/ImporterTopLevel.java | 176 ++ .../mozilla/javascript/InterpretedFunction.java | 135 ++ src/org/mozilla/javascript/InterpretedScript.java | 102 + src/org/mozilla/javascript/Interpreter.java | 2514 ++++++++++++++++++++ src/org/mozilla/javascript/InterpreterData.java | 130 + src/org/mozilla/javascript/InterpreterFrame.java | 77 + src/org/mozilla/javascript/Invoker.java | 54 + src/org/mozilla/javascript/JavaMembers.java | 591 +++++ .../mozilla/javascript/JavaScriptException.java | 114 + src/org/mozilla/javascript/Label.java | 106 + src/org/mozilla/javascript/LabelTable.java | 80 + src/org/mozilla/javascript/LazilyLoadedCtor.java | 133 ++ src/org/mozilla/javascript/LineBuffer.java | 412 ++++ src/org/mozilla/javascript/ListenerArray.java | 131 + src/org/mozilla/javascript/LocalVariable.java | 79 + src/org/mozilla/javascript/NativeArray.java | 1147 +++++++++ src/org/mozilla/javascript/NativeBoolean.java | 168 ++ src/org/mozilla/javascript/NativeCall.java | 171 ++ src/org/mozilla/javascript/NativeDate.java | 1707 +++++++++++++ src/org/mozilla/javascript/NativeError.java | 204 ++ src/org/mozilla/javascript/NativeFunction.java | 798 +++++++ src/org/mozilla/javascript/NativeGlobal.java | 843 +++++++ src/org/mozilla/javascript/NativeJavaArray.java | 155 ++ src/org/mozilla/javascript/NativeJavaClass.java | 275 +++ .../mozilla/javascript/NativeJavaConstructor.java | 86 + src/org/mozilla/javascript/NativeJavaMethod.java | 517 ++++ src/org/mozilla/javascript/NativeJavaObject.java | 926 +++++++ src/org/mozilla/javascript/NativeJavaPackage.java | 240 ++ src/org/mozilla/javascript/NativeMath.java | 405 ++++ src/org/mozilla/javascript/NativeNumber.java | 280 +++ src/org/mozilla/javascript/NativeObject.java | 291 +++ src/org/mozilla/javascript/NativeScript.java | 286 +++ src/org/mozilla/javascript/NativeString.java | 950 ++++++++ src/org/mozilla/javascript/NativeWith.java | 195 ++ src/org/mozilla/javascript/Node.java | 489 ++++ src/org/mozilla/javascript/NodeTransformer.java | 743 ++++++ .../mozilla/javascript/NotAFunctionException.java | 52 + src/org/mozilla/javascript/Parser.java | 1516 ++++++++++++ .../mozilla/javascript/PreorderNodeIterator.java | 92 + src/org/mozilla/javascript/PropertyException.java | 65 + src/org/mozilla/javascript/RegExpProxy.java | 68 + src/org/mozilla/javascript/Script.java | 73 + src/org/mozilla/javascript/ScriptRuntime.java | 2168 +++++++++++++++++ src/org/mozilla/javascript/Scriptable.java | 339 +++ src/org/mozilla/javascript/ScriptableObject.java | 1807 ++++++++++++++ src/org/mozilla/javascript/SecuritySupport.java | 148 ++ .../mozilla/javascript/ShallowNodeIterator.java | 69 + src/org/mozilla/javascript/SourceTextItem.java | 222 ++ src/org/mozilla/javascript/SourceTextManager.java | 82 + src/org/mozilla/javascript/Synchronizer.java | 77 + src/org/mozilla/javascript/TokenStream.java | 1432 +++++++++++ src/org/mozilla/javascript/UintMap.java | 468 ++++ src/org/mozilla/javascript/Undefined.java | 138 ++ src/org/mozilla/javascript/VariableTable.java | 158 ++ src/org/mozilla/javascript/WrapHandler.java | 68 + src/org/mozilla/javascript/WrappedException.java | 115 + src/org/mozilla/javascript/Wrapper.java | 55 + src/org/mozilla/javascript/debug/DebugFrame.java | 51 + src/org/mozilla/javascript/debug/DebugReader.java | 101 + .../mozilla/javascript/debug/DebuggableEngine.java | 92 + .../mozilla/javascript/debug/DebuggableScript.java | 84 + src/org/mozilla/javascript/debug/Debugger.java | 51 + .../mozilla/javascript/regexp/NativeRegExp.java | 2502 +++++++++++++++++++ .../javascript/regexp/NativeRegExpCtor.java | 271 +++ src/org/mozilla/javascript/regexp/RegExpImpl.java | 567 +++++ src/org/mozilla/javascript/regexp/SubString.java | 62 + src/org/xwt/Box.java | 1454 +++++++++++ src/org/xwt/DoubleBuffer.java | 43 + src/org/xwt/GIF.java | 443 ++++ src/org/xwt/ImageDecoder.java | 17 + src/org/xwt/Main.java | 1935 +++++++++++++++ src/org/xwt/Message.java | 11 + src/org/xwt/MessageQueue.java | 146 ++ src/org/xwt/PNG.java | 695 ++++++ src/org/xwt/Picture.java | 20 + src/org/xwt/Platform.java | 308 +++ src/org/xwt/Resources.java | 194 ++ src/org/xwt/SOAP.java | 262 ++ src/org/xwt/SpecialBoxProperty.java | 671 ++++++ src/org/xwt/Static.java | 57 + src/org/xwt/Surface.java | 580 +++++ src/org/xwt/Template.java | 745 ++++++ src/org/xwt/ThreadMessage.java | 96 + src/org/xwt/TinySSL.java | 1549 ++++++++++++ src/org/xwt/Trap.java | 252 ++ src/org/xwt/Weak.java | 9 + src/org/xwt/XML.java | 973 ++++++++ src/org/xwt/XMLRPC.java | 472 ++++ src/org/xwt/XWF.java | 222 ++ src/org/xwt/XWT.java | 204 ++ src/org/xwt/package.html | 40 + src/org/xwt/plat/AWT.java | 359 +++ src/org/xwt/plat/GCJ.java | 24 + src/org/xwt/plat/GCJ.xml | 72 + src/org/xwt/plat/Java2.java | 178 ++ src/org/xwt/plat/Java2.xml | 33 + src/org/xwt/plat/Win32.cc | 1117 +++++++++ src/org/xwt/plat/Win32.def | 8 + src/org/xwt/plat/Win32.inf | 22 + src/org/xwt/plat/Win32.java | 253 ++ src/org/xwt/plat/Win32.xml | 48 + src/org/xwt/plat/package.html | 41 + 112 files changed, 45431 insertions(+) create mode 100644 src/org/mozilla/javascript/ErrorReporter.java create mode 100644 src/org/mozilla/javascript/EvaluatorException.java create mode 100644 src/org/mozilla/javascript/FlattenedObject.java create mode 100644 src/org/mozilla/javascript/Function.java create mode 100644 src/org/mozilla/javascript/FunctionNode.java create mode 100644 src/org/mozilla/javascript/FunctionObject.java create mode 100644 src/org/mozilla/javascript/IRFactory.java create mode 100644 src/org/mozilla/javascript/IdFunction.java create mode 100644 src/org/mozilla/javascript/IdFunctionMaster.java create mode 100644 src/org/mozilla/javascript/IdScriptable.java create mode 100644 src/org/mozilla/javascript/ImporterTopLevel.java create mode 100644 src/org/mozilla/javascript/InterpretedFunction.java create mode 100644 src/org/mozilla/javascript/InterpretedScript.java create mode 100644 src/org/mozilla/javascript/Interpreter.java create mode 100644 src/org/mozilla/javascript/InterpreterData.java create mode 100644 src/org/mozilla/javascript/InterpreterFrame.java create mode 100644 src/org/mozilla/javascript/Invoker.java create mode 100644 src/org/mozilla/javascript/JavaMembers.java create mode 100644 src/org/mozilla/javascript/JavaScriptException.java create mode 100644 src/org/mozilla/javascript/Label.java create mode 100644 src/org/mozilla/javascript/LabelTable.java create mode 100644 src/org/mozilla/javascript/LazilyLoadedCtor.java create mode 100644 src/org/mozilla/javascript/LineBuffer.java create mode 100644 src/org/mozilla/javascript/ListenerArray.java create mode 100644 src/org/mozilla/javascript/LocalVariable.java create mode 100644 src/org/mozilla/javascript/NativeArray.java create mode 100644 src/org/mozilla/javascript/NativeBoolean.java create mode 100644 src/org/mozilla/javascript/NativeCall.java create mode 100644 src/org/mozilla/javascript/NativeDate.java create mode 100644 src/org/mozilla/javascript/NativeError.java create mode 100644 src/org/mozilla/javascript/NativeFunction.java create mode 100644 src/org/mozilla/javascript/NativeGlobal.java create mode 100644 src/org/mozilla/javascript/NativeJavaArray.java create mode 100644 src/org/mozilla/javascript/NativeJavaClass.java create mode 100644 src/org/mozilla/javascript/NativeJavaConstructor.java create mode 100644 src/org/mozilla/javascript/NativeJavaMethod.java create mode 100644 src/org/mozilla/javascript/NativeJavaObject.java create mode 100644 src/org/mozilla/javascript/NativeJavaPackage.java create mode 100644 src/org/mozilla/javascript/NativeMath.java create mode 100644 src/org/mozilla/javascript/NativeNumber.java create mode 100644 src/org/mozilla/javascript/NativeObject.java create mode 100644 src/org/mozilla/javascript/NativeScript.java create mode 100644 src/org/mozilla/javascript/NativeString.java create mode 100644 src/org/mozilla/javascript/NativeWith.java create mode 100644 src/org/mozilla/javascript/Node.java create mode 100644 src/org/mozilla/javascript/NodeTransformer.java create mode 100644 src/org/mozilla/javascript/NotAFunctionException.java create mode 100644 src/org/mozilla/javascript/Parser.java create mode 100644 src/org/mozilla/javascript/PreorderNodeIterator.java create mode 100644 src/org/mozilla/javascript/PropertyException.java create mode 100644 src/org/mozilla/javascript/RegExpProxy.java create mode 100644 src/org/mozilla/javascript/Script.java create mode 100644 src/org/mozilla/javascript/ScriptRuntime.java create mode 100644 src/org/mozilla/javascript/Scriptable.java create mode 100644 src/org/mozilla/javascript/ScriptableObject.java create mode 100644 src/org/mozilla/javascript/SecuritySupport.java create mode 100644 src/org/mozilla/javascript/ShallowNodeIterator.java create mode 100644 src/org/mozilla/javascript/SourceTextItem.java create mode 100644 src/org/mozilla/javascript/SourceTextManager.java create mode 100644 src/org/mozilla/javascript/Synchronizer.java create mode 100644 src/org/mozilla/javascript/TokenStream.java create mode 100644 src/org/mozilla/javascript/UintMap.java create mode 100644 src/org/mozilla/javascript/Undefined.java create mode 100644 src/org/mozilla/javascript/VariableTable.java create mode 100644 src/org/mozilla/javascript/WrapHandler.java create mode 100644 src/org/mozilla/javascript/WrappedException.java create mode 100644 src/org/mozilla/javascript/Wrapper.java create mode 100644 src/org/mozilla/javascript/debug/DebugFrame.java create mode 100644 src/org/mozilla/javascript/debug/DebugReader.java create mode 100644 src/org/mozilla/javascript/debug/DebuggableEngine.java create mode 100644 src/org/mozilla/javascript/debug/DebuggableScript.java create mode 100644 src/org/mozilla/javascript/debug/Debugger.java create mode 100644 src/org/mozilla/javascript/regexp/NativeRegExp.java create mode 100644 src/org/mozilla/javascript/regexp/NativeRegExpCtor.java create mode 100644 src/org/mozilla/javascript/regexp/RegExpImpl.java create mode 100644 src/org/mozilla/javascript/regexp/SubString.java create mode 100644 src/org/xwt/Box.java create mode 100644 src/org/xwt/DoubleBuffer.java create mode 100644 src/org/xwt/GIF.java create mode 100644 src/org/xwt/ImageDecoder.java create mode 100644 src/org/xwt/Main.java create mode 100644 src/org/xwt/Message.java create mode 100644 src/org/xwt/MessageQueue.java create mode 100644 src/org/xwt/PNG.java create mode 100644 src/org/xwt/Picture.java create mode 100644 src/org/xwt/Platform.java create mode 100644 src/org/xwt/Resources.java create mode 100644 src/org/xwt/SOAP.java create mode 100644 src/org/xwt/SpecialBoxProperty.java create mode 100644 src/org/xwt/Static.java create mode 100644 src/org/xwt/Surface.java create mode 100644 src/org/xwt/Template.java create mode 100644 src/org/xwt/ThreadMessage.java create mode 100644 src/org/xwt/TinySSL.java create mode 100644 src/org/xwt/Trap.java create mode 100644 src/org/xwt/Weak.java create mode 100644 src/org/xwt/XML.java create mode 100644 src/org/xwt/XMLRPC.java create mode 100644 src/org/xwt/XWF.java create mode 100644 src/org/xwt/XWT.java create mode 100644 src/org/xwt/package.html create mode 100644 src/org/xwt/plat/AWT.java create mode 100644 src/org/xwt/plat/GCJ.java create mode 100644 src/org/xwt/plat/GCJ.xml create mode 100644 src/org/xwt/plat/Java2.java create mode 100644 src/org/xwt/plat/Java2.xml create mode 100644 src/org/xwt/plat/Win32.cc create mode 100644 src/org/xwt/plat/Win32.def create mode 100644 src/org/xwt/plat/Win32.inf create mode 100644 src/org/xwt/plat/Win32.java create mode 100644 src/org/xwt/plat/Win32.xml create mode 100644 src/org/xwt/plat/package.html diff --git a/src/org/mozilla/javascript/ErrorReporter.java b/src/org/mozilla/javascript/ErrorReporter.java new file mode 100644 index 0000000..b945e7e --- /dev/null +++ b/src/org/mozilla/javascript/ErrorReporter.java @@ -0,0 +1,103 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * The contents of this file are subject to the Netscape Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express oqr + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is Rhino code, released + * May 6, 1999. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1997-1999 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (the "GPL"), in which case the + * provisions of the GPL are applicable instead of those above. + * If you wish to allow use of your version of this file only + * under the terms of the GPL and not to allow others to use your + * version of this file under the NPL, indicate your decision by + * deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete + * the provisions above, a recipient may use your version of this + * file under either the NPL or the GPL. + */ + +// API class + +package org.mozilla.javascript; + +/** + * This is interface defines a protocol for the reporting of + * errors during JavaScript translation or execution. + * + * @author Norris Boyd + */ + +public interface ErrorReporter { + + /** + * Report a warning. + * + * The implementing class may choose to ignore the warning + * if it desires. + * + * @param message a String describing the warning + * @param sourceName a String describing the JavaScript source + * where the warning occured; typically a filename or URL + * @param line the line number associated with the warning + * @param lineSource the text of the line (may be null) + * @param lineOffset the offset into lineSource where problem was detected + */ + void warning(String message, String sourceName, int line, + String lineSource, int lineOffset); + + /** + * Report an error. + * + * The implementing class is free to throw an exception if + * it desires. + * + * If execution has not yet begun, the JavaScript engine is + * free to find additional errors rather than terminating + * the translation. It will not execute a script that had + * errors, however. + * + * @param message a String describing the error + * @param sourceName a String describing the JavaScript source + * where the error occured; typically a filename or URL + * @param line the line number associated with the error + * @param lineSource the text of the line (may be null) + * @param lineOffset the offset into lineSource where problem was detected + */ + void error(String message, String sourceName, int line, + String lineSource, int lineOffset); + + /** + * Creates an EvaluatorException that may be thrown. + * + * runtimeErrors, unlike errors, will always terminate the + * current script. + * + * @param message a String describing the error + * @param sourceName a String describing the JavaScript source + * where the error occured; typically a filename or URL + * @param line the line number associated with the error + * @param lineSource the text of the line (may be null) + * @param lineOffset the offset into lineSource where problem was detected + * @return an EvaluatorException that will be thrown. + */ + EvaluatorException runtimeError(String message, String sourceName, + int line, String lineSource, + int lineOffset); +} diff --git a/src/org/mozilla/javascript/EvaluatorException.java b/src/org/mozilla/javascript/EvaluatorException.java new file mode 100644 index 0000000..5f31201 --- /dev/null +++ b/src/org/mozilla/javascript/EvaluatorException.java @@ -0,0 +1,56 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * The contents of this file are subject to the Netscape Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express oqr + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is Rhino code, released + * May 6, 1999. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1997-1999 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (the "GPL"), in which case the + * provisions of the GPL are applicable instead of those above. + * If you wish to allow use of your version of this file only + * under the terms of the GPL and not to allow others to use your + * version of this file under the NPL, indicate your decision by + * deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete + * the provisions above, a recipient may use your version of this + * file under either the NPL or the GPL. + */ + + +package org.mozilla.javascript; + +/** + * The class of exceptions thrown by the JavaScript engine. + */ +public class EvaluatorException extends RuntimeException { + + /** + * Create an exception with the specified detail message. + * + * Errors internal to the JavaScript engine will simply throw a + * RuntimeException. + * + * @param detail a message with detail about the exception + */ + public EvaluatorException(String detail) { + super(detail); + } + +} diff --git a/src/org/mozilla/javascript/FlattenedObject.java b/src/org/mozilla/javascript/FlattenedObject.java new file mode 100644 index 0000000..85c5f0b --- /dev/null +++ b/src/org/mozilla/javascript/FlattenedObject.java @@ -0,0 +1,341 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * The contents of this file are subject to the Netscape Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express oqr + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is Rhino code, released + * May 6, 1999. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1997-1999 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * Roger Lawrence + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (the "GPL"), in which case the + * provisions of the GPL are applicable instead of those above. + * If you wish to allow use of your version of this file only + * under the terms of the GPL and not to allow others to use your + * version of this file under the NPL, indicate your decision by + * deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete + * the provisions above, a recipient may use your version of this + * file under either the NPL or the GPL. + */ + +package org.mozilla.javascript; + +import java.util.Hashtable; +import java.util.Enumeration; + +/** + * Manipulate a Scriptable object as if its prototype chain were flattened. + *

+ * This class has been deprecated in favor of the static methods + * getProperty, putProperty, and + * deleteProperty of ScripableObject. Those methods provide the + * same functionality without the confusing and inefficient need to construct + * a new object instance. + * + * @see org.mozilla.javascript.ScriptableObject + * @deprecated + * @author Norris Boyd + */ + +public class FlattenedObject { + + /** + * Construct a new FlattenedObject. + * + * @param object the object to be viewed with flattened properties + * @deprecated + */ + public FlattenedObject(Scriptable object) { + this.obj = object; + } + + /** + * Get the associated Scriptable object. + * @deprecated + */ + public Scriptable getObject() { + return obj; + } + + /** + * Determine if a property exists in an object. + * + * This is a more convenient (and less efficient) form than + * Scriptable.has(). + * It returns true if and only if the property + * exists in this object or any of the objects in its prototype + * chain. + * + * @param id the property index, which may be either a String or a + * Number + * @return true if and only if the property exists in the prototype + * chain + * @see org.mozilla.javascript.Scriptable#has + * @deprecated As of 1.5R2, replaced by ScriptableObject.getProperty + */ + public boolean hasProperty(Object id) { + String stringId = ScriptRuntime.toString(id); + String s = ScriptRuntime.getStringId(stringId); + if (s == null) + return getBase(obj, ScriptRuntime.getIntId(stringId)) != null; + return getBase(obj, s) != null; + } + + /** + * Get a property of an object. + *

+ * This is a more convenient (and less efficient) form than + * Scriptable.get(). It corresponds exactly to the + * expression obj[id] in JavaScript. This method + * will traverse the prototype chain of an object to find the + * property.

+ * + * If the property does not exist in the object or its prototype + * chain, the undefined value will be returned. + * + * @param id the property index; can be a String or a Number; the + * String may contain characters representing a number + * @return the value of the property or the undefined value + * @see org.mozilla.javascript.Scriptable#get + * @see org.mozilla.javascript.Context#getUndefinedValue + * @deprecated As of 1.5R2, replaced by ScriptableObject.getProperty + */ + public Object getProperty(Object id) { + String s = ScriptRuntime.getStringId(id); + int index = s == null ? ScriptRuntime.getIntId(id) : 0; + Scriptable m = obj; + Object result; + for(;;) { + result = s == null ? m.get(index, obj) : m.get(s, obj); + if (result != Scriptable.NOT_FOUND) + break; + m = m.getPrototype(); + if (m == null) + return Undefined.instance; + } + if (result instanceof Scriptable) + return new FlattenedObject((Scriptable) result); + return result; + } + + /** + * Set a property of an object. + * + * This is a more convenient (and less efficient) form than that + * provided in Scriptable. It corresponds exactly to the + * expression obj[id] = val in JavaScript.

+ * + * @param id the property index, which may be either a String or + * a Number + * @param value the value of the property + * @see org.mozilla.javascript.Scriptable#put + * @deprecated As of 1.5R2, replaced by ScriptableObject.putProperty + */ + public void putProperty(Object id, Object value) { + String s = ScriptRuntime.getStringId(id); + if (value instanceof FlattenedObject) + value = ((FlattenedObject) value).getObject(); + Scriptable x; + if (s == null) { + int index = ScriptRuntime.getIntId(id); + x = getBase(obj, index); + if (x == null) + x = obj; + x.put(index, obj, value); + return; + } + x = getBase(obj, s); + if (x == null) + x = obj; + x.put(s, obj, value); + } + + /** + * Remove a property. + * + * This method provides the functionality of the delete + * operator in JavaScript. + * + * @param id the property index, which may be either a String or + * a Number + * @return true if the property didn't exist, or existed and was removed + * @see org.mozilla.javascript.Scriptable#delete + * @deprecated as of 1.5R2, replaced by ScriptableObject.deleteProperty + */ + public boolean deleteProperty(Object id) { + String s = ScriptRuntime.getStringId(id); + if (s == null) { + int index = ScriptRuntime.getIntId(id); + Scriptable base = getBase(obj, index); + if (base == null) + return true; + base.delete(index); + return !base.has(index, base); + } + Scriptable base = getBase(obj, s); + if (base == null) + return true; + base.delete(s); + return !base.has(s, base); + } + + /** + * Return an array that contains the ids of the properties. + * + *

This method will walk the prototype chain and collect the + * ids of all objects in the prototype chain.

+ * + * If an id appears in more than one object in the prototype chain, + * it will only be in the array once. (So all the entries in the + * array will be unique respective to equals().) + * + * @see org.mozilla.javascript.Scriptable#getIds + * @deprecated + */ + public Object[] getIds() { + Hashtable h = new Hashtable(11); + Scriptable m = obj; + while (m != null) { + Object[] e = m.getIds(); + for (int i=0; i < e.length; i++) { + h.put(e[i], Boolean.TRUE); + } + m = m.getPrototype(); + } + Enumeration keys = h.keys(); + Object elem; + Object[] result = new Object[h.size()]; + int index = 0; + while (keys.hasMoreElements()) { + elem = keys.nextElement(); + result[index++] = elem; + } + return result; + } + + /** + * Consider this object to be a function, and call it. + * + * @param cx the current Context for this thread + * @param thisObj the JavaScript 'this' for the call + * @param args the arguments for the call + * @return the result of the JavaScript function call + * @exception NotAFunctionException if this object is not a function + * @exception JavaScriptException if an uncaught JavaScript exception + * occurred while executing the function + * @see org.mozilla.javascript.Function#call + * @deprecated + */ + public Object call(Context cx, Scriptable thisObj, Object[] args) + throws NotAFunctionException, + JavaScriptException + { + if (!(obj instanceof Function)) { + throw new NotAFunctionException(); + } + return ScriptRuntime.call(cx, obj, thisObj, args, (Function) obj); + } + + /** + * Consider this object to be a function, and invoke it as a + * constructor call. + * + * @param cx the current Context for this thread + * @param args the arguments for the constructor call + * @return the allocated object + * @exception NotAFunctionException if this object is not a function + * @exception JavaScriptException if an uncaught JavaScript exception + * occurred while executing the constructor + * @see org.mozilla.javascript.Function#construct + * @deprecated + */ + public Scriptable construct(Context cx, Object[] args) + throws NotAFunctionException, + JavaScriptException + { + if (!(obj instanceof Function)) { + throw new NotAFunctionException(); + } + return ScriptRuntime.newObject(cx, obj, args, null); + } + + /** + * Get the property indicated by the id, and invoke it with the + * specified arguments. + *

+ * For example, for a FlattenedObject obj, + * and a Java array a consisting of a single string + * "hi", the call

+     * obj.callMethod("m", a)
+ * is equivalent to the JavaScript code obj.m("hi").

+ * + * If the property is not found or is not a function, an + * exception will be thrown. + * + * @param id the Number or String to use to find the function property + * to call + * @param args the arguments for the constructor call + * @return the result of the call + * @exception PropertyException if the designated property + * was not found + * @exception NotAFunctionException if this object is not a function + * @exception JavaScriptException if an uncaught JavaScript exception + * occurred while executing the method + * @see org.mozilla.javascript.Function#call + * @deprecated + */ + public Object callMethod(Object id, Object[] args) + throws PropertyException, + NotAFunctionException, + JavaScriptException + { + if (!hasProperty(id)) { + throw PropertyException.withMessage0("msg.prop.not.found"); + } + Object o = getProperty(id); + if (o instanceof FlattenedObject) + return ((FlattenedObject) o).call(Context.getContext(), obj, args); + throw new NotAFunctionException(); + } + + /****** End of API *******/ + + private static Scriptable getBase(Scriptable obj, String s) { + Scriptable m = obj; + while (m != null) { + if (m.has(s, obj)) + return m; + m = m.getPrototype(); + } + return null; + } + + private static Scriptable getBase(Scriptable obj, int index) { + Scriptable m = obj; + while (m != null) { + if (m.has(index, obj)) + return m; + m = m.getPrototype(); + } + return null; + } + + private Scriptable obj; +} + diff --git a/src/org/mozilla/javascript/Function.java b/src/org/mozilla/javascript/Function.java new file mode 100644 index 0000000..c958d5b --- /dev/null +++ b/src/org/mozilla/javascript/Function.java @@ -0,0 +1,86 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * The contents of this file are subject to the Netscape Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express oqr + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is Rhino code, released + * May 6, 1999. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1997-1999 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (the "GPL"), in which case the + * provisions of the GPL are applicable instead of those above. + * If you wish to allow use of your version of this file only + * under the terms of the GPL and not to allow others to use your + * version of this file under the NPL, indicate your decision by + * deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete + * the provisions above, a recipient may use your version of this + * file under either the NPL or the GPL. + */ + +// API class + +package org.mozilla.javascript; + +/** + * This is interface that all functions in JavaScript must implement. + * The interface provides for calling functions and constructors. + * + * @see org.mozilla.javascript.Scriptable + * @author Norris Boyd + */ + +public interface Function extends Scriptable { + /** + * Call the function. + * + * Note that the array of arguments is not guaranteed to have + * length greater than 0. + * + * @param cx the current Context for this thread + * @param scope the scope to execute the function relative to. This is + * set to the value returned by getParentScope() except + * when the function is called from a closure. + * @param thisObj the JavaScript this object + * @param args the array of arguments + * @return the result of the call + * @exception JavaScriptException if an uncaught exception + * occurred while executing the function + */ + public Object call(Context cx, Scriptable scope, Scriptable thisObj, + Object[] args) + throws JavaScriptException; + + /** + * Call the function as a constructor. + * + * This method is invoked by the runtime in order to satisfy a use + * of the JavaScript new operator. This method is + * expected to create a new object and return it. + * + * @param cx the current Context for this thread + * @param scope an enclosing scope of the caller except + * when the function is called from a closure. + * @param args the array of arguments + * @return the allocated object + * @exception JavaScriptException if an uncaught exception + * occurred while executing the constructor + */ + public Scriptable construct(Context cx, Scriptable scope, Object[] args) + throws JavaScriptException; +} diff --git a/src/org/mozilla/javascript/FunctionNode.java b/src/org/mozilla/javascript/FunctionNode.java new file mode 100644 index 0000000..1584bc4 --- /dev/null +++ b/src/org/mozilla/javascript/FunctionNode.java @@ -0,0 +1,103 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * The contents of this file are subject to the Netscape Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express oqr + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is Rhino code, released + * May 6, 1999. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1997-1999 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * Roger Lawrence + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (the "GPL"), in which case the + * provisions of the GPL are applicable instead of those above. + * If you wish to allow use of your version of this file only + * under the terms of the GPL and not to allow others to use your + * version of this file under the NPL, indicate your decision by + * deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete + * the provisions above, a recipient may use your version of this + * file under either the NPL or the GPL. + */ + +package org.mozilla.javascript; + +import java.util.*; + +public class FunctionNode extends Node { + + public FunctionNode(String name, Node left, Node right) { + super(TokenStream.FUNCTION, left, right, name); + itsVariableTable = new VariableTable(); + } + + public String getFunctionName() { + return getString(); + } + + public VariableTable getVariableTable() { + return itsVariableTable; + } + + public boolean requiresActivation() { + return itsNeedsActivation; + } + + public boolean setRequiresActivation(boolean b) { + return itsNeedsActivation = b; + } + + public boolean getCheckThis() { + return itsCheckThis; + } + + public void setCheckThis(boolean b) { + itsCheckThis = b; + } + + /** + * There are three types of functions that can be defined. The first + * is a function statement. This is a function appearing as a top-level + * statement (i.e., not nested inside some other statement) in either a + * script or a function. + * + * The second is a function expression, which is a function appearing in + * an expression except for the third type, which is... + * + * The third type is a function expression where the expression is the + * top-level expression in an expression statement. + * + * The three types of functions have different treatment and must be + * distinquished. + */ + public static final byte FUNCTION_STATEMENT = 1; + public static final byte FUNCTION_EXPRESSION = 2; + public static final byte FUNCTION_EXPRESSION_STATEMENT = 3; + + public byte getFunctionType() { + return itsFunctionType; + } + + public void setFunctionType(byte functionType) { + itsFunctionType = functionType; + } + + protected VariableTable itsVariableTable; + protected boolean itsNeedsActivation; + protected boolean itsCheckThis; + protected byte itsFunctionType; +} diff --git a/src/org/mozilla/javascript/FunctionObject.java b/src/org/mozilla/javascript/FunctionObject.java new file mode 100644 index 0000000..50a3e93 --- /dev/null +++ b/src/org/mozilla/javascript/FunctionObject.java @@ -0,0 +1,626 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * The contents of this file are subject to the Netscape Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express oqr + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is Rhino code, released + * May 6, 1999. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1997-2000 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * Igor Bukanov + * David C. Navas + * Ted Neward + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (the "GPL"), in which case the + * provisions of the GPL are applicable instead of those above. + * If you wish to allow use of your version of this file only + * under the terms of the GPL and not to allow others to use your + * version of this file under the NPL, indicate your decision by + * deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete + * the provisions above, a recipient may use your version of this + * file under either the NPL or the GPL. + */ + +// API class + +package org.mozilla.javascript; + +import java.util.Vector; +import java.lang.reflect.Constructor; +import java.lang.reflect.Member; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.InvocationTargetException; + +public class FunctionObject extends NativeFunction { + + /** + * Create a JavaScript function object from a Java method. + * + *

The member argument must be either a java.lang.reflect.Method + * or a java.lang.reflect.Constructor and must match one of two forms.

+ * + * The first form is a member with zero or more parameters + * of the following types: Object, String, boolean, Scriptable, + * byte, short, int, float, or double. The Long type is not supported + * because the double representation of a long (which is the + * EMCA-mandated storage type for Numbers) may lose precision. + * If the member is a Method, the return value must be void or one + * of the types allowed for parameters.

+ * + * The runtime will perform appropriate conversions based + * upon the type of the parameter. A parameter type of + * Object specifies that no conversions are to be done. A parameter + * of type String will use Context.toString to convert arguments. + * Similarly, parameters of type double, boolean, and Scriptable + * will cause Context.toNumber, Context.toBoolean, and + * Context.toObject, respectively, to be called.

+ * + * If the method is not static, the Java 'this' value will + * correspond to the JavaScript 'this' value. Any attempt + * to call the function with a 'this' value that is not + * of the right Java type will result in an error.

+ * + * The second form is the variable arguments (or "varargs") + * form. If the FunctionObject will be used as a constructor, + * the member must have the following parameters + *

+     *      (Context cx, Object[] args, Function ctorObj,
+     *       boolean inNewExpr)
+ * and if it is a Method, be static and return an Object result.

+ * + * Otherwise, if the FunctionObject will not be used to define a + * constructor, the member must be a static Method with parameters + * (Context cx, Scriptable thisObj, Object[] args, + * Function funObj) + *

+     * and an Object result.

+ * + * When the function varargs form is called as part of a function call, + * the args parameter contains the + * arguments, with thisObj + * set to the JavaScript 'this' value. funObj + * is the function object for the invoked function.

+ * + * When the constructor varargs form is called or invoked while evaluating + * a new expression, args contains the + * arguments, ctorObj refers to this FunctionObject, and + * inNewExpr is true if and only if a new + * expression caused the call. This supports defining a function that + * has different behavior when called as a constructor than when + * invoked as a normal function call. (For example, the Boolean + * constructor, when called as a function, + * will convert to boolean rather than creating a new object.)

+ * + * @param name the name of the function + * @param methodOrConstructor a java.lang.reflect.Method or a java.lang.reflect.Constructor + * that defines the object + * @param scope enclosing scope of function + * @see org.mozilla.javascript.Scriptable + */ + public FunctionObject(String name, Member methodOrConstructor, + Scriptable scope) + { + String methodName; + if (methodOrConstructor instanceof Constructor) { + ctor = (Constructor) methodOrConstructor; + isStatic = true; // well, doesn't take a 'this' + types = ctor.getParameterTypes(); + methodName = ctor.getName(); + } else { + method = (Method) methodOrConstructor; + isStatic = Modifier.isStatic(method.getModifiers()); + types = method.getParameterTypes(); + methodName = method.getName(); + } + this.functionName = name; + int length; + if (types.length == 4 && (types[1].isArray() || types[2].isArray())) { + // Either variable args or an error. + if (types[1].isArray()) { + if (!isStatic || + types[0] != Context.class || + types[1].getComponentType() != ScriptRuntime.ObjectClass || + types[2] != ScriptRuntime.FunctionClass || + types[3] != Boolean.TYPE) + { + throw Context.reportRuntimeError1( + "msg.varargs.ctor", methodName); + } + parmsLength = VARARGS_CTOR; + } else { + if (!isStatic || + types[0] != Context.class || + types[1] != ScriptRuntime.ScriptableClass || + types[2].getComponentType() != ScriptRuntime.ObjectClass || + types[3] != ScriptRuntime.FunctionClass) + { + throw Context.reportRuntimeError1( + "msg.varargs.fun", methodName); + } + parmsLength = VARARGS_METHOD; + } + // XXX check return type + length = 1; + } else { + parmsLength = (short) types.length; + for (int i=0; i < parmsLength; i++) { + Class type = types[i]; + if (type != ScriptRuntime.ObjectClass && + type != ScriptRuntime.StringClass && + type != ScriptRuntime.BooleanClass && + !ScriptRuntime.NumberClass.isAssignableFrom(type) && + !Scriptable.class.isAssignableFrom(type) && + type != Boolean.TYPE && + type != Byte.TYPE && + type != Short.TYPE && + type != Integer.TYPE && + type != Float.TYPE && + type != Double.TYPE) + { + // Note that long is not supported. + throw Context.reportRuntimeError1("msg.bad.parms", + methodName); + } + } + length = parmsLength; + } + + // Initialize length property + lengthPropertyValue = (short) length; + + hasVoidReturn = method != null && method.getReturnType() == Void.TYPE; + this.argCount = (short) length; + + setParentScope(scope); + setPrototype(getFunctionPrototype(scope)); + Context cx = Context.getCurrentContext(); + useDynamicScope = cx != null && + cx.hasCompileFunctionsWithDynamicScope(); + } + + /** + * Return the value defined by the method used to construct the object + * (number of parameters of the method, or 1 if the method is a "varargs" + * form), unless setLength has been called with a new value. + * Overrides getLength in BaseFunction. + * + * @see org.mozilla.javascript.FunctionObject#setLength + * @see org.mozilla.javascript.BaseFunction#getLength + */ + public int getLength() { + return lengthPropertyValue; + } + + /** + * Set the value of the "length" property. + * + *

Changing the value of the "length" property of a FunctionObject only + * affects the value retrieved from get() and does not affect the way + * the method itself is called.

+ * + * The "length" property will be defined by default as the number + * of parameters of the method used to construct the FunctionObject, + * unless the method is a "varargs" form, in which case the "length" + * property will be defined to 1. + * + * @param length the new length + */ + public void setLength(short length) { + lengthPropertyValue = length; + } + + // TODO: Make not public + /** + * Finds methods of a given name in a given class. + * + *

Searches clazz for methods with name + * name. Maintains a cache so that multiple + * lookups on the same class are cheap. + * + * @param clazz the class to search + * @param name the name of the methods to find + * @return an array of the found methods, or null if no methods + * by that name were found. + * @see java.lang.Class#getMethods + */ + public static Method[] findMethods(Class clazz, String name) { + return findMethods(getMethodList(clazz), name); + } + + static Method[] findMethods(Method[] methods, String name) { + // Usually we're just looking for a single method, so optimize + // for that case. + Vector v = null; + Method first = null; + for (int i=0; i < methods.length; i++) { + if (methods[i] == null) + continue; + if (methods[i].getName().equals(name)) { + if (first == null) { + first = methods[i]; + } else { + if (v == null) { + v = new Vector(5); + v.addElement(first); + } + v.addElement(methods[i]); + } + } + } + if (v == null) { + if (first == null) + return null; + Method[] single = { first }; + return single; + } + Method[] result = new Method[v.size()]; + v.copyInto(result); + return result; + } + + static Method[] getMethodList(Class clazz) { + Method[] cached = methodsCache; // get once to avoid synchronization + if (cached != null && cached[0].getDeclaringClass() == clazz) + return cached; + Method[] methods = null; + try { + // getDeclaredMethods may be rejected by the security manager + // but getMethods is more expensive + if (!sawSecurityException) + methods = clazz.getDeclaredMethods(); + } catch (SecurityException e) { + // If we get an exception once, give up on getDeclaredMethods + sawSecurityException = true; + } + if (methods == null) { + methods = clazz.getMethods(); + } + int count = 0; + for (int i=0; i < methods.length; i++) { + if (sawSecurityException + ? methods[i].getDeclaringClass() != clazz + : !Modifier.isPublic(methods[i].getModifiers())) + { + methods[i] = null; + } else { + count++; + } + } + Method[] result = new Method[count]; + int j=0; + for (int i=0; i < methods.length; i++) { + if (methods[i] != null) + result[j++] = methods[i]; + } + if (result.length > 0 && Context.isCachingEnabled) + methodsCache = result; + return result; + } + + /** + * Define this function as a JavaScript constructor. + *

+ * Sets up the "prototype" and "constructor" properties. Also + * calls setParent and setPrototype with appropriate values. + * Then adds the function object as a property of the given scope, using + * prototype.getClassName() + * as the name of the property. + * + * @param scope the scope in which to define the constructor (typically + * the global object) + * @param prototype the prototype object + * @see org.mozilla.javascript.Scriptable#setParentScope + * @see org.mozilla.javascript.Scriptable#setPrototype + * @see org.mozilla.javascript.Scriptable#getClassName + */ + public void addAsConstructor(Scriptable scope, Scriptable prototype) { + setParentScope(scope); + setPrototype(getFunctionPrototype(scope)); + setImmunePrototypeProperty(prototype); + + prototype.setParentScope(this); + + final int attr = ScriptableObject.DONTENUM | + ScriptableObject.PERMANENT | + ScriptableObject.READONLY; + defineProperty(prototype, "constructor", this, attr); + + String name = prototype.getClassName(); + defineProperty(scope, name, this, ScriptableObject.DONTENUM); + + setParentScope(scope); + } + + static public Object convertArg(Scriptable scope, + Object arg, Class desired) + { + if (desired == ScriptRuntime.StringClass) + return ScriptRuntime.toString(arg); + if (desired == ScriptRuntime.IntegerClass || + desired == Integer.TYPE) + { + return new Integer(ScriptRuntime.toInt32(arg)); + } + if (desired == ScriptRuntime.BooleanClass || + desired == Boolean.TYPE) + { + return ScriptRuntime.toBoolean(arg) ? Boolean.TRUE + : Boolean.FALSE; + } + if (desired == ScriptRuntime.DoubleClass || + desired == Double.TYPE) + { + return new Double(ScriptRuntime.toNumber(arg)); + } + if (desired == ScriptRuntime.ScriptableClass) + return ScriptRuntime.toObject(scope, arg); + if (desired == ScriptRuntime.ObjectClass) + return arg; + + // Note that the long type is not supported; see the javadoc for + // the constructor for this class + throw Context.reportRuntimeError1 + ("msg.cant.convert", desired.getName()); + } + + /** + * Performs conversions on argument types if needed and + * invokes the underlying Java method or constructor. + *

+ * Implements Function.call. + * + * @see org.mozilla.javascript.Function#call + * @exception JavaScriptException if the underlying Java method or + * constructor threw an exception + */ + public Object call(Context cx, Scriptable scope, Scriptable thisObj, + Object[] args) + throws JavaScriptException + { + if (parmsLength < 0) { + return callVarargs(cx, thisObj, args, false); + } + if (!isStatic) { + // OPT: cache "clazz"? + Class clazz = method != null ? method.getDeclaringClass() + : ctor.getDeclaringClass(); + while (!clazz.isInstance(thisObj)) { + thisObj = thisObj.getPrototype(); + if (thisObj == null || !useDynamicScope) { + // Couldn't find an object to call this on. + throw NativeGlobal.typeError1 + ("msg.incompat.call", functionName, scope); + } + } + } + Object[] invokeArgs; + int i; + if (parmsLength == args.length) { + invokeArgs = args; + // avoid copy loop if no conversions needed + i = (types == null) ? parmsLength : 0; + } else { + invokeArgs = new Object[parmsLength]; + i = 0; + } + for (; i < parmsLength; i++) { + Object arg = (i < args.length) + ? args[i] + : Undefined.instance; + if (types != null) { + arg = convertArg(this, arg, types[i]); + } + invokeArgs[i] = arg; + } + try { + Object result = method == null ? ctor.newInstance(invokeArgs) + : doInvoke(thisObj, invokeArgs); + return hasVoidReturn ? Undefined.instance : result; + } + catch (InvocationTargetException e) { + throw JavaScriptException.wrapException(scope, e); + } + catch (IllegalAccessException e) { + throw WrappedException.wrapException(e); + } + catch (InstantiationException e) { + throw WrappedException.wrapException(e); + } + } + + /** + * Performs conversions on argument types if needed and + * invokes the underlying Java method or constructor + * to create a new Scriptable object. + *

+ * Implements Function.construct. + * + * @param cx the current Context for this thread + * @param scope the scope to execute the function relative to. This + * set to the value returned by getParentScope() except + * when the function is called from a closure. + * @param args arguments to the constructor + * @see org.mozilla.javascript.Function#construct + * @exception JavaScriptException if the underlying Java method or constructor + * threw an exception + */ + public Scriptable construct(Context cx, Scriptable scope, Object[] args) + throws JavaScriptException + { + if (method == null || parmsLength == VARARGS_CTOR) { + Scriptable result; + if (method != null) { + result = (Scriptable) callVarargs(cx, null, args, true); + } else { + result = (Scriptable) call(cx, scope, null, args); + } + + if (result.getPrototype() == null) + result.setPrototype(getClassPrototype()); + if (result.getParentScope() == null) { + Scriptable parent = getParentScope(); + if (result != parent) + result.setParentScope(parent); + } + + return result; + } else if (method != null && !isStatic) { + Scriptable result; + try { + result = (Scriptable) method.getDeclaringClass().newInstance(); + } catch (IllegalAccessException e) { + throw WrappedException.wrapException(e); + } catch (InstantiationException e) { + throw WrappedException.wrapException(e); + } + + result.setPrototype(getClassPrototype()); + result.setParentScope(getParentScope()); + + Object val = call(cx, scope, result, args); + if (val != null && val != Undefined.instance && + val instanceof Scriptable) + { + return (Scriptable) val; + } + return result; + } + + return super.construct(cx, scope, args); + } + + private final Object doInvoke(Object thisObj, Object[] args) + throws IllegalAccessException, InvocationTargetException + { + Invoker master = invokerMaster; + if (master != null) { + if (invoker == null) { + invoker = master.createInvoker(method, types); + } + try { + return invoker.invoke(thisObj, args); + } catch (RuntimeException e) { + throw new InvocationTargetException(e); + } + } + return method.invoke(thisObj, args); + } + + private Object callVarargs(Context cx, Scriptable thisObj, Object[] args, + boolean inNewExpr) + throws JavaScriptException + { + try { + Object[] invokeArgs; + Object ret; + if (cx.arrayCache.size() > 0) invokeArgs = (Object[])cx.arrayCache.lastElement(); + else invokeArgs = new Object[4]; + + if (parmsLength == VARARGS_METHOD) { + invokeArgs[0] = cx; + invokeArgs[1] = thisObj; + invokeArgs[2] = args; + invokeArgs[3] = this; + Object result = doInvoke(null, invokeArgs); + ret = hasVoidReturn ? Undefined.instance : result; + } else { + Boolean b = inNewExpr ? Boolean.TRUE : Boolean.FALSE; + invokeArgs[0] = cx; + invokeArgs[1] = args; + invokeArgs[2] = this; + invokeArgs[3] = b; + ret = (method == null) + ? ctor.newInstance(invokeArgs) + : doInvoke(null, invokeArgs); + } + + cx.arrayCache.addElement(invokeArgs); + return ret; + } + catch (InvocationTargetException e) { + Throwable target = e.getTargetException(); + if (target instanceof EvaluatorException) + throw (EvaluatorException) target; + if (target instanceof EcmaError) + throw (EcmaError) target; + Scriptable scope = thisObj == null ? this : thisObj; + throw JavaScriptException.wrapException(scope, target); + } + catch (IllegalAccessException e) { + throw WrappedException.wrapException(e); + } + catch (InstantiationException e) { + throw WrappedException.wrapException(e); + } + } + + boolean isVarArgsMethod() { + return parmsLength == VARARGS_METHOD; + } + + boolean isVarArgsConstructor() { + return parmsLength == VARARGS_CTOR; + } + + static void setCachingEnabled(boolean enabled) { + if (!enabled) { + methodsCache = null; + invokerMaster = null; + } else if (invokerMaster == null) { + invokerMaster = newInvokerMaster(); + } + } + + /** Get default master implementation or null if not available */ + private static Invoker newInvokerMaster() { + /* + try { + Class cl = ScriptRuntime.loadClassName(INVOKER_MASTER_CLASS); + return (Invoker)cl.newInstance(); + } + catch (ClassNotFoundException ex) {} + catch (IllegalAccessException ex) {} + catch (InstantiationException ex) {} + catch (SecurityException ex) {} + */ + return null; + } + + private static final String + INVOKER_MASTER_CLASS = "org.mozilla.javascript.optimizer.InvokerImpl"; + + static Invoker invokerMaster = newInvokerMaster(); + + private static final short VARARGS_METHOD = -1; + private static final short VARARGS_CTOR = -2; + + private static boolean sawSecurityException; + + static Method[] methodsCache; + + Method method; + Constructor ctor; + private Class[] types; + Invoker invoker; + private short parmsLength; + private short lengthPropertyValue; + private boolean hasVoidReturn; + private boolean isStatic; + private boolean useDynamicScope; +} diff --git a/src/org/mozilla/javascript/IRFactory.java b/src/org/mozilla/javascript/IRFactory.java new file mode 100644 index 0000000..948ac10 --- /dev/null +++ b/src/org/mozilla/javascript/IRFactory.java @@ -0,0 +1,1065 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * The contents of this file are subject to the Netscape Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express oqr + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is Rhino code, released + * May 6, 1999. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1997-1999 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (the "GPL"), in which case the + * provisions of the GPL are applicable instead of those above. + * If you wish to allow use of your version of this file only + * under the terms of the GPL and not to allow others to use your + * version of this file under the NPL, indicate your decision by + * deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete + * the provisions above, a recipient may use your version of this + * file under either the NPL or the GPL. + */ + +package org.mozilla.javascript; + +/** + * This class allows the creation of nodes, and follows the Factory pattern. + * + * @see Node + * @author Mike McCabe + * @author Norris Boyd + */ +public class IRFactory { + + public IRFactory(TokenStream ts, Scriptable scope) { + this.ts = ts; + this.scope = scope; + } + + /** + * Script (for associating file/url names with toplevel scripts.) + */ + public Object createScript(Object body, String sourceName, + int baseLineno, int endLineno, Object source) + { + Node result = new Node(TokenStream.SCRIPT, sourceName); + Node children = ((Node) body).getFirstChild(); + if (children != null) + result.addChildrenToBack(children); + result.putProp(Node.SOURCENAME_PROP, sourceName); + result.putProp(Node.BASE_LINENO_PROP, new Integer(baseLineno)); + result.putProp(Node.END_LINENO_PROP, new Integer(endLineno)); + if (source != null) + result.putProp(Node.SOURCE_PROP, source); + return result; + } + + /** + * Leaf + */ + public Object createLeaf(int nodeType) { + return new Node(nodeType); + } + + public Object createLeaf(int nodeType, String id) { + return new Node(nodeType, id); + } + + public Object createLeaf(int nodeType, int nodeOp) { + return new Node(nodeType, new Integer(nodeOp)); + } + + /** + * Statement leaf nodes. + */ + + public Object createSwitch(int lineno) { + return new Node(TokenStream.SWITCH, new Integer(lineno)); + } + + public Object createVariables(int lineno) { + return new Node(TokenStream.VAR, new Integer(lineno)); + } + + public Object createExprStatement(Object expr, int lineno) { + return new Node(TokenStream.EXPRSTMT, (Node) expr, new Integer(lineno)); + } + + /** + * Name + */ + public Object createName(String name) { + return new Node(TokenStream.NAME, name); + } + + /** + * String (for literals) + */ + public Object createString(String string) { + return new Node(TokenStream.STRING, string); + } + + /** + * Number (for literals) + */ + public Object createNumber(Number number) { + return new Node(TokenStream.NUMBER, number); + } + + /** + * Catch clause of try/catch/finally + * @param varName the name of the variable to bind to the exception + * @param catchCond the condition under which to catch the exception. + * May be null if no condition is given. + * @param stmts the statements in the catch clause + * @param lineno the starting line number of the catch clause + */ + public Object createCatch(String varName, Object catchCond, Object stmts, + int lineno) + { + if (catchCond == null) + catchCond = new Node(TokenStream.PRIMARY, + new Integer(TokenStream.TRUE)); + Node result = new Node(TokenStream.CATCH, (Node)createName(varName), + (Node)catchCond, (Node)stmts); + result.setDatum(new Integer(lineno)); + return result; + } + + /** + * Throw + */ + public Object createThrow(Object expr, int lineno) { + return new Node(TokenStream.THROW, (Node)expr, new Integer(lineno)); + } + + /** + * Return + */ + public Object createReturn(Object expr, int lineno) { + return expr == null + ? new Node(TokenStream.RETURN, new Integer(lineno)) + : new Node(TokenStream.RETURN, (Node)expr, new Integer(lineno)); + } + + /** + * Assert + */ + public Object createAssert(Object expr, int lineno) { + return expr == null + ? new Node(TokenStream.ASSERT, new Integer(lineno)) + : new Node(TokenStream.ASSERT, (Node)expr, new Integer(lineno)); + } + + /** + * Label + */ + public Object createLabel(String label, int lineno) { + Node result = new Node(TokenStream.LABEL, new Integer(lineno)); + Node name = new Node(TokenStream.NAME, label); + result.addChildToBack(name); + return result; + } + + /** + * Break (possibly labeled) + */ + public Object createBreak(String label, int lineno) { + Node result = new Node(TokenStream.BREAK, new Integer(lineno)); + if (label == null) { + return result; + } else { + Node name = new Node(TokenStream.NAME, label); + result.addChildToBack(name); + return result; + } + } + + /** + * Continue (possibly labeled) + */ + public Object createContinue(String label, int lineno) { + Node result = new Node(TokenStream.CONTINUE, new Integer(lineno)); + if (label == null) { + return result; + } else { + Node name = new Node(TokenStream.NAME, label); + result.addChildToBack(name); + return result; + } + } + + /** + * Statement block + * Creates the empty statement block + * Must make subsequent calls to add statements to the node + */ + public Object createBlock(int lineno) { + return new Node(TokenStream.BLOCK, new Integer(lineno)); + } + + public Object createFunctionNode(String name, Object args, + Object statements) + { + if (name == null) + name = ""; + return new FunctionNode(name, (Node) args, (Node) statements); + } + + public Object createFunction(String name, Object args, Object statements, + String sourceName, int baseLineno, + int endLineno, Object source, + boolean isExpr) + { + FunctionNode f = (FunctionNode) createFunctionNode(name, args, + statements); + f.setFunctionType(isExpr ? FunctionNode.FUNCTION_EXPRESSION + : FunctionNode.FUNCTION_STATEMENT); + f.putProp(Node.SOURCENAME_PROP, sourceName); + f.putProp(Node.BASE_LINENO_PROP, new Integer(baseLineno)); + f.putProp(Node.END_LINENO_PROP, new Integer(endLineno)); + if (source != null) + f.putProp(Node.SOURCE_PROP, source); + Node result = new Node(TokenStream.FUNCTION, name); + result.putProp(Node.FUNCTION_PROP, f); + return result; + } + + public void setFunctionExpressionStatement(Object o) { + Node n = (Node) o; + FunctionNode f = (FunctionNode) n.getProp(Node.FUNCTION_PROP); + f.setFunctionType(FunctionNode.FUNCTION_EXPRESSION_STATEMENT); + } + + /** + * Add a child to the back of the given node. This function + * breaks the Factory abstraction, but it removes a requirement + * from implementors of Node. + */ + public void addChildToBack(Object parent, Object child) { + ((Node)parent).addChildToBack((Node)child); + } + + /** + * While + */ + public Object createWhile(Object cond, Object body, int lineno) { + // Just add a GOTO to the condition in the do..while + Node result = (Node) createDoWhile(body, cond, lineno); + Node condTarget = (Node) result.getProp(Node.CONTINUE_PROP); + Node GOTO = new Node(TokenStream.GOTO); + GOTO.putProp(Node.TARGET_PROP, condTarget); + result.addChildToFront(GOTO); + return result; + } + + /** + * DoWhile + */ + public Object createDoWhile(Object body, Object cond, int lineno) { + Node result = new Node(TokenStream.LOOP, new Integer(lineno)); + Node bodyTarget = new Node(TokenStream.TARGET); + Node condTarget = new Node(TokenStream.TARGET); + Node IFEQ = new Node(TokenStream.IFEQ, (Node)cond); + IFEQ.putProp(Node.TARGET_PROP, bodyTarget); + Node breakTarget = new Node(TokenStream.TARGET); + + result.addChildToBack(bodyTarget); + result.addChildrenToBack((Node)body); + result.addChildToBack(condTarget); + result.addChildToBack(IFEQ); + result.addChildToBack(breakTarget); + + result.putProp(Node.BREAK_PROP, breakTarget); + result.putProp(Node.CONTINUE_PROP, condTarget); + + return result; + } + + /** + * For + */ + public Object createFor(Object init, Object test, Object incr, + Object body, int lineno) + { + if (((Node) test).getType() == TokenStream.VOID) { + test = new Node(TokenStream.PRIMARY, + new Integer(TokenStream.TRUE)); + } + Node result = (Node)createWhile(test, body, lineno); + Node initNode = (Node) init; + if (initNode.getType() != TokenStream.VOID) { + if (initNode.getType() != TokenStream.VAR) + initNode = new Node(TokenStream.POP, initNode); + result.addChildToFront(initNode); + } + Node condTarget = (Node)result.getProp(Node.CONTINUE_PROP); + Node incrTarget = new Node(TokenStream.TARGET); + result.addChildBefore(incrTarget, condTarget); + if (((Node) incr).getType() != TokenStream.VOID) { + incr = createUnary(TokenStream.POP, incr); + result.addChildAfter((Node)incr, incrTarget); + } + result.putProp(Node.CONTINUE_PROP, incrTarget); + return result; + } + + /** + * For .. In + * + */ + public Object createForIn(Object lhs, Object obj, Object body, int lineno) { + String name; + Node lhsNode = (Node) lhs; + Node objNode = (Node) obj; + int type = lhsNode.getType(); + + Node lvalue = lhsNode; + switch (type) { + + case TokenStream.NAME: + case TokenStream.GETPROP: + case TokenStream.GETELEM: + break; + + case TokenStream.VAR: + /* + * check that there was only one variable given. + * we can't do this in the parser, because then the + * parser would have to know something about the + * 'init' node of the for-in loop. + */ + Node lastChild = lhsNode.getLastChild(); + if (lhsNode.getFirstChild() != lastChild) { + reportError("msg.mult.index"); + } + lvalue = new Node(TokenStream.NAME, lastChild.getString()); + break; + + default: + reportError("msg.bad.for.in.lhs"); + return objNode; + } + + Node init = new Node(TokenStream.ENUMINIT, objNode); + Node next = new Node(TokenStream.ENUMNEXT); + next.putProp(Node.ENUM_PROP, init); + Node temp = createNewTemp(next); + Node cond = new Node(TokenStream.EQOP, new Integer(TokenStream.NE)); + cond.addChildToBack(temp); + cond.addChildToBack(new Node(TokenStream.PRIMARY, + new Integer(TokenStream.NULL))); + Node newBody = new Node(TokenStream.BLOCK); + Node assign = (Node) createAssignment(TokenStream.NOP, lvalue, + createUseTemp(temp), null, + false); + newBody.addChildToBack(new Node(TokenStream.POP, assign)); + newBody.addChildToBack((Node) body); + Node result = (Node) createWhile(cond, newBody, lineno); + + result.addChildToFront(init); + if (type == TokenStream.VAR) + result.addChildToFront(lhsNode); + + Node done = new Node(TokenStream.ENUMDONE); + done.putProp(Node.ENUM_PROP, init); + result.addChildToBack(done); + + return result; + } + + /** + * Try/Catch/Finally + * + * The IRFactory tries to express as much as possible in the tree; + * the responsibilities remaining for Codegen are to add the Java + * handlers: (Either (but not both) of TARGET and FINALLY might not + * be defined) + + * - a catch handler for javascript exceptions that unwraps the + * exception onto the stack and GOTOes to the catch target - + * TARGET_PROP in the try node. + + * - a finally handler that catches any exception, stores it to a + * temporary, and JSRs to the finally target - FINALLY_PROP in the + * try node - before re-throwing the exception. + + * ... and a goto to GOTO around these handlers. + */ + public Object createTryCatchFinally(Object tryblock, Object catchblocks, + Object finallyblock, int lineno) + { + Node trynode = (Node)tryblock; + + // short circuit + if (trynode.getType() == TokenStream.BLOCK && !trynode.hasChildren()) + return trynode; + + Node pn = new Node(TokenStream.TRY, trynode, new Integer(lineno)); + Node catchNodes = (Node)catchblocks; + boolean hasCatch = catchNodes.hasChildren(); + boolean hasFinally = false; + Node finallyNode = null; + Node finallyTarget = null; + if (finallyblock != null) { + finallyNode = (Node)finallyblock; + hasFinally = (finallyNode.getType() != TokenStream.BLOCK + || finallyNode.hasChildren()); + if (hasFinally) { + // make a TARGET for the finally that the tcf node knows about + finallyTarget = new Node(TokenStream.TARGET); + pn.putProp(Node.FINALLY_PROP, finallyTarget); + + // add jsr finally to the try block + Node jsrFinally = new Node(TokenStream.JSR); + jsrFinally.putProp(Node.TARGET_PROP, finallyTarget); + pn.addChildToBack(jsrFinally); + } + } + + // short circuit + if (!hasFinally && !hasCatch) // bc finally might be an empty block... + return trynode; + + Node endTarget = new Node(TokenStream.TARGET); + Node GOTOToEnd = new Node(TokenStream.GOTO); + GOTOToEnd.putProp(Node.TARGET_PROP, endTarget); + pn.addChildToBack(GOTOToEnd); + + if (hasCatch) { + /* + * + Given + + try { + throw 3; + } catch (e: e instanceof Object) { + print("object"); + } catch (e2) { + print(e2); + } + + rewrite as + + try { + throw 3; + } catch (x) { + o = newScope(); + o.e = x; + with (o) { + if (e instanceof Object) { + print("object"); + } + } + o2 = newScope(); + o2.e2 = x; + with (o2) { + if (true) { + print(e2); + } + } + } + */ + // make a TARGET for the catch that the tcf node knows about + Node catchTarget = new Node(TokenStream.TARGET); + pn.putProp(Node.TARGET_PROP, catchTarget); + // mark it + pn.addChildToBack(catchTarget); + + // get the exception object and store it in a temp + Node exn = createNewLocal(new Node(TokenStream.VOID)); + pn.addChildToBack(new Node(TokenStream.POP, exn)); + + Node endCatch = new Node(TokenStream.TARGET); + + // add [jsr finally?] goto end to each catch block + // expects catchNode children to be (cond block) pairs. + Node cb = catchNodes.getFirstChild(); + while (cb != null) { + Node catchStmt = new Node(TokenStream.BLOCK); + int catchLineNo = ((Integer)cb.getDatum()).intValue(); + + Node name = cb.getFirstChild(); + Node cond = name.getNextSibling(); + Node catchBlock = cond.getNextSibling(); + cb.removeChild(name); + cb.removeChild(cond); + cb.removeChild(catchBlock); + + Node newScope = createNewLocal(new Node(TokenStream.NEWSCOPE)); + Node initScope = new Node(TokenStream.SETPROP, newScope, + new Node(TokenStream.STRING, + name.getString()), + createUseLocal(exn)); + catchStmt.addChildToBack(new Node(TokenStream.POP, initScope)); + + catchBlock.addChildToBack(new Node(TokenStream.LEAVEWITH)); + Node GOTOToEndCatch = new Node(TokenStream.GOTO); + GOTOToEndCatch.putProp(Node.TARGET_PROP, endCatch); + catchBlock.addChildToBack(GOTOToEndCatch); + + Node ifStmt = (Node) createIf(cond, catchBlock, null, catchLineNo); + // Try..catch produces "with" code in order to limit + // the scope of the exception object. + // OPT: We should be able to figure out the correct + // scoping at compile-time and avoid the + // runtime overhead. + Node withStmt = (Node) createWith(createUseLocal(newScope), + ifStmt, catchLineNo); + catchStmt.addChildToBack(withStmt); + + pn.addChildToBack(catchStmt); + + // move to next cb + cb = cb.getNextSibling(); + } + + // Generate code to rethrow if no catch clause was executed + Node rethrow = new Node(TokenStream.THROW, createUseLocal(exn)); + pn.addChildToBack(rethrow); + + pn.addChildToBack(endCatch); + // add a JSR finally if needed + if (hasFinally) { + Node jsrFinally = new Node(TokenStream.JSR); + jsrFinally.putProp(Node.TARGET_PROP, finallyTarget); + pn.addChildToBack(jsrFinally); + Node GOTO = new Node(TokenStream.GOTO); + GOTO.putProp(Node.TARGET_PROP, endTarget); + pn.addChildToBack(GOTO); + } + } + + if (hasFinally) { + pn.addChildToBack(finallyTarget); + Node returnTemp = createNewLocal(new Node(TokenStream.VOID)); + Node popAndMake = new Node(TokenStream.POP, returnTemp); + pn.addChildToBack(popAndMake); + pn.addChildToBack(finallyNode); + Node ret = createUseLocal(returnTemp); + + // add the magic prop that makes it output a RET + ret.putProp(Node.TARGET_PROP, Boolean.TRUE); + pn.addChildToBack(ret); + } + pn.addChildToBack(endTarget); + return pn; + } + + /** + * Throw, Return, Label, Break and Continue are defined in ASTFactory. + */ + + /** + * With + */ + public Object createWith(Object obj, Object body, int lineno) { + Node result = new Node(TokenStream.BLOCK, new Integer(lineno)); + result.addChildToBack(new Node(TokenStream.ENTERWITH, (Node)obj)); + Node bodyNode = new Node(TokenStream.WITH, (Node) body, + new Integer(lineno)); + result.addChildrenToBack(bodyNode); + result.addChildToBack(new Node(TokenStream.LEAVEWITH)); + return result; + } + + /** + * Array Literal + *
createArrayLiteral rewrites its argument as array creation + * plus a series of array element entries, so later compiler + * stages don't need to know about array literals. + */ + public Object createArrayLiteral(Object obj) { + Node array; + Node result; + array = result = new Node(TokenStream.NEW, + new Node(TokenStream.NAME, "Array")); + Node temp = createNewTemp(result); + result = temp; + + java.util.Enumeration children = ((Node) obj).getChildIterator(); + + Node elem = null; + int i = 0; + while (children.hasMoreElements()) { + elem = (Node) children.nextElement(); + if (elem.getType() == TokenStream.PRIMARY && + elem.getInt() == TokenStream.UNDEFINED) + { + i++; + continue; + } + Node addelem = new Node(TokenStream.SETELEM, createUseTemp(temp), + new Node(TokenStream.NUMBER, + new Integer(i)), + elem); + i++; + result = new Node(TokenStream.COMMA, result, addelem); + } + + /* + * If the version is 120, then new Array(4) means create a new + * array with 4 as the first element. In this case, we might + * need to explicitly check against trailing undefined + * elements in the array literal, and set the length manually + * if these occur. Otherwise, we can add an argument to the + * node specifying new Array() to provide the array length. + * (Which will make Array optimizations involving allocating a + * Java array to back the javascript array work better.) + */ + if (Context.getContext().getLanguageVersion() == Context.VERSION_1_2) { + /* When last array element is empty, we need to set the + * length explicitly, because we can't depend on SETELEM + * to do it for us - because empty [,,] array elements + * never set anything at all. */ + if (elem != null && + elem.getType() == TokenStream.PRIMARY && + elem.getInt() == TokenStream.UNDEFINED) + { + Node setlength = new Node(TokenStream.SETPROP, + createUseTemp(temp), + new Node(TokenStream.STRING, + "length"), + new Node(TokenStream.NUMBER, + new Integer(i))); + result = new Node(TokenStream.COMMA, result, setlength); + } + } else { + array.addChildToBack(new Node(TokenStream.NUMBER, + new Integer(i))); + } + return new Node(TokenStream.COMMA, result, createUseTemp(temp)); + } + + /** + * Object Literals + *
createObjectLiteral rewrites its argument as object + * creation plus object property entries, so later compiler + * stages don't need to know about object literals. + */ + public Object createObjectLiteral(Object obj) { + Node result = new Node(TokenStream.NEW, new Node(TokenStream.NAME, + "Object")); + Node temp = createNewTemp(result); + result = temp; + + java.util.Enumeration children = ((Node) obj).getChildIterator(); + + while (children.hasMoreElements()) { + Node elem = (Node)children.nextElement(); + + int op = (elem.getType() == TokenStream.NAME) + ? TokenStream.SETPROP + : TokenStream.SETELEM; + Node addelem = new Node(op, createUseTemp(temp), + elem, (Node)children.nextElement()); + result = new Node(TokenStream.COMMA, result, addelem); + } + return new Node(TokenStream.COMMA, result, createUseTemp(temp)); + } + + /** + * Regular expressions + */ + public Object createRegExp(String string, String flags) { + return flags.length() == 0 + ? new Node(TokenStream.OBJECT, + new Node(TokenStream.STRING, string)) + : new Node(TokenStream.OBJECT, + new Node(TokenStream.STRING, string), + new Node(TokenStream.STRING, flags)); + } + + /** + * If statement + */ + public Object createIf(Object cond, Object ifTrue, Object ifFalse, + int lineno) + { + Node result = new Node(TokenStream.BLOCK, new Integer(lineno)); + Node ifNotTarget = new Node(TokenStream.TARGET); + Node IFNE = new Node(TokenStream.IFNE, (Node) cond); + IFNE.putProp(Node.TARGET_PROP, ifNotTarget); + + result.addChildToBack(IFNE); + result.addChildrenToBack((Node)ifTrue); + + if (ifFalse != null) { + Node GOTOToEnd = new Node(TokenStream.GOTO); + Node endTarget = new Node(TokenStream.TARGET); + GOTOToEnd.putProp(Node.TARGET_PROP, endTarget); + result.addChildToBack(GOTOToEnd); + result.addChildToBack(ifNotTarget); + result.addChildrenToBack((Node)ifFalse); + result.addChildToBack(endTarget); + } else { + result.addChildToBack(ifNotTarget); + } + + return result; + } + + public Object createTernary(Object cond, Object ifTrue, Object ifFalse) { + return createIf(cond, ifTrue, ifFalse, -1); + } + + /** + * Unary + */ + public Object createUnary(int nodeType, Object child) { + Node childNode = (Node) child; + if (nodeType == TokenStream.DELPROP) { + int childType = childNode.getType(); + Node left; + Node right; + if (childType == TokenStream.NAME) { + // Transform Delete(Name "a") + // to Delete(Bind("a"), String("a")) + childNode.setType(TokenStream.BINDNAME); + left = childNode; + right = childNode.cloneNode(); + right.setType(TokenStream.STRING); + } else if (childType == TokenStream.GETPROP || + childType == TokenStream.GETELEM) + { + left = childNode.getFirstChild(); + right = childNode.getLastChild(); + childNode.removeChild(left); + childNode.removeChild(right); + } else { + return new Node(TokenStream.PRIMARY, + new Integer(TokenStream.TRUE)); + } + return new Node(nodeType, left, right); + } + return new Node(nodeType, childNode); + } + + public Object createUnary(int nodeType, int nodeOp, Object child) { + Node childNode = (Node) child; + int childType = childNode.getType(); + if (nodeOp == TokenStream.TYPEOF && + childType == TokenStream.NAME) + { + childNode.setType(TokenStream.TYPEOF); + return childNode; + } + + if (nodeType == TokenStream.INC || nodeType == TokenStream.DEC) { + + if (!hasSideEffects(childNode) + && (nodeOp == TokenStream.POST) + && (childType == TokenStream.NAME + || childType == TokenStream.GETPROP + || childType == TokenStream.GETELEM)) + { + // if it's not a LHS type, createAssignment (below) will throw + // an exception. + return new Node(nodeType, childNode); + } + + /* + * Transform INC/DEC ops to +=1, -=1, + * expecting later optimization of all +/-=1 cases to INC, DEC. + */ + // we have to use Double for now, because + // 0.0 and 1.0 are stored as dconst_[01], + // and using a Float creates a stack mismatch. + Node rhs = (Node) createNumber(new Double(1.0)); + + return createAssignment(nodeType == TokenStream.INC + ? TokenStream.ADD + : TokenStream.SUB, + childNode, + rhs, + ScriptRuntime.NumberClass, + nodeOp == TokenStream.POST); + } + + Node result = new Node(nodeType, new Integer(nodeOp)); + result.addChildToBack((Node)child); + return result; + } + + /** + * Binary + */ + public Object createBinary(int nodeType, Object left, Object right) { + Node temp; + switch (nodeType) { + + case TokenStream.DOT: + nodeType = TokenStream.GETPROP; + Node idNode = (Node) right; + idNode.setType(TokenStream.STRING); + String id = idNode.getString(); + if (id.equals("__proto__") || id.equals("__parent__")) { + Node result = new Node(nodeType, (Node) left); + result.putProp(Node.SPECIAL_PROP_PROP, id); + return result; + } + break; + + case TokenStream.LB: + // OPT: could optimize to GETPROP iff string can't be a number + nodeType = TokenStream.GETELEM; + break; +/* + case TokenStream.AND: + temp = createNewTemp((Node) left); + return createTernary(temp, right, createUseTemp(temp)); + + case TokenStream.OR: + temp = createNewTemp((Node) left); + return createTernary(temp, createUseTemp(temp), right); +*/ + } + return new Node(nodeType, (Node)left, (Node)right); + } + + public Object createBinary(int nodeType, int nodeOp, Object left, + Object right) + { + if (nodeType == TokenStream.ASSIGN) { + return createAssignment(nodeOp, (Node) left, (Node) right, + null, false); + } + return new Node(nodeType, (Node) left, (Node) right, + new Integer(nodeOp)); + } + + public Object createAssignment(int nodeOp, Node left, Node right, + Class convert, boolean postfix) + { + int nodeType = left.getType(); + String idString; + Node id = null; + switch (nodeType) { + case TokenStream.NAME: + return createSetName(nodeOp, left, right, convert, postfix); + + case TokenStream.GETPROP: + idString = (String) left.getProp(Node.SPECIAL_PROP_PROP); + if (idString != null) + id = new Node(TokenStream.STRING, idString); + /* fall through */ + case TokenStream.GETELEM: + if (id == null) + id = left.getLastChild(); + return createSetProp(nodeType, nodeOp, left.getFirstChild(), + id, right, convert, postfix); + default: + // TODO: This should be a ReferenceError--but that's a runtime + // exception. Should we compile an exception into the code? + reportError("msg.bad.lhs.assign"); + return left; + } + } + + private Node createConvert(Class toType, Node expr) { + if (toType == null) + return expr; + Node result = new Node(TokenStream.CONVERT, expr); + result.putProp(Node.TYPE_PROP, ScriptRuntime.NumberClass); + return result; + } + + private Object createSetName(int nodeOp, Node left, Node right, + Class convert, boolean postfix) + { + if (nodeOp == TokenStream.NOP) { + left.setType(TokenStream.BINDNAME); + return new Node(TokenStream.SETNAME, left, right); + } + + String s = left.getString(); + + if (s.equals("__proto__") || s.equals("__parent__")) { + Node result = new Node(TokenStream.SETPROP, left, right); + result.putProp(Node.SPECIAL_PROP_PROP, s); + return result; + } + + Node opLeft = new Node(TokenStream.NAME, s); + if (convert != null) + opLeft = createConvert(convert, opLeft); + if (postfix) + opLeft = createNewTemp(opLeft); + Node op = new Node(nodeOp, opLeft, right); + + Node lvalueLeft = new Node(TokenStream.BINDNAME, s); + Node result = new Node(TokenStream.SETNAME, lvalueLeft, op); + if (postfix) { + result = new Node(TokenStream.COMMA, result, + createUseTemp(opLeft)); + } + return result; + } + + public Node createNewTemp(Node n) { + int type = n.getType(); + if (type == TokenStream.STRING || type == TokenStream.NUMBER) { + // Optimization: clone these values rather than storing + // and loading from a temp + return n; + } + Node result = new Node(TokenStream.NEWTEMP, n); + return result; + } + + public Node createUseTemp(Node newTemp) { + int type = newTemp.getType(); + if (type == TokenStream.NEWTEMP) { + Node result = new Node(TokenStream.USETEMP); + result.putProp(Node.TEMP_PROP, newTemp); + Integer n = (Integer) newTemp.getProp(Node.USES_PROP); + if (n == null) { + n = new Integer(1); + } else { + if (n.intValue() < Integer.MAX_VALUE) + n = new Integer(n.intValue() + 1); + } + newTemp.putProp(Node.USES_PROP, n); + return result; + } + return newTemp.cloneNode(); + } + + public Node createNewLocal(Node n) { + Node result = new Node(TokenStream.NEWLOCAL, n); + return result; + } + + public Node createUseLocal(Node newLocal) { + int type = newLocal.getType(); + if (type == TokenStream.NEWLOCAL) { + Node result = new Node(TokenStream.USELOCAL); + result.putProp(Node.LOCAL_PROP, newLocal); + return result; + } + return newLocal.cloneNode(); // what's this path for ? + } + + public static boolean hasSideEffects(Node exprTree) { + switch (exprTree.getType()) { + case TokenStream.INC: + case TokenStream.DEC: + case TokenStream.SETPROP: + case TokenStream.SETELEM: + case TokenStream.SETNAME: + case TokenStream.CALL: + case TokenStream.NEW: + return true; + default: + Node child = exprTree.getFirstChild(); + while (child != null) { + if (hasSideEffects(child)) + return true; + else + child = child.getNextSibling(); + } + break; + } + return false; + } + + private Node createSetProp(int nodeType, int nodeOp, Node obj, Node id, + Node expr, Class convert, boolean postfix) + { + int type = nodeType == TokenStream.GETPROP + ? TokenStream.SETPROP + : TokenStream.SETELEM; + + Object datum = id.getDatum(); + if (type == TokenStream.SETPROP && datum != null && + datum instanceof String) + { + String s = (String) datum; + if (s.equals("__proto__") || s.equals("__parent__")) { + Node result = new Node(type, obj, expr); + result.putProp(Node.SPECIAL_PROP_PROP, s); + return result; + } + } + + if (nodeOp == TokenStream.NOP) + return new Node(type, obj, id, expr); +/* +* If the RHS expression could modify the LHS we have +* to construct a temporary to hold the LHS context +* prior to running the expression. Ditto, if the id +* expression has side-effects. +* +* XXX If the hasSideEffects tests take too long, we +* could make this an optimizer-only transform +* and always do the temp assignment otherwise. +* +*/ + Node tmp1, tmp2, opLeft; + if (hasSideEffects(expr) + || hasSideEffects(id) + || (obj.getType() != TokenStream.NAME)) { + tmp1 = createNewTemp(obj); + Node useTmp1 = createUseTemp(tmp1); + + tmp2 = createNewTemp(id); + Node useTmp2 = createUseTemp(tmp2); + + opLeft = new Node(nodeType, useTmp1, useTmp2); + } else { + tmp1 = obj.cloneNode(); + tmp2 = id.cloneNode(); + opLeft = new Node(nodeType, obj, id); + } + + if (convert != null) + opLeft = createConvert(convert, opLeft); + if (postfix) + opLeft = createNewTemp(opLeft); + Node op = new Node(nodeOp, opLeft, expr); + + Node result = new Node(type, tmp1, tmp2, op); + if (postfix) { + result = new Node(TokenStream.COMMA, result, + createUseTemp(opLeft)); + } + + return result; + } + + private void reportError(String msgResource) { + + if (scope != null) + throw NativeGlobal.constructError( + Context.getContext(), "SyntaxError", + ScriptRuntime.getMessage0(msgResource), + scope); + else { + String message = Context.getMessage0(msgResource); + Context.reportError(message, ts.getSourceName(), ts.getLineno(), + ts.getLine(), ts.getOffset()); + } + } + + // Only needed to get file/line information. Could create an interface + // that TokenStream implements if we want to make the connection less + // direct. + private TokenStream ts; + + // Only needed to pass to the Erorr exception constructors + private Scriptable scope; +} + diff --git a/src/org/mozilla/javascript/IdFunction.java b/src/org/mozilla/javascript/IdFunction.java new file mode 100644 index 0000000..ec5dc3e --- /dev/null +++ b/src/org/mozilla/javascript/IdFunction.java @@ -0,0 +1,164 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * The contents of this file are subject to the Netscape Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express oqr + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is Rhino code, released + * May 6, 1999. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1997-1999 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * Igor Bukanov + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (the "GPL"), in which case the + * provisions of the GPL are applicable instead of those above. + * If you wish to allow use of your version of this file only + * under the terms of the GPL and not to allow others to use your + * version of this file under the NPL, indicate your decision by + * deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete + * the provisions above, a recipient may use your version of this + * file under either the NPL or the GPL. + */ + +// API class + +package org.mozilla.javascript; + +public class IdFunction extends BaseFunction +{ + public static final int FUNCTION_ONLY = 0; + + public static final int CONSTRUCTOR_ONLY = 1; + + public static final int FUNCTION_AND_CONSTRUCTOR = 2; + + public IdFunction(IdFunctionMaster master, String name, int id) { + this.functionName = name; + this.master = master; + this.methodId = id; + } + + public final int functionType() { + return functionType; + } + + public void setFunctionType(int type) { + functionType = type; + } + + public Scriptable getPrototype() { + // Lazy initialization of prototype: for native functions this + // may not be called at all + Scriptable proto = super.getPrototype(); + if (proto == null) { + proto = getFunctionPrototype(getParentScope()); + setPrototype(proto); + } + return proto; + } + + public Object call(Context cx, Scriptable scope, Scriptable thisObj, + Object[] args) + throws JavaScriptException + { + if (functionType != CONSTRUCTOR_ONLY) { + return master.execMethod(methodId, this, cx, scope, thisObj, args); + } + else { + return Undefined.instance; + } + } + + public Scriptable construct(Context cx, Scriptable scope, Object[] args) + throws JavaScriptException + { + if (functionType != FUNCTION_ONLY) { + // It is program error not to return Scriptable from constructor + Scriptable result = (Scriptable)master.execMethod(methodId, this, + cx, scope, + null, args); + postConstruction(result); + return result; + } + else { + return Undefined.instance; + } + } + + public String decompile(Context cx, int indent, boolean justbody) { + StringBuffer sb = new StringBuffer(); + if (!justbody) { + sb.append("function "); + sb.append(getFunctionName()); + sb.append("() { "); + } + sb.append("[native code for "); + if (master instanceof Scriptable) { + Scriptable smaster = (Scriptable)master; + sb.append(smaster.getClassName()); + sb.append('.'); + } + sb.append(getFunctionName()); + sb.append(", arity="); + sb.append(getArity()); + sb.append(justbody ? "]\n" : "] }\n"); + return sb.toString(); + } + + public int getArity() { + int arity = master.methodArity(methodId); + if (arity < 0) { + throw onBadMethodId(master, methodId); + } + return arity; + } + + public int getLength() { return getArity(); } + + /** Prepare to be used as constructor . + ** @param scope constructor scope + ** @param prototype DontEnum, DontDelete, ReadOnly prototype property + ** of the constructor */ + public void initAsConstructor(Scriptable scope, Scriptable prototype) { + setFunctionType(FUNCTION_AND_CONSTRUCTOR); + setParentScope(scope); + setImmunePrototypeProperty(prototype); + } + + static RuntimeException onBadMethodId(IdFunctionMaster master, int id) { + // It is program error to call id-like methods for unknown or + // non-function id + return new RuntimeException("BAD FUNCTION ID="+id+" MASTER="+master); + } + + // Copied from NativeFunction.construct + private void postConstruction(Scriptable newObj) { + if (newObj.getPrototype() == null) { + newObj.setPrototype(getClassPrototype()); + } + if (newObj.getParentScope() == null) { + Scriptable parent = getParentScope(); + if (newObj != parent) { + newObj.setParentScope(parent); + } + } + } + + protected IdFunctionMaster master; + protected int methodId; + + protected int functionType = FUNCTION_ONLY; +} diff --git a/src/org/mozilla/javascript/IdFunctionMaster.java b/src/org/mozilla/javascript/IdFunctionMaster.java new file mode 100644 index 0000000..0a04e58 --- /dev/null +++ b/src/org/mozilla/javascript/IdFunctionMaster.java @@ -0,0 +1,54 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * The contents of this file are subject to the Netscape Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express oqr + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is Rhino code, released + * May 6, 1999. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1997-1999 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * Igor Bukanov + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (the "GPL"), in which case the + * provisions of the GPL are applicable instead of those above. + * If you wish to allow use of your version of this file only + * under the terms of the GPL and not to allow others to use your + * version of this file under the NPL, indicate your decision by + * deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete + * the provisions above, a recipient may use your version of this + * file under either the NPL or the GPL. + */ + +package org.mozilla.javascript; + +/** Master for id-based functions that knows their properties and how to + ** execute them + */ +public interface IdFunctionMaster { + /** 'thisObj' will be null if invoked as constructor, in which case + ** instance of Scriptable should be returned */ + public Object execMethod(int methodId, IdFunction function, + Context cx, Scriptable scope, + Scriptable thisObj, Object[] args) + throws JavaScriptException; + + /** Get arity or defined argument count for method with given id. + ** Should return -1 if methodId is not known or can not be used + ** with execMethod call */ + public int methodArity(int methodId); +} + diff --git a/src/org/mozilla/javascript/IdScriptable.java b/src/org/mozilla/javascript/IdScriptable.java new file mode 100644 index 0000000..84fbc92 --- /dev/null +++ b/src/org/mozilla/javascript/IdScriptable.java @@ -0,0 +1,577 @@ +/* -*- Mode: java; tab-width: 4; indent-tabs-mode: 1; c-basic-offset: 4 -*- + * + * The contents of this file are subject to the Netscape Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express oqr + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is Rhino code, released + * May 6, 1999. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1997-1999 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * Igor Bukanov + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (the "GPL"), in which case the + * provisions of the GPL are applicable instead of those above. + * If you wish to allow use of your version of this file only + * under the terms of the GPL and not to allow others to use your + * version of this file under the NPL, indicate your decision by + * deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete + * the provisions above, a recipient may use your version of this + * file under either the NPL or the GPL. + */ + +package org.mozilla.javascript; + +/** +Base class for native object implementation that uses IdFunction to export its methods to script via .prototype object. + +Any descendant should implement at least the following methods: + mapNameToId + getIdName + execMethod + methodArity + +To define non-function properties, the descendant should customize + getIdValue + setIdValue + getIdDefaultAttributes + maxInstanceId +to get/set property value and provide its default attributes. + +To customize initializition of constructor and protype objects, descendant +may override scopeInit or fillConstructorProperties methods. + +*/ +public abstract class IdScriptable extends ScriptableObject + implements IdFunctionMaster +{ + /** NULL_TAG can be used to distinguish between uninitialized and null + ** values + */ + protected static final Object NULL_TAG = new Object(); + + public IdScriptable() { + activateIdMap(maxInstanceId()); + } + + public boolean has(String name, Scriptable start) { + if (maxId != 0) { + int id = mapNameToId(name); + if (id != 0) { + return hasValue(id); + } + } + return super.has(name, start); + } + + public Object get(String name, Scriptable start) { + if (CACHE_NAMES) { + int maxId = this.maxId; + L:if (maxId != 0) { + Object[] data = idMapData; + if (data == null) { + int id = mapNameToId(name); + if (id != 0) { + return getIdValue(id); + } + } + else { + int id = lastIdCache; + if (data[id - 1 + maxId] != name) { + id = mapNameToId(name); + if (id == 0) { break L; } + data[id - 1 + maxId] = name; + lastIdCache = id; + } + Object value = data[id - 1]; + if (value == null) { + value = getIdValue(id); + } + else if (value == NULL_TAG) { + value = null; + } + return value; + } + } + } + else { + if (maxId != 0) { + int id = mapNameToId(name); + if (id != 0) { + Object[] data = idMapData; + if (data == null) { + return getIdValue(id); + } + else { + Object value = data[id - 1]; + if (value == null) { + value = getIdValue(id); + } + else if (value == NULL_TAG) { + value = null; + } + return value; + } + } + } + } + return super.get(name, start); + } + + public void put(String name, Scriptable start, Object value) { + if (maxId != 0) { + int id = mapNameToId(name); + if (id != 0) { + int attr = getAttributes(id); + if ((attr & READONLY) == 0) { + if (start == this) { + setIdValue(id, value); + } + else { + start.put(name, start, value); + } + } + return; + } + } + super.put(name, start, value); + } + + public void delete(String name) { + if (maxId != 0) { + int id = mapNameToId(name); + if (id != 0) { + // Let the super class to throw exceptions for sealed objects + if (!isSealed()) { + int attr = getAttributes(id); + if ((attr & PERMANENT) == 0) { + deleteIdValue(id); + } + return; + } + } + } + super.delete(name); + } + + public int getAttributes(String name, Scriptable start) + throws PropertyException + { + if (maxId != 0) { + int id = mapNameToId(name); + if (id != 0) { + if (hasValue(id)) { + return getAttributes(id); + } + // For ids with deleted values super will throw exceptions + } + } + return super.getAttributes(name, start); + } + + public void setAttributes(String name, Scriptable start, + int attributes) + throws PropertyException + { + if (maxId != 0) { + int id = mapNameToId(name); + if (id != 0) { + if (hasValue(id)) { + synchronized (this) { + setAttributes(id, attributes); + } + return; + } + // For ids with deleted values super will throw exceptions + } + } + super.setAttributes(name, start, attributes); + } + + synchronized void addPropertyAttribute(int attribute) { + extraIdAttributes |= (byte)attribute; + super.addPropertyAttribute(attribute); + } + + /** + * Redefine ScriptableObject.defineProperty to allow changing + * values/attributes of id-based properties unless + * getIdDefaultAttributes contains the READONLY attribute. + * @see #getIdDefaultAttributes + * @see org.mozilla.javascript.ScriptableObject#defineProperty + */ + public void defineProperty(String propertyName, Object value, + int attributes) + { + if (maxId != 0) { + int id = mapNameToId(propertyName); + if (id != 0) { + int default_attributes = getIdDefaultAttributes(id); + if ((default_attributes & READONLY) != 0) { + // It is a bug to redefine id with readonly attributes + throw new RuntimeException + ("Attempt to redefine read-only id " + propertyName); + } + setAttributes(id, attributes); + setIdValue(id, value); + return; + } + } + super.defineProperty(propertyName, value, attributes); + } + + Object[] getIds(boolean getAll) { + Object[] result = super.getIds(getAll); + + if (maxId != 0) { + Object[] ids = null; + int count = 0; + + for (int id = maxId; id != 0; --id) { + if (hasValue(id)) { + if (getAll || (getAttributes(id) & DONTENUM) == 0) { + if (count == 0) { + // Need extra room for nor more then [1..id] names + ids = new Object[id]; + } + ids[count++] = getIdName(id); + } + } + } + if (count != 0) { + if (result.length == 0 && ids.length == count) { + result = ids; + } + else { + Object[] tmp = new Object[result.length + count]; + System.arraycopy(result, 0, tmp, 0, result.length); + System.arraycopy(ids, 0, tmp, result.length, count); + result = tmp; + } + } + } + return result; + } + + /** Return maximum id number that should be present in each instance. */ + protected int maxInstanceId() { return 0; } + + /** + * Map name to id of prototype or instance property. + * Should return 0 if not found + */ + protected abstract int mapNameToId(String name); + + /** Map id back to property name it defines. + */ + protected abstract String getIdName(int id); + + /** Get default attributes for id. + ** Default implementation return DONTENUM that is the standard attribute + ** for core EcmaScript function. Typically descendants need to overwrite + ** this for non-function attributes like length to return + ** DONTENUM | READONLY | PERMANENT or DONTENUM | PERMANENT + */ + protected int getIdDefaultAttributes(int id) { + return DONTENUM; + } + + /** Check if id value exists. + ** Default implementation always returns true */ + protected boolean hasIdValue(int id) { + return true; + } + + /** Get id value. + ** If id value is constant, descendant can call cacheIdValue to store + ** value in the permanent cache. + ** Default implementation creates IdFunction instance for given id + ** and cache its value + */ + protected Object getIdValue(int id) { + IdFunction f = newIdFunction(id); + f.setParentScope(getParentScope()); + return cacheIdValue(id, f); + } + + /** + * Set id value. + * IdScriptable never calls this method if result of + * getIdDefaultAttributes(id) contains READONLY attribute. + * Descendants can overwrite this method to provide custom handler for + * property assignments. + */ + protected void setIdValue(int id, Object value) { + synchronized (this) { + ensureIdData()[id - 1] = (value != null) ? value : NULL_TAG; + } + } + + /** + * Store value in permanent cache unless value was already assigned to id. + * After this call IdScriptable never calls hasIdValue and getIdValue + * for the given id. + */ + protected Object cacheIdValue(int id, Object value) { + synchronized (this) { + Object[] data = ensureIdData(); + Object curValue = data[id - 1]; + if (curValue == null) { + data[id - 1] = (value != null) ? value : NULL_TAG; + } + else { + value = curValue; + } + } + return value; + } + + /** + * Delete value represented by id so hasIdValue return false. + * IdScriptable never calls this method if result of + * getIdDefaultAttributes(id) contains PERMANENT attribute. + * Descendants can overwrite this method to provide custom handler for + * property delete. + */ + protected void deleteIdValue(int id) { + synchronized (this) { + ensureIdData()[id - 1] = NOT_FOUND; + } + } + + /** 'thisObj' will be null if invoked as constructor, in which case + ** instance of Scriptable should be returned. */ + public Object execMethod(int methodId, IdFunction function, + Context cx, Scriptable scope, + Scriptable thisObj, Object[] args) + throws JavaScriptException + { + throw IdFunction.onBadMethodId(this, methodId); + } + + /** Get arity or defined argument count for method with given id. + ** Should return -1 if methodId is not known or can not be used + ** with execMethod call. */ + public int methodArity(int methodId) { + return -1; + } + + /** Activate id support with the given maximum id */ + protected void activateIdMap(int maxId) { + this.maxId = maxId; + } + + /** Sets whether newly constructed function objects should be sealed */ + protected void setSealFunctionsFlag(boolean sealed) { + setSetupFlag(SEAL_FUNCTIONS_FLAG, sealed); + } + + /** + * Set parameters of function properties. + * Currently only determines whether functions should use dynamic scope. + * @param cx context to read function parameters. + * + * @see org.mozilla.javascript.Context#hasCompileFunctionsWithDynamicScope + */ + protected void setFunctionParametrs(Context cx) { + setSetupFlag(USE_DYNAMIC_SCOPE_FLAG, + cx.hasCompileFunctionsWithDynamicScope()); + } + + private void setSetupFlag(int flag, boolean value) { + setupFlags = (byte)(value ? setupFlags | flag : setupFlags & ~flag); + } + + /** + * Prepare this object to serve as the prototype property of constructor + * object with name getClassName() defined in + * scope. + * @param maxId maximum id available in prototype object + * @param cx current context + * @param scope object to define constructor in. + * @param sealed indicates whether object and all its properties should + * be sealed + */ + public void addAsPrototype(int maxId, Context cx, Scriptable scope, + boolean sealed) + { + activateIdMap(maxId); + + setSealFunctionsFlag(sealed); + setFunctionParametrs(cx); + + int constructorId = mapNameToId("constructor"); + if (constructorId == 0) { + // It is a bug to call this function without id for constructor + throw new RuntimeException("No id for constructor property"); + } + + IdFunction ctor = newIdFunction(constructorId); + ctor.initAsConstructor(scope, this); + fillConstructorProperties(cx, ctor, sealed); + if (sealed) { + ctor.sealObject(); + ctor.addPropertyAttribute(READONLY); + } + + setParentScope(ctor); + setPrototype(getObjectPrototype(scope)); + cacheIdValue(constructorId, ctor); + + if (sealed) { + sealObject(); + } + + defineProperty(scope, getClassName(), ctor, ScriptableObject.DONTENUM); + } + + protected void fillConstructorProperties + (Context cx, IdFunction ctor, boolean sealed) + { + } + + protected void addIdFunctionProperty + (Scriptable obj, int id, boolean sealed) + { + IdFunction f = newIdFunction(id); + if (sealed) { f.sealObject(); } + defineProperty(obj, getIdName(id), f, DONTENUM); + } + + /** + * Utility method for converting target object into native this. + * Possible usage would be to have a private function like realThis: + *

+       private NativeSomething realThis(Scriptable thisObj,
+                                        IdFunction f, boolean readOnly)
+       {
+           while (!(thisObj instanceof NativeSomething)) {
+               thisObj = nextInstanceCheck(thisObj, f, readOnly);
+           }
+           return (NativeSomething)thisObj;
+       }
+    * 
+ * Note that although such function can be implemented universally via + * java.lang.Class.isInstance(), it would be much more slower. + * @param readOnly specify if the function f does not change state of object. + * @return Scriptable object suitable for a check by the instanceof operator. + * @throws RuntimeException if no more instanceof target can be found + */ + protected Scriptable nextInstanceCheck(Scriptable thisObj, + IdFunction f, + boolean readOnly) + { + if (readOnly && 0 != (setupFlags & USE_DYNAMIC_SCOPE_FLAG)) { + // for read only functions under dynamic scope look prototype chain + thisObj = thisObj.getPrototype(); + if (thisObj != null) { return thisObj; } + } + throw NativeGlobal.typeError1("msg.incompat.call", + f.getFunctionName(), f); + } + + protected IdFunction newIdFunction(int id) { + IdFunction f = new IdFunction(this, getIdName(id), id); + if (0 != (setupFlags & SEAL_FUNCTIONS_FLAG)) { f.sealObject(); } + return f; + } + + protected final Object wrap_double(double x) { + return (x == x) ? new Double(x) : ScriptRuntime.NaNobj; + } + + protected final Object wrap_int(int x) { + byte b = (byte)x; + if (b == x) { return new Byte(b); } + return new Integer(x); + } + + protected final Object wrap_long(long x) { + int i = (int)x; + if (i == x) { return wrap_int(i); } + return new Long(x); + } + + protected final Object wrap_boolean(boolean x) { + return x ? Boolean.TRUE : Boolean.FALSE; + } + + private boolean hasValue(int id) { + Object value; + Object[] data = idMapData; + if (data == null || (value = data[id - 1]) == null) { + return hasIdValue(id); + } + else { + return value != NOT_FOUND; + } + } + + // Must be called only from synchronized (this) + private Object[] ensureIdData() { + Object[] data = idMapData; + if (data == null) { + idMapData = data = new Object[CACHE_NAMES ? maxId * 2 : maxId]; + } + return data; + } + + private int getAttributes(int id) { + int attributes = getIdDefaultAttributes(id) | extraIdAttributes; + byte[] array = attributesArray; + if (array != null) { + attributes |= 0xFF & array[id - 1]; + } + return attributes; + } + + private void setAttributes(int id, int attributes) { + int defaultAttrs = getIdDefaultAttributes(id); + if ((attributes & defaultAttrs) != defaultAttrs) { + // It is a bug to set attributes to less restrictive values + // then given by defaultAttrs + throw new RuntimeException("Attempt to unset default attributes"); + } + // Store only additional bits + attributes &= ~defaultAttrs; + byte[] array = attributesArray; + if (array == null && attributes != 0) { + synchronized (this) { + array = attributesArray; + if (array == null) { + attributesArray = array = new byte[maxId]; + } + } + } + if (array != null) { + array[id - 1] = (byte)attributes; + } + } + + private int maxId; + private Object[] idMapData; + private byte[] attributesArray; + + private static final boolean CACHE_NAMES = true; + private int lastIdCache; + + private static final int USE_DYNAMIC_SCOPE_FLAG = 1 << 0; + private static final int SEAL_FUNCTIONS_FLAG = 1 << 1; + + private byte setupFlags; + private byte extraIdAttributes; +} + diff --git a/src/org/mozilla/javascript/ImporterTopLevel.java b/src/org/mozilla/javascript/ImporterTopLevel.java new file mode 100644 index 0000000..ea4540d --- /dev/null +++ b/src/org/mozilla/javascript/ImporterTopLevel.java @@ -0,0 +1,176 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * The contents of this file are subject to the Netscape Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express oqr + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is Rhino code, released + * May 6, 1999. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1999 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * Matthias Radestock + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (the "GPL"), in which case the + * provisions of the GPL are applicable instead of those above. + * If you wish to allow use of your version of this file only + * under the terms of the GPL and not to allow others to use your + * version of this file under the NPL, indicate your decision by + * deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete + * the provisions above, a recipient may use your version of this + * file under either the NPL or the GPL. + */ + +// API class + +package org.mozilla.javascript; + +import java.util.Vector; + +/** + * Class ImporterTopLevel + * + * This class defines a ScriptableObject that can be instantiated + * as a top-level ("global") object to provide functionality similar + * to Java's "import" statement. + *

+ * This class can be used to create a top-level scope using the following code: + *

+ *  Scriptable scope = new ImporterTopLevel(cx);
+ * 
+ * Then JavaScript code will have access to the following methods: + *
    + *
  • importClass - will "import" a class by making its unqualified name + * available as a property of the top-level scope + *
  • importPackage - will "import" all the classes of the package by + * searching for unqualified names as classes qualified + * by the given package. + *
+ * The following code from the shell illustrates this use: + *
+ * js> importClass(java.io.File)
+ * js> f = new File('help.txt')
+ * help.txt
+ * js> importPackage(java.util)
+ * js> v = new Vector()
+ * []
+ * 
+ * @author Norris Boyd
+ */
+public class ImporterTopLevel extends ScriptableObject {
+    
+    /**
+     * @deprecated
+     */
+    public ImporterTopLevel() {
+        init();
+    }
+
+    public ImporterTopLevel(Context cx) {
+        cx.initStandardObjects(this);
+        init();
+    }
+    
+    private void init() {
+        String[] names = { "importClass", "importPackage" };
+
+        try {
+            this.defineFunctionProperties(names, ImporterTopLevel.class,
+                                          ScriptableObject.DONTENUM);
+        } catch (PropertyException e) {
+            throw new Error();  // should never happen
+        }
+    }
+
+    public String getClassName() { 
+        return "global";
+    }
+    
+    public Object get(String name, Scriptable start) {
+        Object result = super.get(name, start);
+        if (result != NOT_FOUND) 
+            return result;
+        if (name.equals("_packages_")) 
+            return result;
+        Object plist = ScriptableObject.getProperty(start,"_packages_");
+        if (plist == NOT_FOUND) 
+            return result;
+        Context cx = Context.enter();
+        Object[] elements = cx.getElements((Scriptable)plist);
+        Context.exit();
+        for (int i=0; i < elements.length; i++) {
+            NativeJavaPackage p = (NativeJavaPackage) elements[i];
+            Object v = p.getPkgProperty(name, start, false);
+            if (v != null && !(v instanceof NativeJavaPackage)) {
+                if (result == NOT_FOUND) {
+                    result = v;
+                } else {
+                    throw Context.reportRuntimeError2(
+                        "msg.ambig.import", result.toString(), v.toString());
+                }
+            }
+        }
+        return result;
+    }
+    
+    public static void importClass(Context cx, Scriptable thisObj,
+                                   Object[] args, Function funObj) {
+        for (int i=0; i 200) {
+            NativeError ne = new NativeError();
+            ne.put("message", ne, "maximum interpreter stack depth limit exceeded");
+            cx.stackDepth--;
+            throw new EcmaError(ne, cx.interpreterSourceFile, cx.interpreterLine, 0, "");
+        }
+        cx.currentFunction = this;
+        cx.interpreterSourceFile = itsData.itsSourceFile;
+        if (itsClosure != null)
+            scope = itsClosure;
+        else if (!itsData.itsUseDynamicScope)
+            scope = getParentScope();
+
+        if (itsData.itsCheckThis) 
+            thisObj = ScriptRuntime.getThis(thisObj);
+        
+        if (itsData.itsNeedsActivation) {
+            scope = ScriptRuntime.initVarObj(cx, scope, this, thisObj, args);
+        }
+        try {
+            return Interpreter.interpret(cx, scope, thisObj, args, this,
+                                         itsData);
+        } finally {
+            if (itsData.itsNeedsActivation) {
+                ScriptRuntime.popActivation(cx);
+            }
+            cx.currentFunction = temp;
+            cx.stackDepth--;
+        }
+    }
+    
+    public boolean isFunction() {
+        return true;
+    }
+    
+    public Scriptable getScriptable() {
+        return this;
+    }
+    
+    public String getSourceName() {
+        return itsData.itsSourceFile;
+    }
+    
+    public int[] getLineNumbers() { 
+        return itsData.itsLineNumberTable.getKeys();
+    }
+    
+    public boolean placeBreakpoint(int line) { // XXX throw exn?
+        return itsData.placeBreakpoint(line);
+    }
+    
+    public boolean removeBreakpoint(int line) {
+        return itsData.removeBreakpoint(line);
+    }
+    
+    InterpreterData itsData;
+    Scriptable itsClosure;
+}
+    
diff --git a/src/org/mozilla/javascript/InterpretedScript.java b/src/org/mozilla/javascript/InterpretedScript.java
new file mode 100644
index 0000000..2c7ef2e
--- /dev/null
+++ b/src/org/mozilla/javascript/InterpretedScript.java
@@ -0,0 +1,102 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * The contents of this file are subject to the Netscape Public
+ * License Version 1.1 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.mozilla.org/NPL/
+ *
+ * Software distributed under the License is distributed on an "AS
+ * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express oqr
+ * implied. See the License for the specific language governing
+ * rights and limitations under the License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is Netscape
+ * Communications Corporation.  Portions created by Netscape are
+ * Copyright (C) 1997-1999 Netscape Communications Corporation. All
+ * Rights Reserved.
+ *
+ * Contributor(s): 
+ * Roger Lawrence
+ *
+ * Alternatively, the contents of this file may be used under the
+ * terms of the GNU Public License (the "GPL"), in which case the
+ * provisions of the GPL are applicable instead of those above.
+ * If you wish to allow use of your version of this file only
+ * under the terms of the GPL and not to allow others to use your
+ * version of this file under the NPL, indicate your decision by
+ * deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL.  If you do not delete
+ * the provisions above, a recipient may use your version of this
+ * file under either the NPL or the GPL.
+ */
+
+package org.mozilla.javascript;
+
+import org.mozilla.javascript.debug.*;
+
+import java.util.*;
+
+public class InterpretedScript extends NativeScript implements DebuggableScript {
+
+    InterpretedScript(Context cx,
+                      InterpreterData theData, 
+                      String[] argNames, short argCount)
+    {
+        itsData = theData;
+        this.argNames = argNames;
+        this.argCount = argCount;
+        functionName = "";
+        nestedFunctions = itsData.itsNestedFunctions;
+        version = (short)cx.getLanguageVersion();   
+    }
+    
+    public Object exec(Context cx, Scriptable scope)
+        throws JavaScriptException
+    {
+        return call(cx, scope, scope, null);    
+    }
+
+    public Object call(Context cx, Scriptable scope, 
+                       Scriptable thisObj, Object[] args)
+        throws JavaScriptException
+    {
+        Function temp = cx.currentFunction;
+        cx.currentFunction = this;
+        cx.interpreterSourceFile = itsData.itsSourceFile;
+        scope = ScriptRuntime.initScript(cx, scope, this, thisObj, 
+                                         itsData.itsFromEvalCode);
+        Object ret = Interpreter.interpret(cx, scope, thisObj, args, this, itsData);    
+        cx.currentFunction = temp;
+        return ret;
+    }
+    
+    public boolean isFunction() {
+        return false;
+    }
+    
+    public Scriptable getScriptable() {
+        return this;
+    }
+    
+    public String getSourceName() {
+        return itsData.itsSourceFile;
+    }
+    
+    public int[] getLineNumbers() {
+        return itsData.itsLineNumberTable.getKeys();
+    }
+    
+    public boolean placeBreakpoint(int line) { // XXX throw exn?
+        return itsData.placeBreakpoint(line);
+    }
+    
+    public boolean removeBreakpoint(int line) {
+        return itsData.removeBreakpoint(line);
+    }
+    
+    InterpreterData itsData;
+}
+
diff --git a/src/org/mozilla/javascript/Interpreter.java b/src/org/mozilla/javascript/Interpreter.java
new file mode 100644
index 0000000..37fbc73
--- /dev/null
+++ b/src/org/mozilla/javascript/Interpreter.java
@@ -0,0 +1,2514 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * The contents of this file are subject to the Netscape Public
+ * License Version 1.1 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.mozilla.org/NPL/
+ *
+ * Software distributed under the License is distributed on an "AS
+ * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express oqr
+ * implied. See the License for the specific language governing
+ * rights and limitations under the License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is Netscape
+ * Communications Corporation.  Portions created by Netscape are
+ * Copyright (C) 1997-2000 Netscape Communications Corporation. All
+ * Rights Reserved.
+ *
+ * Contributor(s): 
+ * Patrick Beard
+ * Norris Boyd
+ * Igor Bukanov
+ * Roger Lawrence
+ *
+ * Alternatively, the contents of this file may be used under the
+ * terms of the GNU Public License (the "GPL"), in which case the
+ * provisions of the GPL are applicable instead of those above.
+ * If you wish to allow use of your version of this file only
+ * under the terms of the GPL and not to allow others to use your
+ * version of this file under the NPL, indicate your decision by
+ * deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL.  If you do not delete
+ * the provisions above, a recipient may use your version of this
+ * file under either the NPL or the GPL.
+ */
+
+package org.mozilla.javascript;
+
+import java.io.*;
+import java.util.Vector;
+import java.util.Enumeration;
+
+import org.mozilla.javascript.debug.*;
+
+public class Interpreter extends LabelTable {
+    
+    public static final boolean printICode = false;
+    
+    public IRFactory createIRFactory(TokenStream ts, 
+                                     ClassNameHelper nameHelper, Scriptable scope) 
+    {
+        return new IRFactory(ts, scope);
+    }
+    
+    public Node transform(Node tree, TokenStream ts, Scriptable scope) {
+        return (new NodeTransformer()).transform(tree, null, ts, scope);
+    }
+    
+    public Object compile(Context cx, Scriptable scope, Node tree, 
+                          Object securityDomain,
+                          SecuritySupport securitySupport,
+                          ClassNameHelper nameHelper)
+        throws IOException
+    {
+        version = cx.getLanguageVersion();
+        itsData = new InterpreterData(0, 0, 0, securityDomain, 
+                    cx.hasCompileFunctionsWithDynamicScope(), false);
+        if (tree instanceof FunctionNode) {
+            FunctionNode f = (FunctionNode) tree;
+            InterpretedFunction result = 
+                generateFunctionICode(cx, scope, f, securityDomain);
+            result.itsData.itsFunctionType = f.getFunctionType();
+            createFunctionObject(result, scope);
+            return result;
+        }
+        return generateScriptICode(cx, scope, tree, securityDomain);
+    }
+       
+    private void generateICodeFromTree(Node tree, 
+                                       VariableTable varTable, 
+                                       boolean needsActivation,
+                                       Object securityDomain)
+    {
+        int theICodeTop = 0;
+        itsVariableTable = varTable;
+        itsData.itsNeedsActivation = needsActivation;
+        theICodeTop = generateICode(tree, theICodeTop);
+        itsData.itsICodeTop = theICodeTop;
+        if (itsEpilogLabel != -1)
+            markLabel(itsEpilogLabel, theICodeTop);
+        for (int i = 0; i < itsLabelTableTop; i++)
+            itsLabelTable[i].fixGotos(itsData.itsICode);            
+    }
+
+    private Object[] generateRegExpLiterals(Context cx,
+                                            Scriptable scope,
+                                            Vector regexps)
+    {
+        Object[] result = new Object[regexps.size()];
+        RegExpProxy rep = cx.getRegExpProxy();
+        for (int i = 0; i < regexps.size(); i++) {
+            Node regexp = (Node) regexps.elementAt(i);
+            Node left = regexp.getFirstChild();
+            Node right = regexp.getLastChild();
+            result[i] = rep.newRegExp(cx, scope, left.getString(), 
+                                (left != right) ? right.getString() : null, false);
+            regexp.putProp(Node.REGEXP_PROP, new Integer(i));
+        }
+        return result;
+    }
+        
+    private InterpretedScript generateScriptICode(Context cx, 
+                                                  Scriptable scope, 
+                                                  Node tree,
+                                                  Object securityDomain)
+    {        
+        itsSourceFile = (String) tree.getProp(Node.SOURCENAME_PROP);
+        itsData.itsSourceFile = itsSourceFile;
+        itsFunctionList = (Vector) tree.getProp(Node.FUNCTION_PROP);        
+        debugSource = (StringBuffer) tree.getProp(Node.DEBUGSOURCE_PROP);
+        if (itsFunctionList != null)
+            generateNestedFunctions(scope, cx, securityDomain);
+        Object[] regExpLiterals = null;
+        Vector regexps = (Vector)tree.getProp(Node.REGEXP_PROP);
+        if (regexps != null) 
+            regExpLiterals = generateRegExpLiterals(cx, scope, regexps);
+        
+        VariableTable varTable = (VariableTable)tree.getProp(Node.VARS_PROP);
+        // The default is not to generate debug information
+        boolean activationNeeded = cx.isGeneratingDebugChanged() && 
+                                   cx.isGeneratingDebug();
+        generateICodeFromTree(tree, varTable, activationNeeded, securityDomain);
+        itsData.itsNestedFunctions = itsNestedFunctions;
+        itsData.itsRegExpLiterals = regExpLiterals;
+        if (printICode) dumpICode(itsData);
+                                                               
+        String[] argNames = itsVariableTable.getAllNames();
+        short argCount = (short)itsVariableTable.getParameterCount();
+        InterpretedScript
+            result = new InterpretedScript(cx, itsData, argNames, argCount);
+        if (cx.debugger != null) {
+            cx.debugger.handleCompilationDone(cx, result, debugSource);
+        }
+        return result;
+    }
+    
+    private void generateNestedFunctions(Scriptable scope,
+                                         Context cx, 
+                                         Object securityDomain)
+    {
+        itsNestedFunctions = new InterpretedFunction[itsFunctionList.size()];
+        for (short i = 0; i < itsFunctionList.size(); i++) {
+            FunctionNode def = (FunctionNode)itsFunctionList.elementAt(i);
+            Interpreter jsi = new Interpreter();
+            jsi.itsSourceFile = itsSourceFile;
+            jsi.itsData = new InterpreterData(0, 0, 0, securityDomain,
+                            cx.hasCompileFunctionsWithDynamicScope(),
+                            def.getCheckThis());
+            jsi.itsData.itsFunctionType = def.getFunctionType();
+            jsi.itsInFunctionFlag = true;
+            jsi.debugSource = debugSource;
+            itsNestedFunctions[i] = jsi.generateFunctionICode(cx, scope, def, 
+                                                              securityDomain);
+            def.putProp(Node.FUNCTION_PROP, new Short(i));
+        }
+    }        
+    
+    private InterpretedFunction 
+    generateFunctionICode(Context cx, Scriptable scope, 
+                          FunctionNode theFunction, Object securityDomain)
+    {
+        itsFunctionList = (Vector) theFunction.getProp(Node.FUNCTION_PROP);
+        if (itsFunctionList != null) 
+            generateNestedFunctions(scope, cx, securityDomain);
+        Object[] regExpLiterals = null;
+        Vector regexps = (Vector)theFunction.getProp(Node.REGEXP_PROP);
+        if (regexps != null) 
+            regExpLiterals = generateRegExpLiterals(cx, scope, regexps);
+
+        VariableTable varTable = theFunction.getVariableTable();
+        boolean needsActivation = theFunction.requiresActivation() ||
+                                  (cx.isGeneratingDebugChanged() && 
+                                   cx.isGeneratingDebug());
+        generateICodeFromTree(theFunction.getLastChild(), 
+                              varTable, needsActivation,
+                              securityDomain);
+            
+        itsData.itsName = theFunction.getFunctionName();
+        itsData.itsSourceFile = (String) theFunction.getProp(
+                                    Node.SOURCENAME_PROP);
+        itsData.itsSource = (String)theFunction.getProp(Node.SOURCE_PROP);
+        itsData.itsNestedFunctions = itsNestedFunctions;
+        itsData.itsRegExpLiterals = regExpLiterals;
+        if (printICode) dumpICode(itsData);            
+            
+        String[] argNames = itsVariableTable.getAllNames();
+        short argCount = (short)itsVariableTable.getParameterCount();
+        InterpretedFunction 
+            result = new InterpretedFunction(cx, itsData, argNames, argCount); 
+        if (cx.debugger != null) {
+            cx.debugger.handleCompilationDone(cx, result, debugSource);
+        }
+        return result;
+    }
+    
+    boolean itsInFunctionFlag;
+    Vector itsFunctionList;
+    
+    InterpreterData itsData;
+    VariableTable itsVariableTable;
+    int itsTryDepth = 0;
+    int itsStackDepth = 0;
+    int itsEpilogLabel = -1;
+    String itsSourceFile;
+    int itsLineNumber = 0;
+    InterpretedFunction[] itsNestedFunctions = null;
+    
+    private int updateLineNumber(Node node, int iCodeTop)
+    {
+        Object datum = node.getDatum();
+        if (datum == null || !(datum instanceof Number))
+            return iCodeTop;
+        short lineNumber = ((Number) datum).shortValue(); 
+        if (lineNumber != itsLineNumber) {
+            itsLineNumber = lineNumber;
+            if (itsData.itsLineNumberTable == null && 
+                Context.getCurrentContext().isGeneratingDebug())
+            {
+                itsData.itsLineNumberTable = new UintMap();
+            }
+            if (itsData.itsLineNumberTable != null) {
+                itsData.itsLineNumberTable.put(lineNumber, iCodeTop);
+            }
+            iCodeTop = addByte((byte) TokenStream.LINE, iCodeTop);
+            iCodeTop = addByte((byte)(lineNumber >> 8), iCodeTop);
+            iCodeTop = addByte((byte)(lineNumber & 0xff), iCodeTop);
+            
+        }
+        
+        return iCodeTop;
+    }
+    
+    private void badTree(Node node)
+    {
+        try {
+            out = new PrintWriter(new FileOutputStream("icode.txt", true));
+            out.println("Un-handled node : " + node.toString());
+            out.close();
+        }
+        catch (IOException x) {}
+        throw new RuntimeException("Un-handled node : "
+                                        + node.toString());
+    }
+    
+    private int generateICode(Node node, int iCodeTop) {
+        int type = node.getType();
+        Node child = node.getFirstChild();
+        Node firstChild = child;
+        switch (type) {
+            
+            case TokenStream.FUNCTION : {                                        
+                    iCodeTop = addByte((byte) TokenStream.CLOSURE, iCodeTop);
+                    Node fn = (Node) node.getProp(Node.FUNCTION_PROP);
+                    Short index = (Short) fn.getProp(Node.FUNCTION_PROP);
+                    iCodeTop = addByte((byte)(index.shortValue() >> 8), iCodeTop);
+                    iCodeTop = addByte((byte)(index.shortValue() & 0xff), iCodeTop);                    
+                    itsStackDepth++;
+                    if (itsStackDepth > itsData.itsMaxStack)
+                        itsData.itsMaxStack = itsStackDepth;
+                }
+                break;
+
+            case TokenStream.SCRIPT :
+                iCodeTop = updateLineNumber(node, iCodeTop);
+                while (child != null) {
+                    if (child.getType() != TokenStream.FUNCTION) 
+                        iCodeTop = generateICode(child, iCodeTop);
+                    child = child.getNextSibling();
+                }
+                break;
+
+            case TokenStream.CASE :
+                iCodeTop = updateLineNumber(node, iCodeTop);
+                child = child.getNextSibling();
+                while (child != null) {
+                    iCodeTop = generateICode(child, iCodeTop);
+                    child = child.getNextSibling();
+                }
+                break;
+                
+            case TokenStream.LABEL :
+            case TokenStream.WITH :
+            case TokenStream.LOOP :
+            case TokenStream.DEFAULT :
+            case TokenStream.BLOCK :
+            case TokenStream.VOID :
+            case TokenStream.NOP :
+                iCodeTop = updateLineNumber(node, iCodeTop);
+                while (child != null) {
+                    iCodeTop = generateICode(child, iCodeTop);
+                    child = child.getNextSibling();
+                }
+                break;
+
+            case TokenStream.COMMA :
+                iCodeTop = generateICode(child, iCodeTop);
+                iCodeTop = addByte((byte) TokenStream.POP, iCodeTop);
+                itsStackDepth--;
+                child = child.getNextSibling();
+                iCodeTop = generateICode(child, iCodeTop);
+                break;
+               
+            case TokenStream.SWITCH : {
+                    iCodeTop = updateLineNumber(node, iCodeTop);
+                    iCodeTop = generateICode(child, iCodeTop);
+                    int theLocalSlot = itsData.itsMaxLocals++;
+                    iCodeTop = addByte((byte) TokenStream.NEWTEMP, iCodeTop);
+                    iCodeTop = addByte((byte)theLocalSlot, iCodeTop);
+                    iCodeTop = addByte((byte) TokenStream.POP, iCodeTop);
+                    itsStackDepth--;
+         /*
+            reminder - below we construct new GOTO nodes that aren't
+            linked into the tree just for the purpose of having a node
+            to pass to the addGoto routine. (Parallels codegen here).
+            Seems unnecessary.        
+         */
+                    Vector cases = (Vector) node.getProp(Node.CASES_PROP);
+                    for (int i = 0; i < cases.size(); i++) {
+                        Node thisCase = (Node)cases.elementAt(i);
+                        Node first = thisCase.getFirstChild();
+                        // the case expression is the firstmost child
+                        // the rest will be generated when the case
+                        // statements are encountered as siblings of
+                        // the switch statement.
+                        iCodeTop = generateICode(first, iCodeTop);                   
+                        iCodeTop = addByte((byte) TokenStream.USETEMP, iCodeTop);
+                        itsStackDepth++;
+                        if (itsStackDepth > itsData.itsMaxStack)
+                            itsData.itsMaxStack = itsStackDepth;
+                        iCodeTop = addByte((byte) theLocalSlot, iCodeTop);
+                        iCodeTop = addByte((byte) TokenStream.SHEQ, iCodeTop);
+                        Node target = new Node(TokenStream.TARGET);
+                        thisCase.addChildAfter(target, first);
+                        Node branch = new Node(TokenStream.IFEQ);
+                        branch.putProp(Node.TARGET_PROP, target);
+                        iCodeTop = addGoto(branch, TokenStream.IFEQ, 
+                                           iCodeTop);
+                        itsStackDepth--;
+                    }
+
+                    Node defaultNode = (Node) node.getProp(Node.DEFAULT_PROP);
+                    if (defaultNode != null) {
+                        Node defaultTarget = new Node(TokenStream.TARGET);
+                        defaultNode.getFirstChild().addChildToFront(defaultTarget);
+                        Node branch = new Node(TokenStream.GOTO);
+                        branch.putProp(Node.TARGET_PROP, defaultTarget);
+                        iCodeTop = addGoto(branch, TokenStream.GOTO,
+                                                            iCodeTop);                    
+                    }
+
+                    Node breakTarget = (Node) node.getProp(Node.BREAK_PROP);
+                    Node branch = new Node(TokenStream.GOTO);
+                    branch.putProp(Node.TARGET_PROP, breakTarget);
+                    iCodeTop = addGoto(branch, TokenStream.GOTO, 
+                                       iCodeTop);                    
+                }
+                break;
+                                
+            case TokenStream.TARGET : { 
+                    Object lblObect = node.getProp(Node.LABEL_PROP);
+                    if (lblObect == null) {
+                        int label = markLabel(acquireLabel(), iCodeTop);
+                        node.putProp(Node.LABEL_PROP, new Integer(label));
+                    }
+                    else {
+                        int label = ((Integer)lblObect).intValue();
+                        markLabel(label, iCodeTop);
+                    }
+                    // if this target has a FINALLY_PROP, it is a JSR target
+                    // and so has a PC value on the top of the stack
+                    if (node.getProp(Node.FINALLY_PROP) != null) {
+                        itsStackDepth = 1;
+                        if (itsStackDepth > itsData.itsMaxStack)
+                            itsData.itsMaxStack = itsStackDepth;
+                    }
+                }
+                break;
+                
+            case TokenStream.EQOP :
+            case TokenStream.RELOP : {
+                    iCodeTop = generateICode(child, iCodeTop);
+                    child = child.getNextSibling();
+                    iCodeTop = generateICode(child, iCodeTop);
+                    int op = node.getInt();
+                    if (version == Context.VERSION_1_2) {
+                        if (op == TokenStream.EQ)
+                            op = TokenStream.SHEQ;
+                        else if (op == TokenStream.NE)
+                            op = TokenStream.SHNE;
+                    }
+                    iCodeTop = addByte((byte) op, iCodeTop);
+                    itsStackDepth--;
+                }
+                break;
+                
+            case TokenStream.NEW :
+            case TokenStream.CALL : {
+                    if (itsSourceFile != null && (itsData.itsSourceFile == null || ! itsSourceFile.equals(itsData.itsSourceFile))) 
+                        itsData.itsSourceFile = itsSourceFile;
+                    iCodeTop = addByte((byte)TokenStream.SOURCEFILE, iCodeTop);
+                    
+                    int childCount = 0;
+                    short nameIndex = -1;
+                    while (child != null) {
+                        iCodeTop = generateICode(child, iCodeTop);
+                        if (nameIndex == -1) {
+                            if (child.getType() == TokenStream.NAME)
+                                nameIndex = (short)(itsData.itsStringTableIndex - 1);
+                            else if (child.getType() == TokenStream.GETPROP)
+                                nameIndex = (short)(itsData.itsStringTableIndex - 1);
+                        }
+                        child = child.getNextSibling();
+                        childCount++;
+                    }
+                    if (node.getProp(Node.SPECIALCALL_PROP) != null) {
+                        // embed line number and source filename
+                        iCodeTop = addByte((byte) TokenStream.CALLSPECIAL, iCodeTop);
+                        iCodeTop = addByte((byte)(itsLineNumber >> 8), iCodeTop);
+                        iCodeTop = addByte((byte)(itsLineNumber & 0xff), iCodeTop);
+                        iCodeTop = addString(itsSourceFile, iCodeTop);
+                    } else {
+                        iCodeTop = addByte((byte) type, iCodeTop);
+                        iCodeTop = addByte((byte)(nameIndex >> 8), iCodeTop);
+                        iCodeTop = addByte((byte)(nameIndex & 0xFF), iCodeTop);
+                    }
+                    
+                    itsStackDepth -= (childCount - 1);  // always a result value
+                    // subtract from child count to account for [thisObj &] fun
+                    if (type == TokenStream.NEW)
+                        childCount -= 1;
+                    else
+                        childCount -= 2;
+                    iCodeTop = addByte((byte)(childCount >> 8), iCodeTop);
+                    iCodeTop = addByte((byte)(childCount & 0xff), iCodeTop);
+                    if (childCount > itsData.itsMaxArgs)
+                        itsData.itsMaxArgs = childCount;
+                    
+                    iCodeTop = addByte((byte)TokenStream.SOURCEFILE, iCodeTop);
+                }
+                break;
+                
+            case TokenStream.NEWLOCAL :
+            case TokenStream.NEWTEMP : {
+                    iCodeTop = generateICode(child, iCodeTop);
+                    iCodeTop = addByte((byte) TokenStream.NEWTEMP, iCodeTop);
+                    iCodeTop = addLocalRef(node, iCodeTop);
+                }
+                break;                
+                   
+            case TokenStream.USELOCAL : {
+                    if (node.getProp(Node.TARGET_PROP) != null) 
+                        iCodeTop = addByte((byte) TokenStream.RETSUB, iCodeTop);
+                    else {
+                        iCodeTop = addByte((byte) TokenStream.USETEMP, iCodeTop);
+                        itsStackDepth++;
+                        if (itsStackDepth > itsData.itsMaxStack)
+                            itsData.itsMaxStack = itsStackDepth;
+                    }
+                    Node temp = (Node) node.getProp(Node.LOCAL_PROP);
+                    iCodeTop = addLocalRef(temp, iCodeTop);
+                }
+                break;                
+
+            case TokenStream.USETEMP : {
+                    iCodeTop = addByte((byte) TokenStream.USETEMP, iCodeTop);
+                    Node temp = (Node) node.getProp(Node.TEMP_PROP);
+                    iCodeTop = addLocalRef(temp, iCodeTop);
+                    itsStackDepth++;
+                    if (itsStackDepth > itsData.itsMaxStack)
+                        itsData.itsMaxStack = itsStackDepth;
+                }
+                break;                
+                
+            case TokenStream.IFEQ :
+            case TokenStream.IFNE :
+                iCodeTop = generateICode(child, iCodeTop);
+                itsStackDepth--;    // after the conditional GOTO, really
+                    // fall thru...
+            case TokenStream.GOTO :
+                iCodeTop = addGoto(node, (byte) type, iCodeTop);
+                break;
+
+            case TokenStream.JSR : {
+                /*
+                    mark the target with a FINALLY_PROP to indicate
+                    that it will have an incoming PC value on the top
+                    of the stack.
+                    !!! 
+                    This only works if the target follows the JSR
+                    in the tree.
+                    !!!
+                */
+                    Node target = (Node)(node.getProp(Node.TARGET_PROP));
+                    target.putProp(Node.FINALLY_PROP, node);
+                    iCodeTop = addGoto(node, TokenStream.GOSUB, iCodeTop);
+                }
+                break;
+            
+            case TokenStream.AND : {            
+                    iCodeTop = generateICode(child, iCodeTop);
+                    iCodeTop = addByte((byte) TokenStream.DUP, iCodeTop);                
+                    itsStackDepth++;
+                    if (itsStackDepth > itsData.itsMaxStack)
+                        itsData.itsMaxStack = itsStackDepth;
+                    int falseTarget = acquireLabel();
+                    iCodeTop = addGoto(falseTarget, TokenStream.IFNE, 
+                                                    iCodeTop);
+                    iCodeTop = addByte((byte) TokenStream.POP, iCodeTop);
+                    itsStackDepth--;
+                    child = child.getNextSibling();
+                    iCodeTop = generateICode(child, iCodeTop);
+                    markLabel(falseTarget, iCodeTop);
+                }
+                break;
+
+            case TokenStream.OR : {
+                    iCodeTop = generateICode(child, iCodeTop);
+                    iCodeTop = addByte((byte) TokenStream.DUP, iCodeTop);                
+                    itsStackDepth++;
+                    if (itsStackDepth > itsData.itsMaxStack)
+                        itsData.itsMaxStack = itsStackDepth;
+                    int trueTarget = acquireLabel();
+                    iCodeTop = addGoto(trueTarget, TokenStream.IFEQ,
+                                       iCodeTop);
+                    iCodeTop = addByte((byte) TokenStream.POP, iCodeTop);                
+                    itsStackDepth--;
+                    child = child.getNextSibling();
+                    iCodeTop = generateICode(child, iCodeTop);
+                    markLabel(trueTarget, iCodeTop);
+                }
+                break;
+
+            case TokenStream.GETPROP : {
+                    iCodeTop = generateICode(child, iCodeTop);
+                    String s = (String) node.getProp(Node.SPECIAL_PROP_PROP);
+                    if (s != null) {
+                        if (s.equals("__proto__"))
+                            iCodeTop = addByte((byte) TokenStream.GETPROTO, iCodeTop);
+                        else
+                            if (s.equals("__parent__"))
+                                iCodeTop = addByte((byte) TokenStream.GETSCOPEPARENT, iCodeTop);
+                            else
+                                badTree(node);
+                    }
+                    else {
+                        child = child.getNextSibling();
+                        iCodeTop = generateICode(child, iCodeTop);
+                        iCodeTop = addByte((byte) TokenStream.GETPROP, iCodeTop);
+                        itsStackDepth--;
+                    }
+                }
+                break;
+
+            case TokenStream.DELPROP :                
+            case TokenStream.BITAND :                
+            case TokenStream.BITOR :
+            case TokenStream.BITXOR :
+            case TokenStream.LSH :
+            case TokenStream.RSH :
+            case TokenStream.URSH :
+            case TokenStream.ADD :
+            case TokenStream.SUB :
+            case TokenStream.MOD :
+            case TokenStream.DIV :
+            case TokenStream.MUL :
+            case TokenStream.GETELEM :
+                iCodeTop = generateICode(child, iCodeTop);
+                child = child.getNextSibling();
+                iCodeTop = generateICode(child, iCodeTop);
+                iCodeTop = addByte((byte) type, iCodeTop);
+                itsStackDepth--;
+                break;
+
+            case TokenStream.CONVERT : {
+                    iCodeTop = generateICode(child, iCodeTop);
+                    Object toType = node.getProp(Node.TYPE_PROP);
+                    if (toType == ScriptRuntime.NumberClass)
+                        iCodeTop = addByte((byte) TokenStream.POS, iCodeTop);
+                    else
+                        badTree(node);
+                }
+                break;
+
+            case TokenStream.UNARYOP :
+                iCodeTop = generateICode(child, iCodeTop);
+                switch (node.getInt()) {
+                    case TokenStream.VOID :
+                        iCodeTop = addByte((byte) TokenStream.POP, iCodeTop);
+                        iCodeTop = addByte((byte) TokenStream.UNDEFINED, iCodeTop);
+                        break;
+                    case TokenStream.NOT : {
+                            int trueTarget = acquireLabel();
+                            int beyond = acquireLabel();
+                            iCodeTop = addGoto(trueTarget, TokenStream.IFEQ,
+                                                        iCodeTop);
+                            iCodeTop = addByte((byte) TokenStream.TRUE, iCodeTop);
+                            iCodeTop = addGoto(beyond, TokenStream.GOTO, 
+                                                        iCodeTop);
+                            markLabel(trueTarget, iCodeTop);
+                            iCodeTop = addByte((byte) TokenStream.FALSE, iCodeTop);
+                            markLabel(beyond, iCodeTop);
+                        }
+                        break;
+                    case TokenStream.BITNOT :
+                        iCodeTop = addByte((byte) TokenStream.BITNOT, iCodeTop);
+                        break;
+                    case TokenStream.TYPEOF :
+                        iCodeTop = addByte((byte) TokenStream.TYPEOF, iCodeTop);
+                        break;
+                    case TokenStream.SUB :
+                        iCodeTop = addByte((byte) TokenStream.NEG, iCodeTop);
+                        break;
+                    case TokenStream.ADD :
+                        iCodeTop = addByte((byte) TokenStream.POS, iCodeTop);
+                        break;
+                    default:
+                        badTree(node);
+                        break;
+                }
+                break;
+
+            case TokenStream.SETPROP : {
+                    iCodeTop = generateICode(child, iCodeTop);
+                    child = child.getNextSibling();
+                    iCodeTop = generateICode(child, iCodeTop);
+                    String s = (String) node.getProp(Node.SPECIAL_PROP_PROP);
+                    if (s != null) {
+                        if (s.equals("__proto__"))
+                            iCodeTop = addByte((byte) TokenStream.SETPROTO, iCodeTop);
+                        else
+                            if (s.equals("__parent__"))
+                                iCodeTop = addByte((byte) TokenStream.SETPARENT, iCodeTop);
+                            else
+                                badTree(node);
+                    }
+                    else {
+                        child = child.getNextSibling();
+                        iCodeTop = generateICode(child, iCodeTop);
+                        iCodeTop = addByte((byte) TokenStream.SETPROP, iCodeTop);
+                        itsStackDepth -= 2;
+                    }
+                }
+                break;            
+
+            case TokenStream.SETELEM :
+                iCodeTop = generateICode(child, iCodeTop);
+                child = child.getNextSibling();
+                iCodeTop = generateICode(child, iCodeTop);
+                child = child.getNextSibling();
+                iCodeTop = generateICode(child, iCodeTop);
+                iCodeTop = addByte((byte) type, iCodeTop);
+                itsStackDepth -= 2;
+                break;
+
+            case TokenStream.SETNAME :
+                iCodeTop = generateICode(child, iCodeTop);
+                child = child.getNextSibling();
+                iCodeTop = generateICode(child, iCodeTop);
+                iCodeTop = addByte((byte) TokenStream.SETNAME, iCodeTop);                
+                iCodeTop = addString(firstChild.getString(), iCodeTop);
+                itsStackDepth--;
+                break;
+                
+            case TokenStream.TYPEOF : {
+                    String name = node.getString();
+                    int index = -1;
+                    // use typeofname if an activation frame exists
+                    // since the vars all exist there instead of in jregs
+                    if (itsInFunctionFlag && !itsData.itsNeedsActivation)
+                        index = itsVariableTable.getOrdinal(name);
+                    if (index == -1) {                    
+                        iCodeTop = addByte((byte) TokenStream.TYPEOFNAME, iCodeTop);
+                        iCodeTop = addString(name, iCodeTop);
+                    }
+                    else {
+                        iCodeTop = addByte((byte) TokenStream.GETVAR, iCodeTop);
+                        iCodeTop = addByte((byte) index, iCodeTop);
+                        iCodeTop = addByte((byte) TokenStream.TYPEOF, iCodeTop);
+                    }
+                    itsStackDepth++;
+                    if (itsStackDepth > itsData.itsMaxStack)
+                        itsData.itsMaxStack = itsStackDepth;
+                }
+                break;
+
+            case TokenStream.PARENT :
+                iCodeTop = generateICode(child, iCodeTop);
+                iCodeTop = addByte((byte) TokenStream.GETPARENT, iCodeTop);
+                break;
+
+            case TokenStream.GETBASE :
+            case TokenStream.BINDNAME :
+            case TokenStream.NAME :
+            case TokenStream.STRING :
+                iCodeTop = addByte((byte) type, iCodeTop);
+                iCodeTop = addString(node.getString(), iCodeTop);
+                itsStackDepth++;
+                if (itsStackDepth > itsData.itsMaxStack)
+                    itsData.itsMaxStack = itsStackDepth;
+                break;
+
+            case TokenStream.INC :
+            case TokenStream.DEC : {
+                    int childType = child.getType();
+                    switch (childType) {
+                        case TokenStream.GETVAR : {
+                                String name = child.getString();
+                                if (itsData.itsNeedsActivation) {
+                                    iCodeTop = addByte((byte) TokenStream.SCOPE, iCodeTop);
+                                    iCodeTop = addByte((byte) TokenStream.STRING, iCodeTop);
+                                    iCodeTop = addString(name, iCodeTop);
+                                    itsStackDepth += 2;
+                                    if (itsStackDepth > itsData.itsMaxStack)
+                                        itsData.itsMaxStack = itsStackDepth;
+                                    iCodeTop = addByte((byte)
+                                                (type == TokenStream.INC
+                                                    ? TokenStream.PROPINC 
+                                                    : TokenStream.PROPDEC),
+                                                 iCodeTop);
+                                    itsStackDepth--;                                        
+                                }
+                                else {
+                                    iCodeTop = addByte((byte)
+                                                (type == TokenStream.INC
+                                                    ? TokenStream.VARINC
+                                                    : TokenStream.VARDEC),
+                                                iCodeTop);
+                                    int i = itsVariableTable.getOrdinal(name);
+                                    iCodeTop = addByte((byte)i, iCodeTop);
+                                    itsStackDepth++;
+                                    if (itsStackDepth > itsData.itsMaxStack)
+                                        itsData.itsMaxStack = itsStackDepth;
+                                }
+                            }
+                            break;
+                        case TokenStream.GETPROP :
+                        case TokenStream.GETELEM : {
+                                Node getPropChild = child.getFirstChild();
+                                iCodeTop = generateICode(getPropChild,
+                                                              iCodeTop);
+                                getPropChild = getPropChild.getNextSibling();
+                                iCodeTop = generateICode(getPropChild,
+                                                              iCodeTop);
+                                if (childType == TokenStream.GETPROP)
+                                    iCodeTop = addByte((byte)
+                                                    (type == TokenStream.INC
+                                                        ? TokenStream.PROPINC 
+                                                        : TokenStream.PROPDEC),
+                                                    iCodeTop);
+                                else                                                        
+                                    iCodeTop = addByte((byte)
+                                                    (type == TokenStream.INC
+                                                        ? TokenStream.ELEMINC 
+                                                        : TokenStream.ELEMDEC),
+                                                    iCodeTop);
+                                itsStackDepth--;                                        
+                            }
+                            break;
+                        default : {
+                                iCodeTop = addByte((byte)
+                                                    (type == TokenStream.INC 
+                                                        ? TokenStream.NAMEINC 
+                                                        : TokenStream.NAMEDEC),
+                                                    iCodeTop);
+                                iCodeTop = addString(child.getString(), 
+                                                            iCodeTop);
+                                itsStackDepth++;
+                                if (itsStackDepth > itsData.itsMaxStack)
+                                    itsData.itsMaxStack = itsStackDepth;
+                            }
+                            break;
+                    }
+                }
+                break;
+
+            case TokenStream.NUMBER : {
+                double num = node.getDouble();
+                if (num == 0.0) {
+                    iCodeTop = addByte((byte) TokenStream.ZERO, iCodeTop);
+                }
+                else if (num == 1.0) {
+                    iCodeTop = addByte((byte) TokenStream.ONE, iCodeTop);
+                }
+                else {
+                    iCodeTop = addByte((byte) TokenStream.NUMBER, iCodeTop);
+                    iCodeTop = addNumber(num, iCodeTop);
+                }
+                itsStackDepth++;
+                if (itsStackDepth > itsData.itsMaxStack)
+                    itsData.itsMaxStack = itsStackDepth;
+                break;
+            }
+
+            case TokenStream.POP :
+            case TokenStream.POPV :
+                iCodeTop = updateLineNumber(node, iCodeTop);
+            case TokenStream.ENTERWITH :
+                iCodeTop = generateICode(child, iCodeTop);
+                iCodeTop = addByte((byte) type, iCodeTop);
+                itsStackDepth--;
+                break;
+
+            case TokenStream.GETTHIS :
+                iCodeTop = generateICode(child, iCodeTop);
+                iCodeTop = addByte((byte) type, iCodeTop);
+                break;
+                
+            case TokenStream.NEWSCOPE :
+                iCodeTop = addByte((byte) type, iCodeTop);
+                itsStackDepth++;
+                if (itsStackDepth > itsData.itsMaxStack)
+                    itsData.itsMaxStack = itsStackDepth;
+                break;
+
+            case TokenStream.LEAVEWITH :
+                iCodeTop = addByte((byte) type, iCodeTop);
+                break;
+
+            case TokenStream.TRY : {
+                    itsTryDepth++;
+                    if (itsTryDepth > itsData.itsMaxTryDepth)
+                        itsData.itsMaxTryDepth = itsTryDepth;
+                    Node catchTarget = (Node)node.getProp(Node.TARGET_PROP);
+                    Node finallyTarget = (Node)node.getProp(Node.FINALLY_PROP);
+                    if (catchTarget == null) {
+                        iCodeTop = addByte((byte) TokenStream.TRY, iCodeTop);
+                        iCodeTop = addByte((byte)0, iCodeTop);
+                        iCodeTop = addByte((byte)0, iCodeTop);
+                    }
+                    else
+                        iCodeTop = 
+                            addGoto(node, TokenStream.TRY, iCodeTop);
+                    int finallyHandler = 0;
+                    if (finallyTarget != null) {
+                        finallyHandler = acquireLabel();
+                        int theLabel = finallyHandler & 0x7FFFFFFF;
+                        itsLabelTable[theLabel].addFixup(iCodeTop);
+                    }
+                    iCodeTop = addByte((byte)0, iCodeTop);
+                    iCodeTop = addByte((byte)0, iCodeTop);
+                    
+                    Node lastChild = null;
+                    /*
+                        when we encounter the child of the catchTarget, we
+                        set the stackDepth to 1 to account for the incoming
+                        exception object.
+                    */
+                    boolean insertedEndTry = false;
+                    while (child != null) {
+                        if (catchTarget != null && lastChild == catchTarget) {
+                            itsStackDepth = 1;
+                            if (itsStackDepth > itsData.itsMaxStack)
+                                itsData.itsMaxStack = itsStackDepth;
+                        }
+                        /*
+                            When the following child is the catchTarget
+                            (or the finallyTarget if there are no catches),
+                            the current child is the goto at the end of
+                            the try statemets, we need to emit the endtry
+                            before that goto.
+                        */
+                        Node nextSibling = child.getNextSibling();
+                        if (!insertedEndTry && nextSibling != null &&
+                            (nextSibling == catchTarget ||
+                             nextSibling == finallyTarget))
+                        {
+                            iCodeTop = addByte((byte) TokenStream.ENDTRY,
+                                               iCodeTop);
+                            insertedEndTry = true;
+                        }
+                        iCodeTop = generateICode(child, iCodeTop);
+                        lastChild = child;
+                        child = child.getNextSibling();
+                    }
+                    itsStackDepth = 0;
+                    if (finallyTarget != null) {
+                        // normal flow goes around the finally handler stublet
+                        int skippy = acquireLabel();
+                        iCodeTop = 
+                            addGoto(skippy, TokenStream.GOTO, iCodeTop);
+                        // on entry the stack will have the exception object
+                        markLabel(finallyHandler, iCodeTop);
+                        itsStackDepth = 1;
+                        if (itsStackDepth > itsData.itsMaxStack)
+                            itsData.itsMaxStack = itsStackDepth;
+                        int theLocalSlot = itsData.itsMaxLocals++;
+                        iCodeTop = addByte((byte) TokenStream.NEWTEMP, iCodeTop);
+                        iCodeTop = addByte((byte)theLocalSlot, iCodeTop);
+                        iCodeTop = addByte((byte) TokenStream.POP, iCodeTop);
+                        Integer finallyLabel 
+                           = (Integer)(finallyTarget.getProp(Node.LABEL_PROP));
+                        iCodeTop = addGoto(finallyLabel.intValue(), 
+                                         TokenStream.GOSUB, iCodeTop);
+                        iCodeTop = addByte((byte) TokenStream.USETEMP, iCodeTop);
+                        iCodeTop = addByte((byte)theLocalSlot, iCodeTop);
+                        iCodeTop = addByte((byte) TokenStream.JTHROW, iCodeTop);
+                        itsStackDepth = 0;
+                        markLabel(skippy, iCodeTop);
+                    }
+                    itsTryDepth--;
+                }
+                break;
+                
+            case TokenStream.THROW :
+                iCodeTop = updateLineNumber(node, iCodeTop);
+                iCodeTop = generateICode(child, iCodeTop);
+                iCodeTop = addByte((byte) TokenStream.THROW, iCodeTop);
+                itsStackDepth--;
+                break;
+                
+            case TokenStream.ASSERT:
+                iCodeTop = updateLineNumber(node, iCodeTop);
+                if (child != null) 
+                    iCodeTop = generateICode(child, iCodeTop);
+                else {
+                    iCodeTop = addByte((byte) TokenStream.UNDEFINED, iCodeTop);
+                    itsStackDepth++;
+                    if (itsStackDepth > itsData.itsMaxStack)
+                        itsData.itsMaxStack = itsStackDepth;
+                }
+                iCodeTop = addGoto(node, TokenStream.ASSERT, iCodeTop);
+                itsStackDepth--;
+                break;
+                
+            case TokenStream.RETURN :
+                iCodeTop = updateLineNumber(node, iCodeTop);
+                if (child != null) 
+                    iCodeTop = generateICode(child, iCodeTop);
+                else {
+                    iCodeTop = addByte((byte) TokenStream.UNDEFINED, iCodeTop);
+                    itsStackDepth++;
+                    if (itsStackDepth > itsData.itsMaxStack)
+                        itsData.itsMaxStack = itsStackDepth;
+                }
+                iCodeTop = addGoto(node, TokenStream.RETURN, iCodeTop);
+                itsStackDepth--;
+                break;
+                
+            case TokenStream.GETVAR : {
+                    String name = node.getString();
+                    if (itsData.itsNeedsActivation) {
+                        // SETVAR handled this by turning into a SETPROP, but
+                        // we can't do that to a GETVAR without manufacturing
+                        // bogus children. Instead we use a special op to
+                        // push the current scope.
+                        iCodeTop = addByte((byte) TokenStream.SCOPE, iCodeTop);
+                        iCodeTop = addByte((byte) TokenStream.STRING, iCodeTop);
+                        iCodeTop = addString(name, iCodeTop);
+                        itsStackDepth += 2;
+                        if (itsStackDepth > itsData.itsMaxStack)
+                            itsData.itsMaxStack = itsStackDepth;
+                        iCodeTop = addByte((byte) TokenStream.GETPROP, iCodeTop);
+                        itsStackDepth--;
+                    }
+                    else {
+                        int index = itsVariableTable.getOrdinal(name);
+                        iCodeTop = addByte((byte) TokenStream.GETVAR, iCodeTop);
+                        iCodeTop = addByte((byte)index, iCodeTop);
+                        itsStackDepth++;
+                        if (itsStackDepth > itsData.itsMaxStack)
+                            itsData.itsMaxStack = itsStackDepth;
+                    }
+                }
+                break;
+                
+            case TokenStream.SETVAR : {
+                    if (itsData.itsNeedsActivation) {
+                        child.setType(TokenStream.BINDNAME);
+                        node.setType(TokenStream.SETNAME);
+                        iCodeTop = generateICode(node, iCodeTop);
+                    }
+                    else {
+                        String name = child.getString();
+                        child = child.getNextSibling();
+                        iCodeTop = generateICode(child, iCodeTop);
+                        int index = itsVariableTable.getOrdinal(name);
+                        iCodeTop = addByte((byte) TokenStream.SETVAR, iCodeTop);
+                        iCodeTop = addByte((byte)index, iCodeTop);
+                    }
+                }
+                break;
+                
+            case TokenStream.PRIMARY:
+                iCodeTop = addByte((byte) node.getInt(), iCodeTop);
+                itsStackDepth++;
+                if (itsStackDepth > itsData.itsMaxStack)
+                    itsData.itsMaxStack = itsStackDepth;
+                break;
+
+            case TokenStream.ENUMINIT :
+                iCodeTop = generateICode(child, iCodeTop);
+                iCodeTop = addByte((byte) TokenStream.ENUMINIT, iCodeTop);
+                iCodeTop = addLocalRef(node, iCodeTop);
+                itsStackDepth--;
+                break;
+                
+            case TokenStream.ENUMNEXT : {
+                    iCodeTop = addByte((byte) TokenStream.ENUMNEXT, iCodeTop);
+                    Node init = (Node)node.getProp(Node.ENUM_PROP);
+                    iCodeTop = addLocalRef(init, iCodeTop);
+                    itsStackDepth++;
+                    if (itsStackDepth > itsData.itsMaxStack)
+                        itsData.itsMaxStack = itsStackDepth;
+                }                        
+                break;
+                
+            case TokenStream.ENUMDONE :
+                // could release the local here??
+                break;
+                
+            case TokenStream.OBJECT : {
+                    Node regexp = (Node) node.getProp(Node.REGEXP_PROP);
+                    int index = ((Integer)(regexp.getProp(
+                                            Node.REGEXP_PROP))).intValue();
+                    iCodeTop = addByte((byte) TokenStream.OBJECT, iCodeTop);
+                    iCodeTop = addByte((byte)(index >> 8), iCodeTop);
+                    iCodeTop = addByte((byte)(index & 0xff), iCodeTop);
+                    itsStackDepth++;
+                    if (itsStackDepth > itsData.itsMaxStack)
+                        itsData.itsMaxStack = itsStackDepth;
+                }
+                break;
+                
+            default : 
+                badTree(node);
+                break;
+        }
+        return iCodeTop;
+    }
+    
+    private int addLocalRef(Node node, int iCodeTop)
+    {
+        int theLocalSlot;
+        Integer localProp = (Integer)node.getProp(Node.LOCAL_PROP);
+        if (localProp == null) {
+            theLocalSlot = itsData.itsMaxLocals++;
+            node.putProp(Node.LOCAL_PROP, new Integer(theLocalSlot));
+        }
+        else
+            theLocalSlot = localProp.intValue();
+        iCodeTop = addByte((byte)theLocalSlot, iCodeTop);
+        if (theLocalSlot >= itsData.itsMaxLocals)
+            itsData.itsMaxLocals = theLocalSlot + 1;
+        return iCodeTop;            
+    }
+    
+    private int addGoto(Node node, int gotoOp, int iCodeTop)
+    {
+        int targetLabel;
+        if (node.getType() == TokenStream.RETURN || node.getType() == TokenStream.ASSERT) {
+            if (itsEpilogLabel == -1)
+                itsEpilogLabel = acquireLabel();
+            targetLabel = itsEpilogLabel;
+        }
+        else {
+            Node target = (Node)(node.getProp(Node.TARGET_PROP));
+            Object lblObect = target.getProp(Node.LABEL_PROP);
+            if (lblObect == null) {
+                targetLabel = acquireLabel();
+                target.putProp(Node.LABEL_PROP, new Integer(targetLabel));
+            }
+            else
+                targetLabel = ((Integer)lblObect).intValue();
+        }
+        iCodeTop = addGoto(targetLabel, (byte) gotoOp, iCodeTop);
+        return iCodeTop;
+    }
+    
+    private int addGoto(int targetLabel, int gotoOp, int iCodeTop)
+    {
+        int gotoPC = iCodeTop;
+        iCodeTop = addByte((byte) gotoOp, iCodeTop);
+        int theLabel = targetLabel & 0x7FFFFFFF;
+        int targetPC = itsLabelTable[theLabel].getPC();
+        if (targetPC != -1) {
+            short offset = (short)(targetPC - gotoPC);
+            iCodeTop = addByte((byte)(offset >> 8), iCodeTop);
+            iCodeTop = addByte((byte)offset, iCodeTop);
+        }
+        else {
+            itsLabelTable[theLabel].addFixup(gotoPC + 1);
+            iCodeTop = addByte((byte)0, iCodeTop);
+            iCodeTop = addByte((byte)0, iCodeTop);
+        }
+        return iCodeTop;
+    }
+    
+    private final int addByte(byte b, int iCodeTop) {
+        if (itsData.itsICode.length == iCodeTop) {
+            byte[] ba = new byte[iCodeTop * 2];
+            System.arraycopy(itsData.itsICode, 0, ba, 0, iCodeTop);
+            itsData.itsICode = ba;
+        }
+        itsData.itsICode[iCodeTop++] = b;
+        return iCodeTop;
+    }
+    
+    private final int addString(String str, int iCodeTop)
+    {
+        int index = itsData.itsStringTableIndex;
+        if (itsData.itsStringTable.length == index) {
+            String[] sa = new String[index * 2];
+            System.arraycopy(itsData.itsStringTable, 0, sa, 0, index);
+            itsData.itsStringTable = sa;
+        }
+        itsData.itsStringTable[index] = str;
+        itsData.itsStringTableIndex = index + 1;
+
+        iCodeTop = addByte((byte)(index >> 8), iCodeTop);
+        iCodeTop = addByte((byte)(index & 0xFF), iCodeTop);
+        return iCodeTop;
+    }
+    
+    private final int addNumber(double num, int iCodeTop)
+    {
+        int index = itsData.itsNumberTableIndex;
+        if (itsData.itsNumberTable.length == index) {
+            double[] na = new double[index * 2];
+            System.arraycopy(itsData.itsNumberTable, 0, na, 0, index);
+            itsData.itsNumberTable = na;
+        }
+        itsData.itsNumberTable[index] = num;
+        itsData.itsNumberTableIndex = index + 1;
+
+        iCodeTop = addByte((byte)(index >> 8), iCodeTop);
+        iCodeTop = addByte((byte)(index & 0xFF), iCodeTop);
+        return iCodeTop;
+    }
+    
+    private static String getString(String[] theStringTable, byte[] iCode, 
+                                    int pc)
+    {
+        int index = (iCode[pc] << 8) + (iCode[pc + 1] & 0xFF);
+        return theStringTable[index];
+    }
+    
+    private static double getNumber(double[] theNumberTable, byte[] iCode, 
+                                    int pc)
+    {
+        int index = (iCode[pc] << 8) + (iCode[pc + 1] & 0xFF);
+        return theNumberTable[index];
+    }
+    
+    private static int getTarget(byte[] iCode, int pc)
+    {
+        int displacement = (iCode[pc] << 8) + (iCode[pc + 1] & 0xFF);
+        return pc - 1 + displacement;
+    }
+    
+    static PrintWriter out;
+    static {
+        if (printICode) {
+            try {
+                out = new PrintWriter(new FileOutputStream("icode.txt"));
+                out.close();
+            }
+            catch (IOException x) {
+            }
+        }
+    }   
+    
+    private static void dumpICode(InterpreterData theData) {
+        if (printICode) {
+            try {
+                int iCodeLength = theData.itsICodeTop;
+                byte iCode[] = theData.itsICode;
+                
+                out = new PrintWriter(new FileOutputStream("icode.txt", true));
+                out.println("ICode dump, for " + theData.itsName + ", length = " + iCodeLength);
+                out.println("MaxStack = " + theData.itsMaxStack);
+                
+                for (int pc = 0; pc < iCodeLength; ) {
+                    out.print("[" + pc + "] ");
+                    switch ((int)(iCode[pc] & 0xff)) {
+                        case TokenStream.SCOPE :
+                        case TokenStream.GETPROTO :
+                        case TokenStream.GETPARENT :
+                        case TokenStream.GETSCOPEPARENT :
+                        case TokenStream.SETPROTO :
+                        case TokenStream.SETPARENT :
+                        case TokenStream.DELPROP :
+                        case TokenStream.TYPEOF :
+                        case TokenStream.NEWSCOPE :
+                        case TokenStream.ENTERWITH :
+                        case TokenStream.LEAVEWITH :
+                        case TokenStream.ENDTRY :
+                        case TokenStream.THROW :
+                        case TokenStream.JTHROW :
+                        case TokenStream.GETTHIS :
+                        case TokenStream.SETELEM :
+                        case TokenStream.GETELEM :
+                        case TokenStream.SETPROP :
+                        case TokenStream.GETPROP :
+                        case TokenStream.PROPINC :
+                        case TokenStream.PROPDEC :
+                        case TokenStream.ELEMINC :
+                        case TokenStream.ELEMDEC :
+                        case TokenStream.BITNOT :                
+                        case TokenStream.BITAND :                
+                        case TokenStream.BITOR :
+                        case TokenStream.BITXOR :
+                        case TokenStream.LSH :
+                        case TokenStream.RSH :
+                        case TokenStream.URSH :
+                        case TokenStream.NEG :
+                        case TokenStream.POS :
+                        case TokenStream.SUB :
+                        case TokenStream.MUL :
+                        case TokenStream.DIV :
+                        case TokenStream.MOD :
+                        case TokenStream.ADD :
+                        case TokenStream.POPV :
+                        case TokenStream.POP :
+                        case TokenStream.DUP :
+                        case TokenStream.LT :
+                        case TokenStream.GT :
+                        case TokenStream.LE :
+                        case TokenStream.GE :
+                        case TokenStream.IN :
+                        case TokenStream.INSTANCEOF :
+                        case TokenStream.EQ :
+                        case TokenStream.NE :
+                        case TokenStream.SHEQ :
+                        case TokenStream.SHNE :
+                        case TokenStream.ZERO :
+                        case TokenStream.ONE :
+                        case TokenStream.NULL :
+                        case TokenStream.THIS :
+                        case TokenStream.THISFN :
+                        case TokenStream.FALSE :
+                        case TokenStream.TRUE :
+                        case TokenStream.UNDEFINED :
+                        case TokenStream.SOURCEFILE : 
+                            out.println(TokenStream.tokenToName(iCode[pc] & 0xff));
+                            break;
+                        case TokenStream.GOSUB :
+                        case TokenStream.RETURN :
+                        case TokenStream.GOTO :
+                        case TokenStream.IFEQ :
+                        case TokenStream.IFNE : {
+                                int newPC = getTarget(iCode, pc + 1);                    
+                                out.println(
+                                    TokenStream.tokenToName(iCode[pc] & 0xff) +
+                                    " " + newPC);
+                                pc += 2;
+                            }
+                            break;
+                        case TokenStream.TRY : {
+                                int newPC1 = getTarget(iCode, pc + 1);                    
+                                int newPC2 = getTarget(iCode, pc + 3);                    
+                                out.println(
+                                    TokenStream.tokenToName(iCode[pc] & 0xff) +
+                                    " " + newPC1 +
+                                    " " + newPC2);
+                                pc += 4;
+                            }
+                            break;
+                        case TokenStream.RETSUB :                        
+                        case TokenStream.ENUMINIT :
+                        case TokenStream.ENUMNEXT :
+                        case TokenStream.VARINC :
+                        case TokenStream.VARDEC :
+                        case TokenStream.GETVAR :
+                        case TokenStream.SETVAR :
+                        case TokenStream.NEWTEMP :
+                        case TokenStream.USETEMP : {
+                                int slot = (iCode[pc + 1] & 0xFF);
+                                out.println(
+                                    TokenStream.tokenToName(iCode[pc] & 0xff) +
+                                    " " + slot);
+                                pc++;
+                            }
+                            break;
+                        case TokenStream.CALLSPECIAL : {
+                                int line = (iCode[pc + 1] << 8) 
+                                                        | (iCode[pc + 2] & 0xFF);
+                                String name = getString(theData.itsStringTable,
+                                                                  iCode, pc + 3);
+                                int count = (iCode[pc + 5] << 8) | (iCode[pc + 6] & 0xFF);
+                                out.println(
+                                    TokenStream.tokenToName(iCode[pc] & 0xff) +
+                                    " " + count + " " + line + " " + name);
+                                pc += 6;
+                            }
+                            break;
+                        case TokenStream.OBJECT :
+                        case TokenStream.CLOSURE :
+                        case TokenStream.NEW :
+                        case TokenStream.CALL : {
+                                int count = (iCode[pc + 3] << 8) | (iCode[pc + 4] & 0xFF);
+                                out.println(
+                                    TokenStream.tokenToName(iCode[pc] & 0xff) +
+                                    " " + count + " \"" + 
+                                    getString(theData.itsStringTable, iCode, 
+                                              pc + 1) + "\"");
+                                pc += 5;
+                            }
+                            break;
+                        case TokenStream.NUMBER :
+                            out.println(
+                                TokenStream.tokenToName(iCode[pc] & 0xff) + 
+                                " " + getNumber(theData.itsNumberTable,
+                                                iCode, pc + 1));
+                            pc += 2;
+                            break;
+                        case TokenStream.TYPEOFNAME :
+                        case TokenStream.GETBASE :
+                        case TokenStream.BINDNAME :
+                        case TokenStream.SETNAME :
+                        case TokenStream.NAME :
+                        case TokenStream.NAMEINC :
+                        case TokenStream.NAMEDEC :
+                        case TokenStream.STRING :
+                            out.println(
+                                TokenStream.tokenToName(iCode[pc] & 0xff) +
+                                " \"" +
+                                getString(theData.itsStringTable, iCode, pc + 1) +
+                                "\"");
+                            pc += 2;
+                            break;
+                        case TokenStream.LINE : {
+                                int line = (iCode[pc + 1] << 8) | (iCode[pc + 2] & 0xFF);
+                                out.println(
+                                    TokenStream.tokenToName(iCode[pc] & 0xff) + " : " + line);
+                                pc += 2;
+                            }
+                            break;
+                        default :
+                            out.close();
+                            throw new RuntimeException("Unknown icode : "
+                                                    + (iCode[pc] & 0xff)  + " @ pc : " + pc);
+                    }
+                    pc++;
+                }
+                out.close();
+            }
+            catch (IOException x) {}
+        }
+    }
+    
+    private static void createFunctionObject(InterpretedFunction fn, 
+                                             Scriptable scope)
+    {
+        fn.setPrototype(ScriptableObject.getClassPrototype(scope, "Function"));
+        fn.setParentScope(scope);
+        InterpreterData id = fn.itsData;
+        if (id.itsName.length() == 0)
+            return;
+        if ((id.itsFunctionType == FunctionNode.FUNCTION_STATEMENT &&
+             fn.itsClosure == null) ||
+            (id.itsFunctionType == FunctionNode.FUNCTION_EXPRESSION_STATEMENT &&
+             fn.itsClosure != null))
+        {
+            ScriptRuntime.setProp(scope, fn.itsData.itsName, fn, scope);
+        }
+    }
+    
+    public static Object interpret(Context cx, Scriptable scope, 
+                                   Scriptable thisObj, Object[] args, 
+                                   NativeFunction fnOrScript,
+                                   InterpreterData theData)
+        throws JavaScriptException
+    {
+        int i;
+        Object lhs;
+
+        final int maxStack = theData.itsMaxStack;
+        final int maxVars = (fnOrScript.argNames == null) 
+                            ? 0 : fnOrScript.argNames.length;
+        final int maxLocals = theData.itsMaxLocals;
+        final int maxTryDepth = theData.itsMaxTryDepth;
+        
+        final int VAR_SHFT = maxStack; 
+        final int LOCAL_SHFT = VAR_SHFT + maxVars; 
+        final int TRY_SCOPE_SHFT = LOCAL_SHFT + maxLocals;
+
+// stack[0 <= i < VAR_SHFT]: stack data
+// stack[VAR_SHFT <= i < LOCAL_SHFT]: variables
+// stack[LOCAL_SHFT <= i < TRY_SCOPE_SHFT]: used for newtemp/usetemp
+// stack[TRY_SCOPE_SHFT <= i]: try scopes
+// when 0 <= i < LOCAL_SHFT and stack[x] == DBL_MRK, 
+// sDbl[i]  gives the number value
+
+        final Object DBL_MRK = Interpreter.DBL_MRK;
+        
+        Object[] stack = new Object[TRY_SCOPE_SHFT + maxTryDepth];
+        double[] sDbl = new double[TRY_SCOPE_SHFT];
+        int stackTop = -1;
+        byte[] iCode = theData.itsICode;        
+        int pc = 0;
+        int iCodeLength = theData.itsICodeTop;
+        
+        final Scriptable undefined = Undefined.instance;
+        if (maxVars != 0) {
+            int definedArgs = fnOrScript.argCount;
+            if (definedArgs != 0) {
+                if (definedArgs > args.length) { definedArgs = args.length;    }
+                for (i = 0; i != definedArgs; ++i) {
+                    stack[VAR_SHFT + i] = args[i];    
+                }
+            }
+            for (i = definedArgs; i != maxVars; ++i) {
+                stack[VAR_SHFT + i] = undefined;
+            }
+        }
+        
+        if (theData.itsNestedFunctions != null) {
+            for (i = 0; i < theData.itsNestedFunctions.length; i++)
+                createFunctionObject(theData.itsNestedFunctions[i], scope);
+        }        
+
+        Object id;
+        Object rhs, val;
+        double valDbl;
+        boolean valBln;
+
+        int count;
+        int slot;
+                
+        String name = null;
+               
+        Object[] outArgs;
+
+        int lIntValue;
+        long lLongValue;
+        double lDbl;
+        int rIntValue;
+        double rDbl;
+                
+        int[] catchStack = null;
+        int tryStackTop = 0;
+        InterpreterFrame frame = null;
+        
+        if (cx.debugger != null) {
+            frame = new InterpreterFrame(scope, theData, fnOrScript);
+            cx.pushFrame(frame);
+        }
+            
+        if (maxTryDepth != 0) {
+            // catchStack[2 * i]: starting pc of catch block
+            // catchStack[2 * i + 1]: starting pc of finally block
+            catchStack = new int[maxTryDepth * 2];
+        }
+        
+        /* Save the security domain. Must restore upon normal exit. 
+         * If we exit the interpreter loop by throwing an exception,
+         * set cx.interpreterSecurityDomain to null, and require the
+         * catching function to restore it.
+         */
+        Object savedSecurityDomain = cx.interpreterSecurityDomain;
+        cx.interpreterSecurityDomain = theData.securityDomain;
+        Object result = undefined;
+        
+        int pcPrevBranch = pc;
+        final int instructionThreshold = cx.instructionThreshold;
+        // During function call this will be set to -1 so catch can properly
+        // adjust it
+        int instructionCount = cx.instructionCount;
+        // arbitrary number to add to instructionCount when calling 
+        // other functions
+        final int INVOCATION_COST = 100;
+        
+        while (pc < iCodeLength) {
+            try {
+                switch (iCode[pc] & 0xff) {
+                    case TokenStream.ENDTRY :
+                        tryStackTop--;
+                        break;
+                    case TokenStream.TRY :
+                        i = getTarget(iCode, pc + 1);
+                        if (i == pc) i = 0;
+                        catchStack[tryStackTop * 2] = i;
+                        i = getTarget(iCode, pc + 3);
+                        if (i == (pc + 2)) i = 0;
+                        catchStack[tryStackTop * 2 + 1] = i;
+                        stack[TRY_SCOPE_SHFT + tryStackTop] = scope;
+                        ++tryStackTop;
+                        pc += 4;
+                        break;
+                    case TokenStream.GE :
+                        --stackTop;
+                        rhs = stack[stackTop + 1];    
+                        lhs = stack[stackTop];
+                        if (rhs == DBL_MRK || lhs == DBL_MRK) {
+                            rDbl = stack_double(stack, sDbl, stackTop + 1);
+                            lDbl = stack_double(stack, sDbl, stackTop);
+                            valBln = (rDbl == rDbl && lDbl == lDbl 
+                                      && rDbl <= lDbl);
+                        }
+                        else {
+                            valBln = (1 == ScriptRuntime.cmp_LE(rhs, lhs));
+                        }
+                        stack[stackTop] = valBln ? Boolean.TRUE : Boolean.FALSE;
+                        break;
+                    case TokenStream.LE :
+                        --stackTop;
+                        rhs = stack[stackTop + 1];    
+                        lhs = stack[stackTop];
+                        if (rhs == DBL_MRK || lhs == DBL_MRK) {
+                            rDbl = stack_double(stack, sDbl, stackTop + 1);
+                            lDbl = stack_double(stack, sDbl, stackTop);
+                            valBln = (rDbl == rDbl && lDbl == lDbl 
+                                      && lDbl <= rDbl);
+                        }
+                        else {
+                            valBln = (1 == ScriptRuntime.cmp_LE(lhs, rhs));
+                        }
+                        stack[stackTop] = valBln ? Boolean.TRUE : Boolean.FALSE;
+                        break;
+                    case TokenStream.GT :
+                        --stackTop;
+                        rhs = stack[stackTop + 1];    
+                        lhs = stack[stackTop];
+                        if (rhs == DBL_MRK || lhs == DBL_MRK) {
+                            rDbl = stack_double(stack, sDbl, stackTop + 1);
+                            lDbl = stack_double(stack, sDbl, stackTop);
+                            valBln = (rDbl == rDbl && lDbl == lDbl 
+                                      && rDbl < lDbl);
+                        }
+                        else {
+                            valBln = (1 == ScriptRuntime.cmp_LT(rhs, lhs));
+                        }
+                        stack[stackTop] = valBln ? Boolean.TRUE : Boolean.FALSE;
+                        break;
+                    case TokenStream.LT :
+                        --stackTop;
+                        rhs = stack[stackTop + 1];    
+                        lhs = stack[stackTop];
+                        if (rhs == DBL_MRK || lhs == DBL_MRK) {
+                            rDbl = stack_double(stack, sDbl, stackTop + 1);
+                            lDbl = stack_double(stack, sDbl, stackTop);
+                            valBln = (rDbl == rDbl && lDbl == lDbl 
+                                      && lDbl < rDbl);
+                        }
+                        else {
+                            valBln = (1 == ScriptRuntime.cmp_LT(lhs, rhs));
+                        }
+                        stack[stackTop] = valBln ? Boolean.TRUE : Boolean.FALSE;
+                        break;
+                    case TokenStream.IN :
+                        rhs = stack[stackTop];    
+                        if (rhs == DBL_MRK) rhs = doubleWrap(sDbl[stackTop]);
+                        --stackTop;
+                        lhs = stack[stackTop];    
+                        if (lhs == DBL_MRK) lhs = doubleWrap(sDbl[stackTop]);
+                        valBln = ScriptRuntime.in(lhs, rhs, scope);
+                        stack[stackTop] = valBln ? Boolean.TRUE : Boolean.FALSE;
+                        break;
+                    case TokenStream.INSTANCEOF :
+                        rhs = stack[stackTop];    
+                        if (rhs == DBL_MRK) rhs = doubleWrap(sDbl[stackTop]);
+                        --stackTop;
+                        lhs = stack[stackTop];    
+                        if (lhs == DBL_MRK) lhs = doubleWrap(sDbl[stackTop]);
+                        valBln = ScriptRuntime.instanceOf(scope, lhs, rhs);
+                        stack[stackTop] = valBln ? Boolean.TRUE : Boolean.FALSE;
+                        break;
+                    case TokenStream.EQ :
+                        --stackTop;
+                        valBln = do_eq(stack, sDbl, stackTop);
+                        stack[stackTop] = valBln ? Boolean.TRUE : Boolean.FALSE;
+                        break;
+                    case TokenStream.NE :
+                        --stackTop;
+                        valBln = !do_eq(stack, sDbl, stackTop);
+                        stack[stackTop] = valBln ? Boolean.TRUE : Boolean.FALSE;
+                        break;
+                    case TokenStream.SHEQ :
+                        --stackTop;
+                        valBln = do_sheq(stack, sDbl, stackTop);
+                        stack[stackTop] = valBln ? Boolean.TRUE : Boolean.FALSE;
+                        break;
+                    case TokenStream.SHNE :
+                        --stackTop;
+                        valBln = !do_sheq(stack, sDbl, stackTop);
+                        stack[stackTop] = valBln ? Boolean.TRUE : Boolean.FALSE;
+                        break;
+                    case TokenStream.IFNE :
+                        val = stack[stackTop];
+                        if (val != DBL_MRK) {
+                            valBln = !ScriptRuntime.toBoolean(val);
+                        }
+                        else {
+                            valDbl = sDbl[stackTop];
+                            valBln = !(valDbl == valDbl && valDbl != 0.0);
+                        }
+                        --stackTop;
+                        if (valBln) {
+                            if (instructionThreshold != 0) {
+                                instructionCount += pc + 3 - pcPrevBranch;
+                                if (instructionCount > instructionThreshold) {
+                                    cx.observeInstructionCount
+                                        (instructionCount);
+                                    instructionCount = 0;
+                                }
+                            }
+                            pcPrevBranch = pc = getTarget(iCode, pc + 1);
+                            continue;
+                        }
+                        pc += 2;
+                        break;
+                    case TokenStream.IFEQ :
+                        val = stack[stackTop];
+                        if (val != DBL_MRK) {
+                            valBln = ScriptRuntime.toBoolean(val);
+                        }
+                        else {
+                            valDbl = sDbl[stackTop];
+                            valBln = (valDbl == valDbl && valDbl != 0.0);
+                        }
+                        --stackTop;
+                        if (valBln) {
+                            if (instructionThreshold != 0) {
+                                instructionCount += pc + 3 - pcPrevBranch;
+                                if (instructionCount > instructionThreshold) {
+                                    cx.observeInstructionCount
+                                        (instructionCount);
+                                    instructionCount = 0;
+                                }
+                            }
+                            pcPrevBranch = pc = getTarget(iCode, pc + 1);
+                            continue;
+                        }
+                        pc += 2;
+                        break;
+                    case TokenStream.GOTO :
+                        if (instructionThreshold != 0) {
+                            instructionCount += pc + 3 - pcPrevBranch;
+                            if (instructionCount > instructionThreshold) {
+                                cx.observeInstructionCount(instructionCount);
+                                instructionCount = 0;
+                            }
+                        }
+                        pcPrevBranch = pc = getTarget(iCode, pc + 1);
+                        continue;
+                    case TokenStream.GOSUB :
+                        sDbl[++stackTop] = pc + 3;
+                        if (instructionThreshold != 0) {
+                            instructionCount += pc + 3 - pcPrevBranch;
+                            if (instructionCount > instructionThreshold) {
+                                cx.observeInstructionCount(instructionCount);
+                                instructionCount = 0;
+                            }
+                        }
+                        pcPrevBranch = pc = getTarget(iCode, pc + 1);                                    continue;
+                    case TokenStream.RETSUB :
+                        slot = (iCode[pc + 1] & 0xFF);
+                        if (instructionThreshold != 0) {
+                            instructionCount += pc + 2 - pcPrevBranch;
+                            if (instructionCount > instructionThreshold) {
+                                cx.observeInstructionCount(instructionCount);
+                                instructionCount = 0;
+                            }
+                        }
+                        pcPrevBranch = pc = (int)sDbl[LOCAL_SHFT + slot];
+                        continue;
+                    case TokenStream.POP :
+                        stackTop--;
+                        break;
+                    case TokenStream.DUP :
+                        stack[stackTop + 1] = stack[stackTop];
+                        sDbl[stackTop + 1] = sDbl[stackTop];
+                        stackTop++;
+                        break;
+                    case TokenStream.POPV :
+                        result = stack[stackTop];    
+                        if (result == DBL_MRK) 
+                            result = doubleWrap(sDbl[stackTop]);
+                        --stackTop;
+                        break;
+                    case TokenStream.RETURN :
+                        result = stack[stackTop];    
+                        if (result == DBL_MRK) 
+                            result = doubleWrap(sDbl[stackTop]);
+                        --stackTop;
+                        pc = getTarget(iCode, pc + 1);
+                        break;
+                    case TokenStream.ASSERT :
+                        val = stack[stackTop];
+                        if (val != DBL_MRK) {
+                            valBln = ScriptRuntime.toBoolean(val);
+                        } else {
+                            valDbl = sDbl[stackTop];
+                            valBln = (valDbl == valDbl && valDbl != 0.0);
+                        }
+                        --stackTop;
+                        if (!valBln) {
+                            System.out.println("assertion failed");
+                            System.exit(-1);
+                        }
+                        pc += 2;
+                        break;
+                    case TokenStream.BITNOT :
+                        rIntValue = stack_int32(stack, sDbl, stackTop);
+                        stack[stackTop] = DBL_MRK;
+                        sDbl[stackTop] = ~rIntValue;
+                        break;
+                    case TokenStream.BITAND :                
+                        rIntValue = stack_int32(stack, sDbl, stackTop);
+                        --stackTop;
+                        lIntValue = stack_int32(stack, sDbl, stackTop);
+                        stack[stackTop] = DBL_MRK;
+                        sDbl[stackTop] = lIntValue & rIntValue;
+                        break;
+                    case TokenStream.BITOR :
+                        rIntValue = stack_int32(stack, sDbl, stackTop);
+                        --stackTop;
+                        lIntValue = stack_int32(stack, sDbl, stackTop);
+                        stack[stackTop] = DBL_MRK;
+                        sDbl[stackTop] = lIntValue | rIntValue;
+                        break;
+                    case TokenStream.BITXOR :
+                        rIntValue = stack_int32(stack, sDbl, stackTop);
+                        --stackTop;
+                        lIntValue = stack_int32(stack, sDbl, stackTop);
+                        stack[stackTop] = DBL_MRK;
+                        sDbl[stackTop] = lIntValue ^ rIntValue;
+                        break;
+                    case TokenStream.LSH :
+                        rIntValue = stack_int32(stack, sDbl, stackTop);
+                        --stackTop;
+                        lIntValue = stack_int32(stack, sDbl, stackTop);
+                        stack[stackTop] = DBL_MRK;
+                        sDbl[stackTop] = lIntValue << rIntValue;
+                        break;
+                    case TokenStream.RSH :
+                        rIntValue = stack_int32(stack, sDbl, stackTop);
+                        --stackTop;
+                        lIntValue = stack_int32(stack, sDbl, stackTop);
+                        stack[stackTop] = DBL_MRK;
+                        sDbl[stackTop] = lIntValue >> rIntValue;
+                        break;
+                    case TokenStream.URSH :
+                        rIntValue = stack_int32(stack, sDbl, stackTop) & 0x1F;
+                        --stackTop;
+                        lLongValue = stack_uint32(stack, sDbl, stackTop);
+                        stack[stackTop] = DBL_MRK;
+                        sDbl[stackTop] = lLongValue >>> rIntValue;
+                        break;
+                    case TokenStream.ADD :
+                        --stackTop;
+                        do_add(stack, sDbl, stackTop);
+                        break;
+                    case TokenStream.SUB :
+                        rDbl = stack_double(stack, sDbl, stackTop);
+                        --stackTop;
+                        lDbl = stack_double(stack, sDbl, stackTop);
+                        stack[stackTop] = DBL_MRK;
+                        sDbl[stackTop] = lDbl - rDbl;
+                        break;
+                    case TokenStream.NEG :
+                        rDbl = stack_double(stack, sDbl, stackTop);
+                        stack[stackTop] = DBL_MRK;
+                        sDbl[stackTop] = -rDbl;
+                        break;
+                    case TokenStream.POS :
+                        rDbl = stack_double(stack, sDbl, stackTop);
+                        stack[stackTop] = DBL_MRK;
+                        sDbl[stackTop] = rDbl;
+                        break;
+                    case TokenStream.MUL :
+                        rDbl = stack_double(stack, sDbl, stackTop);
+                        --stackTop;
+                        lDbl = stack_double(stack, sDbl, stackTop);
+                        stack[stackTop] = DBL_MRK;
+                        sDbl[stackTop] = lDbl * rDbl;
+                        break;
+                    case TokenStream.DIV :
+                        rDbl = stack_double(stack, sDbl, stackTop);
+                        --stackTop;
+                        lDbl = stack_double(stack, sDbl, stackTop);
+                        stack[stackTop] = DBL_MRK;
+                        // Detect the divide by zero or let Java do it ?
+                        sDbl[stackTop] = lDbl / rDbl;
+                        break;
+                    case TokenStream.MOD :
+                        rDbl = stack_double(stack, sDbl, stackTop);
+                        --stackTop;
+                        lDbl = stack_double(stack, sDbl, stackTop);
+                        stack[stackTop] = DBL_MRK;
+                        sDbl[stackTop] = lDbl % rDbl;
+                        break;
+                    case TokenStream.BINDNAME :
+                        stack[++stackTop] = 
+                                ScriptRuntime.bind(scope, 
+                                         getString(theData.itsStringTable, 
+                                                   iCode, pc + 1));
+                        pc += 2;
+                        break;
+                    case TokenStream.GETBASE :
+                        stack[++stackTop] =
+                                ScriptRuntime.getBase(scope, 
+                                         getString(theData.itsStringTable,
+                                                                iCode, pc + 1));
+                        pc += 2;
+                        break;
+                    case TokenStream.SETNAME :
+                        rhs = stack[stackTop];    
+                        if (rhs == DBL_MRK) rhs = doubleWrap(sDbl[stackTop]);
+                        --stackTop;
+                        lhs = stack[stackTop];    
+                        if (lhs == DBL_MRK) lhs = doubleWrap(sDbl[stackTop]);
+                        // what about class cast exception here ?
+                        stack[stackTop] = ScriptRuntime.setName
+                            ((Scriptable)lhs, rhs, scope, 
+                             getString(theData.itsStringTable, iCode, pc + 1));
+                        pc += 2;
+                        break;
+                    case TokenStream.DELPROP :
+                        rhs = stack[stackTop];    
+                        if (rhs == DBL_MRK) rhs = doubleWrap(sDbl[stackTop]);
+                        --stackTop;
+                        lhs = stack[stackTop];    
+                        if (lhs == DBL_MRK) lhs = doubleWrap(sDbl[stackTop]);
+                        stack[stackTop] = ScriptRuntime.delete(lhs, rhs);
+                        break;
+                    case TokenStream.GETPROP :
+                        name = (String)stack[stackTop];
+                        --stackTop;
+                        lhs = stack[stackTop];    
+                        if (lhs == DBL_MRK) lhs = doubleWrap(sDbl[stackTop]);
+                        stack[stackTop] 
+                                = ScriptRuntime.getProp(lhs, name, scope);
+                        break;
+                    case TokenStream.SETPROP :
+                        rhs = stack[stackTop];    
+                        if (rhs == DBL_MRK) rhs = doubleWrap(sDbl[stackTop]);
+                        --stackTop;
+                        name = (String)stack[stackTop];
+                        --stackTop;
+                        lhs = stack[stackTop];    
+                        if (lhs == DBL_MRK) lhs = doubleWrap(sDbl[stackTop]);
+                        stack[stackTop] 
+                                = ScriptRuntime.setProp(lhs, name, rhs, scope);
+                        break;
+                    case TokenStream.GETELEM :
+                        id = stack[stackTop];    
+                        if (id == DBL_MRK) id = doubleWrap(sDbl[stackTop]);
+                        --stackTop;
+                        lhs = stack[stackTop];    
+                        if (lhs == DBL_MRK) lhs = doubleWrap(sDbl[stackTop]);
+                        stack[stackTop] 
+                                = ScriptRuntime.getElem(lhs, id, scope);
+                        break;
+                    case TokenStream.SETELEM :
+                        rhs = stack[stackTop];    
+                        if (rhs == DBL_MRK) rhs = doubleWrap(sDbl[stackTop]);
+                        --stackTop;
+                        id = stack[stackTop];    
+                        if (id == DBL_MRK) id = doubleWrap(sDbl[stackTop]);
+                        --stackTop;
+                        lhs = stack[stackTop];    
+                        if (lhs == DBL_MRK) lhs = doubleWrap(sDbl[stackTop]);
+                        stack[stackTop] 
+                                = ScriptRuntime.setElem(lhs, id, rhs, scope);
+                        break;
+                    case TokenStream.PROPINC :
+                        name = (String)stack[stackTop];
+                        --stackTop;
+                        lhs = stack[stackTop];    
+                        if (lhs == DBL_MRK) lhs = doubleWrap(sDbl[stackTop]);
+                        stack[stackTop] 
+                                = ScriptRuntime.postIncrement(lhs, name, scope);
+                        break;
+                    case TokenStream.PROPDEC :
+                        name = (String)stack[stackTop];
+                        --stackTop;
+                        lhs = stack[stackTop];    
+                        if (lhs == DBL_MRK) lhs = doubleWrap(sDbl[stackTop]);
+                        stack[stackTop] 
+                                = ScriptRuntime.postDecrement(lhs, name, scope);
+                        break;
+                    case TokenStream.ELEMINC :
+                        rhs = stack[stackTop];    
+                        if (rhs == DBL_MRK) rhs = doubleWrap(sDbl[stackTop]);
+                        --stackTop;
+                        lhs = stack[stackTop];    
+                        if (lhs == DBL_MRK) lhs = doubleWrap(sDbl[stackTop]);
+                        stack[stackTop] 
+                           = ScriptRuntime.postIncrementElem(lhs, rhs, scope);
+                        break;
+                    case TokenStream.ELEMDEC :
+                        rhs = stack[stackTop];    
+                        if (rhs == DBL_MRK) rhs = doubleWrap(sDbl[stackTop]);
+                        --stackTop;
+                        lhs = stack[stackTop];    
+                        if (lhs == DBL_MRK) lhs = doubleWrap(sDbl[stackTop]);
+                        stack[stackTop] 
+                           = ScriptRuntime.postDecrementElem(lhs, rhs, scope);
+                        break;
+                    case TokenStream.GETTHIS :
+                        lhs = stack[stackTop];    
+                        if (lhs == DBL_MRK) lhs = doubleWrap(sDbl[stackTop]);
+                        stack[stackTop] 
+                            = ScriptRuntime.getThis((Scriptable)lhs);
+                        break;
+                    case TokenStream.NEWTEMP :
+                        slot = (iCode[++pc] & 0xFF);
+                        stack[LOCAL_SHFT + slot] = stack[stackTop];
+                        sDbl[LOCAL_SHFT + slot] = sDbl[stackTop];
+                        break;
+                    case TokenStream.USETEMP :
+                        slot = (iCode[++pc] & 0xFF);
+                        ++stackTop;
+                        stack[stackTop] = stack[LOCAL_SHFT + slot];
+                        sDbl[stackTop] = sDbl[LOCAL_SHFT + slot];
+                        break;
+                    case TokenStream.CALLSPECIAL :
+                        if (instructionThreshold != 0) {
+                            instructionCount += INVOCATION_COST;
+                            cx.instructionCount = instructionCount;
+                            instructionCount = -1;
+                        }
+                        int lineNum = (iCode[pc + 1] << 8) 
+                                      | (iCode[pc + 2] & 0xFF);   
+                        name = getString(theData.itsStringTable, iCode, pc + 3);
+                        count = (iCode[pc + 5] << 8) | (iCode[pc + 6] & 0xFF);
+                        outArgs = new Object[count];
+                        for (i = count - 1; i >= 0; i--) {
+                            val = stack[stackTop];    
+                            if (val == DBL_MRK) 
+                                val = doubleWrap(sDbl[stackTop]);
+                            outArgs[i] = val;
+                            --stackTop;
+                        }
+                        rhs = stack[stackTop];    
+                        if (rhs == DBL_MRK) rhs = doubleWrap(sDbl[stackTop]);
+                        --stackTop;
+                        lhs = stack[stackTop];    
+                        if (lhs == DBL_MRK) lhs = doubleWrap(sDbl[stackTop]);
+                        stack[stackTop] = ScriptRuntime.callSpecial(
+                                            cx, lhs, rhs, outArgs, 
+                                            thisObj, scope, name, lineNum);
+                        pc += 6;
+                        instructionCount = cx.instructionCount;
+                        break;
+                    case TokenStream.CALL :
+                        if (instructionThreshold != 0) {
+                            instructionCount += INVOCATION_COST;
+                            cx.instructionCount = instructionCount;
+                            instructionCount = -1;
+                        }
+                        cx.instructionCount = instructionCount;
+                        count = (iCode[pc + 3] << 8) | (iCode[pc + 4] & 0xFF);
+                        outArgs = new Object[count];
+                        for (i = count - 1; i >= 0; i--) {
+                            val = stack[stackTop];    
+                            if (val == DBL_MRK) 
+                                val = doubleWrap(sDbl[stackTop]);
+                            outArgs[i] = val;
+                            --stackTop;
+                        }
+                        rhs = stack[stackTop];    
+                        if (rhs == DBL_MRK) rhs = doubleWrap(sDbl[stackTop]);
+                        --stackTop;
+                        lhs = stack[stackTop];    
+                        if (lhs == DBL_MRK) lhs = doubleWrap(sDbl[stackTop]);
+                        if (lhs == undefined) {
+                            lhs = getString(theData.itsStringTable, iCode, 
+                                            pc + 1);
+                        }
+                        Scriptable calleeScope = scope;
+                        if (theData.itsNeedsActivation) {
+                            calleeScope = ScriptableObject.
+                                getTopLevelScope(scope);
+                        }
+                        stack[stackTop] = ScriptRuntime.call(cx, lhs, rhs, 
+                                                             outArgs, 
+                                                             calleeScope);
+                        pc += 4;                                                                         instructionCount = cx.instructionCount;
+                        break;
+                    case TokenStream.NEW :
+                        if (instructionThreshold != 0) {
+                            instructionCount += INVOCATION_COST;
+                            cx.instructionCount = instructionCount;
+                            instructionCount = -1;
+                        }
+                        count = (iCode[pc + 3] << 8) | (iCode[pc + 4] & 0xFF);
+                        outArgs = new Object[count];
+                        for (i = count - 1; i >= 0; i--) {
+                            val = stack[stackTop];    
+                            if (val == DBL_MRK) 
+                                val = doubleWrap(sDbl[stackTop]);
+                            outArgs[i] = val;
+                            --stackTop;
+                        }
+                        lhs = stack[stackTop];    
+                        if (lhs == DBL_MRK) lhs = doubleWrap(sDbl[stackTop]);
+                        if (lhs == undefined && 
+                            (iCode[pc+1] << 8) + (iCode[pc+2] & 0xFF) != -1) 
+                        {
+                            // special code for better error message for call 
+                            //  to undefined
+                            lhs = getString(theData.itsStringTable, iCode, 
+                                            pc + 1);
+                        }
+                        stack[stackTop] = ScriptRuntime.newObject(cx, lhs, 
+                                                                  outArgs, 
+                                                                  scope);
+                        pc += 4;                                                                         instructionCount = cx.instructionCount;
+                        break;
+                    case TokenStream.TYPEOF :
+                        lhs = stack[stackTop];    
+                        if (lhs == DBL_MRK) lhs = doubleWrap(sDbl[stackTop]);
+                        stack[stackTop] = ScriptRuntime.typeof(lhs);
+                        break;
+                    case TokenStream.TYPEOFNAME :
+                        name = getString(theData.itsStringTable, iCode, pc + 1);
+                        stack[++stackTop] 
+                                    = ScriptRuntime.typeofName(scope, name);
+                        pc += 2;
+                        break;
+                    case TokenStream.STRING :
+                        stack[++stackTop] = getString(theData.itsStringTable,
+                                                                iCode, pc + 1);
+                        pc += 2;
+                        break;
+                    case TokenStream.NUMBER :
+                        ++stackTop;
+                        stack[stackTop] = DBL_MRK;
+                        sDbl[stackTop] = getNumber(theData.itsNumberTable,
+                                                   iCode, pc + 1);
+                        pc += 2;
+                        break;
+                    case TokenStream.NAME :
+                        stack[++stackTop] = ScriptRuntime.name(scope,
+                                       getString(theData.itsStringTable,
+                                                                iCode, pc + 1));
+                        pc += 2;
+                        break;
+                    case TokenStream.NAMEINC :
+                        stack[++stackTop] = ScriptRuntime.postIncrement(scope,
+                                       getString(theData.itsStringTable,
+                                                                iCode, pc + 1));
+                        pc += 2;
+                        break;
+                    case TokenStream.NAMEDEC :
+                        stack[++stackTop] = ScriptRuntime.postDecrement(scope,
+                                       getString(theData.itsStringTable,
+                                                                iCode, pc + 1));
+                        pc += 2;
+                        break;
+                    case TokenStream.SETVAR :
+                        slot = (iCode[++pc] & 0xFF);
+                        stack[VAR_SHFT + slot] = stack[stackTop];
+                        sDbl[VAR_SHFT + slot] = sDbl[stackTop];
+                        break;
+                    case TokenStream.GETVAR :
+                        slot = (iCode[++pc] & 0xFF);
+                        ++stackTop;
+                        stack[stackTop] = stack[VAR_SHFT + slot];
+                        sDbl[stackTop] = sDbl[VAR_SHFT + slot];
+                        break;
+                    case TokenStream.VARINC :
+                        slot = (iCode[++pc] & 0xFF);
+                        ++stackTop;
+                        stack[stackTop] = stack[VAR_SHFT + slot];
+                        sDbl[stackTop] = sDbl[VAR_SHFT + slot];
+                        stack[VAR_SHFT + slot] = DBL_MRK;
+                        sDbl[VAR_SHFT + slot] 
+                            = stack_double(stack, sDbl, stackTop) + 1.0;
+                        break;
+                    case TokenStream.VARDEC :
+                        slot = (iCode[++pc] & 0xFF);
+                        ++stackTop;
+                        stack[stackTop] = stack[VAR_SHFT + slot];
+                        sDbl[stackTop] = sDbl[VAR_SHFT + slot];
+                        stack[VAR_SHFT + slot] = DBL_MRK;
+                        sDbl[VAR_SHFT + slot] 
+                            = stack_double(stack, sDbl, stackTop) - 1.0;
+                        break;
+                    case TokenStream.ZERO :
+                        ++stackTop;
+                        stack[stackTop] = DBL_MRK;
+                        sDbl[stackTop] = 0;
+                        break;
+                    case TokenStream.ONE :
+                        ++stackTop;
+                        stack[stackTop] = DBL_MRK;
+                        sDbl[stackTop] = 1;
+                        break;
+                    case TokenStream.NULL :
+                        stack[++stackTop] = null;
+                        break;
+                    case TokenStream.THIS :
+                        stack[++stackTop] = thisObj;
+                        break;
+                    case TokenStream.THISFN :
+                        stack[++stackTop] = fnOrScript;
+                        break;
+                    case TokenStream.FALSE :
+                        stack[++stackTop] = Boolean.FALSE;
+                        break;
+                    case TokenStream.TRUE :
+                        stack[++stackTop] = Boolean.TRUE;
+                        break;
+                    case TokenStream.UNDEFINED :
+                        stack[++stackTop] = Undefined.instance;
+                        break;
+                    case TokenStream.THROW :
+                        result = stack[stackTop];
+                        if (result == DBL_MRK) 
+                            result = doubleWrap(sDbl[stackTop]);
+                        --stackTop;
+                        throw new JavaScriptException(result);
+                    case TokenStream.JTHROW :
+                        result = stack[stackTop];
+                        // No need to check for DBL_MRK: result is Exception
+                        --stackTop;
+                        if (result instanceof JavaScriptException)
+                            throw (JavaScriptException)result;
+                        else
+                            throw (RuntimeException)result;
+                    case TokenStream.ENTERWITH :
+                        lhs = stack[stackTop];    
+                        if (lhs == DBL_MRK) lhs = doubleWrap(sDbl[stackTop]);
+                        --stackTop;
+                        scope = ScriptRuntime.enterWith(lhs, scope);
+                        break;
+                    case TokenStream.LEAVEWITH :
+                        scope = ScriptRuntime.leaveWith(scope);
+                        break;
+                    case TokenStream.NEWSCOPE :
+                        stack[++stackTop] = ScriptRuntime.newScope();
+                        break;
+                    case TokenStream.ENUMINIT :
+                        slot = (iCode[++pc] & 0xFF);
+                        lhs = stack[stackTop];    
+                        if (lhs == DBL_MRK) lhs = doubleWrap(sDbl[stackTop]);
+                        --stackTop;
+                        stack[LOCAL_SHFT + slot] 
+                            = ScriptRuntime.initEnum(lhs, scope);
+                        break;
+                    case TokenStream.ENUMNEXT :
+                        slot = (iCode[++pc] & 0xFF);
+                        val = stack[LOCAL_SHFT + slot];    
+                        ++stackTop;
+                        stack[stackTop] = ScriptRuntime.
+                            nextEnum((Enumeration)val);
+                        break;
+                    case TokenStream.GETPROTO :
+                        lhs = stack[stackTop];    
+                        if (lhs == DBL_MRK) lhs = doubleWrap(sDbl[stackTop]);
+                        stack[stackTop] = ScriptRuntime.getProto(lhs, scope);
+                        break;
+                    case TokenStream.GETPARENT :
+                        lhs = stack[stackTop];    
+                        if (lhs == DBL_MRK) lhs = doubleWrap(sDbl[stackTop]);
+                        stack[stackTop] = ScriptRuntime.getParent(lhs);
+                        break;
+                    case TokenStream.GETSCOPEPARENT :
+                        lhs = stack[stackTop];    
+                        if (lhs == DBL_MRK) lhs = doubleWrap(sDbl[stackTop]);
+                        stack[stackTop] = ScriptRuntime.getParent(lhs, scope);
+                        break;
+                    case TokenStream.SETPROTO :
+                        rhs = stack[stackTop];    
+                        if (rhs == DBL_MRK) rhs = doubleWrap(sDbl[stackTop]);
+                        --stackTop;
+                        lhs = stack[stackTop];    
+                        if (lhs == DBL_MRK) lhs = doubleWrap(sDbl[stackTop]);
+                        stack[stackTop]
+                                = ScriptRuntime.setProto(lhs, rhs, scope);
+                        break;
+                    case TokenStream.SETPARENT :
+                        rhs = stack[stackTop];    
+                        if (rhs == DBL_MRK) rhs = doubleWrap(sDbl[stackTop]);
+                        --stackTop;
+                        lhs = stack[stackTop];    
+                        if (lhs == DBL_MRK) lhs = doubleWrap(sDbl[stackTop]);
+                        stack[stackTop]
+                                = ScriptRuntime.setParent(lhs, rhs, scope);
+                        break;
+                    case TokenStream.SCOPE :
+                        stack[++stackTop] = scope;
+                        break;
+                    case TokenStream.CLOSURE :
+                        i = (iCode[pc + 1] << 8) | (iCode[pc + 2] & 0xFF);
+                        stack[++stackTop] 
+                            = new InterpretedFunction(
+                                    theData.itsNestedFunctions[i],
+                                    scope, cx);
+                        createFunctionObject(
+                              (InterpretedFunction)stack[stackTop], scope);
+                        pc += 2;
+                        break;
+                    case TokenStream.OBJECT :
+                        i = (iCode[pc + 1] << 8) | (iCode[pc + 2] & 0xFF);                    
+                        stack[++stackTop] = theData.itsRegExpLiterals[i];
+                        pc += 2;
+                        break;
+                    case TokenStream.SOURCEFILE :    
+                        cx.interpreterSourceFile = theData.itsSourceFile;
+                        break;
+                    case TokenStream.LINE :    
+                    case TokenStream.BREAKPOINT :
+                        i = (iCode[pc + 1] << 8) | (iCode[pc + 2] & 0xFF);                    
+                        cx.interpreterLine = i;
+                        if (frame != null)
+                            frame.setLineNumber(i);
+                        if ((iCode[pc] & 0xff) == TokenStream.BREAKPOINT ||
+                            cx.inLineStepMode) 
+                        {
+                            cx.getDebuggableEngine().
+                                getDebugger().handleBreakpointHit(cx);
+                        }
+                        pc += 2;
+                        break;
+                    default :
+                        dumpICode(theData);
+                        throw new RuntimeException("Unknown icode : "
+                                     + (iCode[pc] & 0xff) + " @ pc : " + pc);
+                }
+                pc++;
+            }
+            catch (Throwable ex) {
+                cx.interpreterSecurityDomain = null;
+            
+                if (instructionThreshold != 0) {
+                    if (instructionCount < 0) {
+                        // throw during function call
+                        instructionCount = cx.instructionCount;
+                    }
+                    else {
+                        // throw during any other operation
+                        instructionCount += pc - pcPrevBranch;
+                        cx.instructionCount = instructionCount;
+                    }
+                }
+
+                final int SCRIPT_THROW = 0, ECMA = 1, RUNTIME = 2, OTHER = 3;
+
+                int exType;
+                Object errObj; // Object seen by catch
+                if (ex instanceof JavaScriptException) {
+                    errObj = ScriptRuntime.
+                        unwrapJavaScriptException((JavaScriptException)ex);
+                    exType = SCRIPT_THROW;
+                }
+                else if (ex instanceof EcmaError) {
+                    // an offical ECMA error object,
+                    errObj = ((EcmaError)ex).getErrorObject();
+                    exType = ECMA;
+                }
+                else if (ex instanceof RuntimeException) {
+                    errObj = ex;
+                    exType = RUNTIME;
+                }
+                else {
+                    errObj = ex; // Error instance
+                    exType = OTHER;
+                }
+
+                if (exType != OTHER && cx.debugger != null) {
+                    cx.debugger.handleExceptionThrown(cx, errObj);
+                }
+
+                boolean rethrow = true;
+                if (exType != OTHER && tryStackTop > 0) {
+                    --tryStackTop;
+                    if (exType == SCRIPT_THROW || exType == ECMA) {
+                        // Check for catch only for 
+                        // JavaScriptException and EcmaError
+                        pc = catchStack[tryStackTop * 2];
+                        if (pc != 0) {
+                            // Has catch block
+                            rethrow = false;
+                        }
+                    }
+                    if (rethrow) {
+                        pc = catchStack[tryStackTop * 2 + 1];
+                        if (pc != 0) {
+                            // has finally block
+                            rethrow = false;
+                            errObj = ex;
+                        }
+                    }
+                }
+
+                if (rethrow) {
+                    if (frame != null)
+                        cx.popFrame();
+                    
+                    if (exType == SCRIPT_THROW)
+                        throw (JavaScriptException)ex;
+                    if (exType == ECMA || exType == RUNTIME) 
+                        throw (RuntimeException)ex;
+                    throw (Error)ex;
+                }
+            
+                // We caught an exception,
+
+                // Notify instruction observer if necessary
+                // and point pcPrevBranch to start of catch/finally block
+                if (instructionThreshold != 0) {
+                    if (instructionCount > instructionThreshold) {
+                        // Note: this can throw Error 
+                        cx.observeInstructionCount(instructionCount);
+                        instructionCount = 0;
+                    }
+                }
+                pcPrevBranch = pc;
+
+                // prepare stack and restore this function's security domain.
+                scope = (Scriptable)stack[TRY_SCOPE_SHFT + tryStackTop];
+                stackTop = 0;
+                stack[0] = errObj;
+                cx.interpreterSecurityDomain = theData.securityDomain;
+            }
+        }
+        cx.interpreterSecurityDomain = savedSecurityDomain;
+        if (frame != null)
+            cx.popFrame();
+
+        if (instructionThreshold != 0) {
+            if (instructionCount > instructionThreshold) {
+                cx.observeInstructionCount(instructionCount);
+                instructionCount = 0;
+            }
+            cx.instructionCount = instructionCount;
+        }
+
+        return result;    
+    }
+    
+    private static Object doubleWrap(double x) {
+        return new Double(x);
+    }
+
+    private static int stack_int32(Object[] stack, double[] stackDbl, int i) {
+        Object x = stack[i];
+        return (x != DBL_MRK)
+            ? ScriptRuntime.toInt32(x)
+            : ScriptRuntime.toInt32(stackDbl[i]);
+    }
+    
+    private static long stack_uint32(Object[] stack, double[] stackDbl, int i) {
+        Object x = stack[i];
+        return (x != DBL_MRK)
+            ? ScriptRuntime.toUint32(x)
+            : ScriptRuntime.toUint32(stackDbl[i]);
+    }
+    
+    private static double stack_double(Object[] stack, double[] stackDbl, 
+                                       int i) 
+    {
+        Object x = stack[i];
+        return (x != DBL_MRK) ? ScriptRuntime.toNumber(x) : stackDbl[i];
+    }
+    
+    private static void do_add(Object[] stack, double[] stackDbl, int stackTop)
+    {
+        Object rhs = stack[stackTop + 1];    
+        Object lhs = stack[stackTop];
+        if (rhs == DBL_MRK) {
+            double rDbl = stackDbl[stackTop + 1];
+            if (lhs == DBL_MRK) {
+                stackDbl[stackTop] += rDbl;
+            }
+            else {
+                do_add(lhs, rDbl, stack, stackDbl, stackTop, true);
+            }
+        }
+        else if (lhs == DBL_MRK) {
+            do_add(rhs, stackDbl[stackTop], stack, stackDbl, stackTop, false);
+        }
+        else {
+            if (lhs instanceof Scriptable)
+                lhs = ((Scriptable) lhs).getDefaultValue(null);
+            if (rhs instanceof Scriptable)
+                rhs = ((Scriptable) rhs).getDefaultValue(null);
+            if (lhs instanceof String || rhs instanceof String) {
+                stack[stackTop] = ScriptRuntime.toString(lhs)
+                                   + ScriptRuntime.toString(rhs);
+            }
+            else {
+                double lDbl = (lhs instanceof Number)
+                    ? ((Number)lhs).doubleValue() : ScriptRuntime.toNumber(lhs);
+                double rDbl = (rhs instanceof Number)
+                    ? ((Number)rhs).doubleValue() : ScriptRuntime.toNumber(rhs);
+                stack[stackTop] = DBL_MRK;
+                stackDbl[stackTop] = lDbl + rDbl;
+            }
+        }
+    }
+    
+    // x + y when x is Number, see 
+    private static void do_add
+        (Object lhs, double rDbl, 
+         Object[] stack, double[] stackDbl, int stackTop, 
+         boolean left_right_order) 
+    {
+        if (lhs instanceof Scriptable) {
+            if (lhs == Undefined.instance) { lhs = ScriptRuntime.NaNobj; }
+            lhs = ((Scriptable)lhs).getDefaultValue(null);
+        }
+        if (lhs instanceof String) {
+            if (left_right_order) {
+                stack[stackTop] = (String)lhs + ScriptRuntime.toString(rDbl);
+            }
+            else {
+                stack[stackTop] = ScriptRuntime.toString(rDbl) + (String)lhs;
+            }
+        }
+        else {
+            double lDbl = (lhs instanceof Number) 
+                ? ((Number)lhs).doubleValue() : ScriptRuntime.toNumber(lhs);
+            stack[stackTop] = DBL_MRK;
+            stackDbl[stackTop] = lDbl + rDbl;
+        }
+    }
+
+
+    
+    private static boolean do_eq(Object[] stack, double[] stackDbl,
+                                 int stackTop)
+    {
+        boolean result;
+        Object rhs = stack[stackTop + 1];    
+        Object lhs = stack[stackTop];
+        if (rhs == DBL_MRK) {
+            if (lhs == DBL_MRK) {
+                result = (stackDbl[stackTop] == stackDbl[stackTop + 1]);
+            }
+            else {
+                result = do_eq(stackDbl[stackTop + 1], lhs);
+            }
+        }
+        else {
+            if (lhs == DBL_MRK) {
+                result = do_eq(stackDbl[stackTop], rhs);
+            }
+            else {
+                result = ScriptRuntime.eq(lhs, rhs);
+            }
+        }
+        return result;
+    }
+    
+// Optimized version of ScriptRuntime.eq if x is a Number    
+    private static boolean do_eq(double x, Object y) {
+        for (;;) {
+            if (y instanceof Number) {
+                return x == ((Number) y).doubleValue();
+            }
+            if (y instanceof String) {
+                return x == ScriptRuntime.toNumber((String)y);
+            }
+            if (y instanceof Boolean) {
+                return x == (((Boolean)y).booleanValue() ? 1 : 0);
+            }
+            if (y instanceof Scriptable) {
+                if (y == Undefined.instance) { return false; }
+                y = ScriptRuntime.toPrimitive(y);
+                continue;
+            }
+            return false;
+        }
+    }
+
+    private static boolean do_sheq(Object[] stack, double[] stackDbl,
+                                   int stackTop)
+    {
+        boolean result;
+        Object rhs = stack[stackTop + 1];    
+        Object lhs = stack[stackTop];
+        if (rhs == DBL_MRK) {
+            double rDbl = stackDbl[stackTop + 1];
+            if (lhs == DBL_MRK) {
+                result = (stackDbl[stackTop] == rDbl);
+            }
+            else {
+                result = (lhs instanceof Number);
+                if (result) {
+                    result = (((Number)lhs).doubleValue() == rDbl);
+                }
+            }
+        }
+        else if (rhs instanceof Number) {
+            double rDbl = ((Number)rhs).doubleValue();
+            if (lhs == DBL_MRK) {
+                result = (stackDbl[stackTop] == rDbl);
+            }
+            else {
+                result = (lhs instanceof Number);
+                if (result) {
+                    result = (((Number)lhs).doubleValue() == rDbl);
+                }
+            }
+        }
+        else {
+            result = ScriptRuntime.shallowEq(lhs, rhs);
+        }
+        return result;
+    }
+    
+    private int version;
+    private boolean inLineStepMode;
+    private StringBuffer debugSource;
+
+    private static final Object DBL_MRK = new Object();
+}
diff --git a/src/org/mozilla/javascript/InterpreterData.java b/src/org/mozilla/javascript/InterpreterData.java
new file mode 100644
index 0000000..cdf4e45
--- /dev/null
+++ b/src/org/mozilla/javascript/InterpreterData.java
@@ -0,0 +1,130 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * The contents of this file are subject to the Netscape Public
+ * License Version 1.1 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.mozilla.org/NPL/
+ *
+ * Software distributed under the License is distributed on an "AS
+ * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express oqr
+ * implied. See the License for the specific language governing
+ * rights and limitations under the License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is Netscape
+ * Communications Corporation.  Portions created by Netscape are
+ * Copyright (C) 1997-2000 Netscape Communications Corporation. All
+ * Rights Reserved.
+ *
+ * Contributor(s): 
+ * Norris Boyd
+ * Roger Lawrence
+ *
+ * Alternatively, the contents of this file may be used under the
+ * terms of the GNU Public License (the "GPL"), in which case the
+ * provisions of the GPL are applicable instead of those above.
+ * If you wish to allow use of your version of this file only
+ * under the terms of the GPL and not to allow others to use your
+ * version of this file under the NPL, indicate your decision by
+ * deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL.  If you do not delete
+ * the provisions above, a recipient may use your version of this
+ * file under either the NPL or the GPL.
+ */
+
+package org.mozilla.javascript;
+
+import java.util.Vector;
+
+class InterpreterData {
+    
+    static final int INITIAL_MAX_ICODE_LENGTH = 1024;
+    static final int INITIAL_STRINGTABLE_SIZE = 64;
+    static final int INITIAL_NUMBERTABLE_SIZE = 64;
+    
+    InterpreterData(int lastICodeTop, int lastStringTableIndex, 
+                    int lastNumberTableIndex, Object securityDomain,
+                    boolean useDynamicScope, boolean checkThis)
+    {
+        itsICodeTop = lastICodeTop == 0 
+                      ? INITIAL_MAX_ICODE_LENGTH
+                      : lastICodeTop * 2;
+        itsICode = new byte[itsICodeTop];
+
+        itsStringTable = new String[lastStringTableIndex == 0
+                                    ? INITIAL_STRINGTABLE_SIZE
+                                    : lastStringTableIndex * 2];
+
+        itsNumberTable = new double[lastNumberTableIndex == 0
+                                    ? INITIAL_NUMBERTABLE_SIZE
+                                    : lastNumberTableIndex * 2];
+        
+        itsUseDynamicScope = useDynamicScope;
+        itsCheckThis = checkThis;
+        if (securityDomain == null)
+            Context.checkSecurityDomainRequired();
+        this.securityDomain = securityDomain;
+    }
+    
+    public boolean placeBreakpoint(int line) { // XXX throw exn?
+        int offset = getOffset(line);
+        if (offset != -1 && (itsICode[offset] == (byte)TokenStream.LINE ||
+                             itsICode[offset] == (byte)TokenStream.BREAKPOINT))
+        {
+            itsICode[offset] = (byte) TokenStream.BREAKPOINT;
+            return true;
+        }
+        return false;
+    }
+    
+    public boolean removeBreakpoint(int line) {
+        int offset = getOffset(line);
+        if (offset != -1 && itsICode[offset] == (byte) TokenStream.BREAKPOINT)
+        {
+            itsICode[offset] = (byte) TokenStream.LINE;
+            return true;
+        }
+        return false;
+    }
+    
+    private int getOffset(int line) {
+        int offset = itsLineNumberTable.getInt(line, -1);
+        if (0 <= offset && offset <= itsICode.length) {
+            return offset;
+        }
+        return -1;
+    }    
+    
+    String itsName;
+    String itsSource;
+    String itsSourceFile;
+    boolean itsNeedsActivation;
+    boolean itsFromEvalCode;
+    boolean itsUseDynamicScope;
+    boolean itsCheckThis;
+    byte itsFunctionType;
+
+    String[] itsStringTable;
+    int itsStringTableIndex;
+
+    double[] itsNumberTable;
+    int itsNumberTableIndex;
+    
+    InterpretedFunction[] itsNestedFunctions;
+    
+    Object[] itsRegExpLiterals;
+
+    byte[] itsICode;
+    int itsICodeTop;
+    
+    int itsMaxLocals;
+    int itsMaxArgs;
+    int itsMaxStack;
+    int itsMaxTryDepth;
+    
+    UintMap itsLineNumberTable;
+
+    Object securityDomain;
+}
diff --git a/src/org/mozilla/javascript/InterpreterFrame.java b/src/org/mozilla/javascript/InterpreterFrame.java
new file mode 100644
index 0000000..3df4fce
--- /dev/null
+++ b/src/org/mozilla/javascript/InterpreterFrame.java
@@ -0,0 +1,77 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * The contents of this file are subject to the Netscape Public
+ * License Version 1.1 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.mozilla.org/NPL/
+ *
+ * Software distributed under the License is distributed on an "AS
+ * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express oqr
+ * implied. See the License for the specific language governing
+ * rights and limitations under the License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is Netscape
+ * Communications Corporation.  Portions created by Netscape are
+ * Copyright (C) 1997-2000 Netscape Communications Corporation. All
+ * Rights Reserved.
+ *
+ * Contributor(s): 
+ * Norris Boyd
+ *
+ * Alternatively, the contents of this file may be used under the
+ * terms of the GNU Public License (the "GPL"), in which case the
+ * provisions of the GPL are applicable instead of those above.
+ * If you wish to allow use of your version of this file only
+ * under the terms of the GPL and not to allow others to use your
+ * version of this file under the NPL, indicate your decision by
+ * deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL.  If you do not delete
+ * the provisions above, a recipient may use your version of this
+ * file under either the NPL or the GPL.
+ */
+
+package org.mozilla.javascript;
+
+import java.util.Vector;
+
+import org.mozilla.javascript.debug.*;
+
+class InterpreterFrame implements DebugFrame {
+    
+    InterpreterFrame(Scriptable scope, InterpreterData data, Scriptable obj) {
+        this.scope = scope;
+        this.data = data;
+        this.lineNumber = -1;
+        this.obj = obj;
+    }
+
+    public Scriptable getVariableObject() {
+        return scope;
+    }
+    
+    public String getSourceName() {
+        return data.itsSourceFile;
+    }
+    
+    public void setLineNumber(int lineNumber) {
+        this.lineNumber = lineNumber;
+    }
+    
+    public int getLineNumber() {
+        return lineNumber;
+    }
+    
+    public DebuggableScript getScript() {
+        if (obj instanceof DebuggableScript)
+            return (DebuggableScript) obj;
+        return null;
+    }
+    
+    private Scriptable scope;
+    private InterpreterData data;
+    private Scriptable obj;
+    private int lineNumber;
+}
diff --git a/src/org/mozilla/javascript/Invoker.java b/src/org/mozilla/javascript/Invoker.java
new file mode 100644
index 0000000..1d877d1
--- /dev/null
+++ b/src/org/mozilla/javascript/Invoker.java
@@ -0,0 +1,54 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * The contents of this file are subject to the Netscape Public
+ * License Version 1.1 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.mozilla.org/NPL/
+ *
+ * Software distributed under the License is distributed on an "AS
+ * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express oqr
+ * implied. See the License for the specific language governing
+ * rights and limitations under the License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is Netscape
+ * Communications Corporation.  Portions created by Netscape are
+ * Copyright (C) 1997-2000 Netscape Communications Corporation. All
+ * Rights Reserved.
+ *
+ * Contributor(s):
+ * Norris Boyd
+ * David C. Navas
+ *
+ * Alternatively, the contents of this file may be used under the
+ * terms of the GNU Public License (the "GPL"), in which case the
+ * provisions of the GPL are applicable instead of those above.
+ * If you wish to allow use of your version of this file only
+ * under the terms of the GPL and not to allow others to use your
+ * version of this file under the NPL, indicate your decision by
+ * deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL.  If you do not delete
+ * the provisions above, a recipient may use your version of this
+ * file under either the NPL or the GPL.
+ */
+
+package org.mozilla.javascript;
+
+import java.lang.reflect.Method;
+
+/**
+ * Avoid cost of java.lang.reflect.Method.invoke() by compiling a class to
+ * perform the method call directly.
+ */
+public abstract class Invoker {
+
+    public abstract Object invoke(Object that, Object [] args);
+
+    /** Factory method to get invoker for given method */
+    public Invoker createInvoker(Method method, Class[] types) {
+        return null;
+    }
+
+}
diff --git a/src/org/mozilla/javascript/JavaMembers.java b/src/org/mozilla/javascript/JavaMembers.java
new file mode 100644
index 0000000..fbe225c
--- /dev/null
+++ b/src/org/mozilla/javascript/JavaMembers.java
@@ -0,0 +1,591 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * The contents of this file are subject to the Netscape Public
+ * License Version 1.1 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.mozilla.org/NPL/
+ *
+ * Software distributed under the License is distributed on an "AS
+ * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express oqr
+ * implied. See the License for the specific language governing
+ * rights and limitations under the License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is Netscape
+ * Communications Corporation.  Portions created by Netscape are
+ * Copyright (C) 1997-2000 Netscape Communications Corporation. All
+ * Rights Reserved.
+ *
+ * Contributor(s): 
+ * Norris Boyd
+ * Frank Mitchell
+ * Mike Shaver
+ * Kurt Westerfeld
+ *
+ * Alternatively, the contents of this file may be used under the
+ * terms of the GNU Public License (the "GPL"), in which case the
+ * provisions of the GPL are applicable instead of those above.
+ * If you wish to allow use of your version of this file only
+ * under the terms of the GPL and not to allow others to use your
+ * version of this file under the NPL, indicate your decision by
+ * deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL.  If you do not delete
+ * the provisions above, a recipient may use your version of this
+ * file under either the NPL or the GPL.
+ */
+
+package org.mozilla.javascript;
+
+import java.lang.reflect.*;
+import java.util.Hashtable;
+import java.util.Enumeration;
+
+/**
+ *
+ * @author Mike Shaver
+ * @author Norris Boyd
+ * @see NativeJavaObject
+ * @see NativeJavaClass
+ */
+class JavaMembers {
+
+    JavaMembers(Scriptable scope, Class cl) {
+        this.members = new Hashtable(23);
+        this.staticMembers = new Hashtable(7);
+        this.cl = cl;
+        reflect(scope, cl);
+    }
+
+    boolean has(String name, boolean isStatic) {
+        Hashtable ht = isStatic ? staticMembers : members;
+        Object obj = ht.get(name);
+        if (obj != null) {
+            return true;
+        } else {
+            Member member = this.findExplicitFunction(name, isStatic);
+            return member != null;
+        }
+    }
+
+    Object get(Scriptable scope, String name, Object javaObject,
+               boolean isStatic)
+    {
+        Hashtable ht = isStatic ? staticMembers : members;
+        Object member = ht.get(name);
+        if (!isStatic && member == null) {
+            // Try to get static member from instance (LC3)
+            member = staticMembers.get(name);
+        }
+        if (member == null) {
+            member = this.getExplicitFunction(scope, name, 
+                                              javaObject, isStatic);
+            if (member == null)
+                return Scriptable.NOT_FOUND;
+        }
+        if (member instanceof Scriptable)
+            return member;      // why is this here?
+        Object rval;
+        Class type;
+        try {
+            if (member instanceof BeanProperty) {
+                BeanProperty bp = (BeanProperty) member;
+                rval = bp.getter.invoke(javaObject, ScriptRuntime.emptyArgs);
+                type = bp.getter.getReturnType();
+            } else {
+                Field field = (Field) member;
+                rval = field.get(isStatic ? null : javaObject);
+                type = field.getType();
+            }
+        } catch (IllegalAccessException accEx) {
+            throw new RuntimeException("unexpected IllegalAccessException "+
+                                       "accessing Java field");
+        } catch (InvocationTargetException e) {
+            throw WrappedException.wrapException(
+                JavaScriptException.wrapException(scope, e));
+        }
+        // Need to wrap the object before we return it.
+        scope = ScriptableObject.getTopLevelScope(scope);
+        return NativeJavaObject.wrap(scope, rval, type);
+    }
+
+    Member findExplicitFunction(String name, boolean isStatic) {
+        Hashtable ht = isStatic ? staticMembers : members;
+        int sigStart = name.indexOf('(');
+        Member[] methodsOrCtors = null;
+        NativeJavaMethod method = null;
+        boolean isCtor = (isStatic && sigStart == 0);
+
+        if (isCtor) {
+            // Explicit request for an overloaded constructor
+            methodsOrCtors = ctors;
+        }
+        else if (sigStart > 0) {
+            // Explicit request for an overloaded method
+            String trueName = name.substring(0,sigStart);
+            Object obj = ht.get(trueName);
+            if (!isStatic && obj == null) {
+                // Try to get static member from instance (LC3)
+                obj = staticMembers.get(trueName);
+            }
+            if (obj != null && obj instanceof NativeJavaMethod) {
+                method = (NativeJavaMethod)obj;
+                methodsOrCtors = method.getMethods();
+            }
+        }
+
+        if (methodsOrCtors != null) {
+            for (int i = 0; i < methodsOrCtors.length; i++) {
+                String nameWithSig = 
+                    NativeJavaMethod.signature(methodsOrCtors[i]);
+                if (name.equals(nameWithSig)) {
+                    return methodsOrCtors[i];
+                }
+            }
+        }
+
+        return null;
+    }
+
+    Object getExplicitFunction(Scriptable scope, String name, 
+                               Object javaObject, boolean isStatic) 
+    {
+        Hashtable ht = isStatic ? staticMembers : members;
+        Object member = null;
+        Member methodOrCtor = this.findExplicitFunction(name, isStatic);
+
+        if (methodOrCtor != null) {
+            Scriptable prototype = 
+                ScriptableObject.getFunctionPrototype(scope);
+
+            if (methodOrCtor instanceof Constructor) {
+                NativeJavaConstructor fun = 
+                    new NativeJavaConstructor((Constructor)methodOrCtor);
+                fun.setPrototype(prototype);
+                member = fun;
+                ht.put(name, fun);
+            } else {
+                String trueName = methodOrCtor.getName();
+                member = ht.get(trueName);
+
+                if (member instanceof NativeJavaMethod &&
+                    ((NativeJavaMethod)member).getMethods().length > 1 ) {
+                    NativeJavaMethod fun = 
+                        new NativeJavaMethod((Method)methodOrCtor, name);
+                    fun.setPrototype(prototype);
+                    ht.put(name, fun);
+                    member = fun;
+                }
+            }
+        }
+
+        return member;
+    }
+
+
+    public void put(Scriptable scope, String name, Object javaObject, 
+                    Object value, boolean isStatic)
+    {
+        Hashtable ht = isStatic ? staticMembers : members;
+        Object member = ht.get(name);
+        if (!isStatic && member == null) {
+            // Try to get static member from instance (LC3)
+            member = staticMembers.get(name);
+        }
+        if (member == null)
+            throw reportMemberNotFound(name);
+        if (member instanceof FieldAndMethods) {
+            FieldAndMethods fam = (FieldAndMethods) ht.get(name);
+            member = fam.getField();
+        }
+        
+        // Is this a bean property "set"?
+        if (member instanceof BeanProperty) { 
+            try {
+                Method method = ((BeanProperty) member).setter;
+                if (method == null)
+                    throw reportMemberNotFound(name);
+                Class[] types = method.getParameterTypes();
+                Object[] params = { NativeJavaObject.coerceType(types[0], value) };
+                method.invoke(javaObject, params);
+            } catch (IllegalAccessException accessEx) {
+                throw new RuntimeException("unexpected IllegalAccessException " +
+                                           "accessing Java field");
+            } catch (InvocationTargetException e) {
+                throw WrappedException.wrapException(
+                    JavaScriptException.wrapException(scope, e));
+            }
+        }
+        else {
+            Field field = null;
+            try {
+                field = (Field) member;
+                if (field == null) {
+                    throw Context.reportRuntimeError1(
+                        "msg.java.internal.private", name);
+                }
+                field.set(javaObject,
+                          NativeJavaObject.coerceType(field.getType(), value));
+            } catch (ClassCastException e) {
+                throw Context.reportRuntimeError1(
+                    "msg.java.method.assign", name);
+            } catch (IllegalAccessException accessEx) {
+                throw new RuntimeException("unexpected IllegalAccessException "+
+                                           "accessing Java field");
+            } catch (IllegalArgumentException argEx) {
+                throw Context.reportRuntimeError3(
+                    "msg.java.internal.field.type", 
+                    value.getClass().getName(), field,
+                    javaObject.getClass().getName());
+            }
+        }
+    }
+
+    Object[] getIds(boolean isStatic) {
+        Hashtable ht = isStatic ? staticMembers : members;
+        int len = ht.size();
+        Object[] result = new Object[len];
+        Enumeration keys = ht.keys();
+        for (int i=0; i < len; i++)
+            result[i] = keys.nextElement();
+        return result;
+    }
+    
+    Class getReflectedClass() {
+        return cl;
+    }
+    
+    void reflectField(Scriptable scope, Field field) {
+        int mods = field.getModifiers();
+        if (!Modifier.isPublic(mods))
+            return;
+        boolean isStatic = Modifier.isStatic(mods);
+        Hashtable ht = isStatic ? staticMembers : members;
+        String name = field.getName();
+        Object member = ht.get(name);
+        if (member != null) {
+            if (member instanceof NativeJavaMethod) {
+                NativeJavaMethod method = (NativeJavaMethod) member;
+                FieldAndMethods fam = new FieldAndMethods(method.getMethods(),
+                                                          field,
+                                                          null);
+                fam.setPrototype(ScriptableObject.getFunctionPrototype(scope));
+                getFieldAndMethodsTable(isStatic).put(name, fam);
+                ht.put(name, fam);
+                return;
+            }
+            if (member instanceof Field) {
+            	Field oldField = (Field) member;
+            	// If this newly reflected field shadows an inherited field, 
+                // then replace it. Otherwise, since access to the field 
+                // would be ambiguous from Java, no field should be reflected.
+            	// For now, the first field found wins, unless another field 
+                // explicitly shadows it.
+            	if (oldField.getDeclaringClass().isAssignableFrom(field.getDeclaringClass()))
+            		ht.put(name, field);
+            	return;
+            }
+            throw new RuntimeException("unknown member type");
+        }
+        ht.put(name, field);
+    }
+
+    void reflectMethod(Scriptable scope, Method method) {
+        int mods = method.getModifiers();
+        if (!Modifier.isPublic(mods))
+            return;
+        boolean isStatic = Modifier.isStatic(mods);
+        Hashtable ht = isStatic ? staticMembers : members;
+        String name = method.getName();
+        NativeJavaMethod fun = (NativeJavaMethod) ht.get(name);
+        if (fun == null) {
+            fun = new NativeJavaMethod();
+            if (scope != null)
+                fun.setPrototype(ScriptableObject.getFunctionPrototype(scope));
+            ht.put(name, fun);
+            fun.add(method);
+        } else {
+            fun.add(method);
+        }
+    }
+
+    void reflect(Scriptable scope, Class cl) {
+        // We reflect methods first, because we want overloaded field/method
+        // names to be allocated to the NativeJavaMethod before the field
+        // gets in the way.
+        Method[] methods = cl.getMethods();
+        for (int i = 0; i < methods.length; i++)
+            reflectMethod(scope, methods[i]);
+        
+        Field[] fields = cl.getFields();
+        for (int i = 0; i < fields.length; i++)
+            reflectField(scope, fields[i]);
+
+        makeBeanProperties(scope, false);
+        makeBeanProperties(scope, true);
+        
+        ctors = cl.getConstructors();
+    }
+    
+    Hashtable getFieldAndMethodsTable(boolean isStatic) {
+        Hashtable fmht = isStatic ? staticFieldAndMethods
+                                  : fieldAndMethods;
+        if (fmht == null) {
+            fmht = new Hashtable(11);
+            if (isStatic)
+                staticFieldAndMethods = fmht;
+            else
+                fieldAndMethods = fmht;
+        }
+        
+        return fmht;
+    }
+
+    void makeBeanProperties(Scriptable scope, boolean isStatic) {
+        Hashtable ht = isStatic ? staticMembers : members;
+        Hashtable toAdd = new Hashtable();
+        
+        // Now, For each member, make "bean" properties.
+        for (Enumeration e = ht.keys(); e.hasMoreElements(); ) {
+            
+            // Is this a getter?
+            String name = (String) e.nextElement();
+            boolean memberIsGetMethod = name.startsWith("get");
+            boolean memberIsIsMethod = name.startsWith("is");
+            if (memberIsGetMethod || memberIsIsMethod) {
+                // Double check name component.
+                String nameComponent = name.substring(memberIsGetMethod ? 3 : 2);
+                if (nameComponent.length() == 0) 
+                    continue;
+                
+                // Make the bean property name.
+                String beanPropertyName = nameComponent;
+                if (Character.isUpperCase(nameComponent.charAt(0))) {
+                    if (nameComponent.length() == 1) {
+                        beanPropertyName = nameComponent.substring(0, 1).toLowerCase();
+                    } else if (!Character.isUpperCase(nameComponent.charAt(1))) {
+                        beanPropertyName = Character.toLowerCase(nameComponent.charAt(0)) + 
+                                           nameComponent.substring(1);
+                    }
+                }
+                
+                // If we already have a member by this name, don't do this
+                // property.
+                if (ht.containsKey(beanPropertyName))
+                    continue;
+                
+                // Get the method by this name.
+                Object method = ht.get(name);
+                if (!(method instanceof NativeJavaMethod))
+                    continue;
+                NativeJavaMethod getJavaMethod = (NativeJavaMethod) method;
+                
+                // Grab and inspect the getter method; does it have an empty parameter list,
+                // with a return value (eg. a getSomething() or isSomething())?
+                Class[] params;
+                Method[] getMethods = getJavaMethod.getMethods();
+                Class type;
+                if (getMethods != null && 
+                    getMethods.length == 1 && 
+                    (type = getMethods[0].getReturnType()) != null &&
+                    (params = getMethods[0].getParameterTypes()) != null && 
+                    params.length == 0) 
+                { 
+                    
+                    // Make sure the method static-ness is preserved for this property.
+                    if (isStatic && !Modifier.isStatic(getMethods[0].getModifiers()))
+                        continue;
+                        
+                    // We have a getter.  Now, do we have a setter?
+                    Method setMethod = null;
+                    String setter = "set" + nameComponent;
+                    if (ht.containsKey(setter)) { 
+
+                        // Is this value a method?
+                        method = ht.get(setter);
+                        if (method instanceof NativeJavaMethod) {
+                        
+                            //
+                            // Note: it may be preferable to allow NativeJavaMethod.findFunction()
+                            //       to find the appropriate setter; unfortunately, it requires an
+                            //       instance of the target arg to determine that.
+                            //
+
+                            // Make two passes: one to find a method with direct type assignment, 
+                            // and one to find a widening conversion.
+                            NativeJavaMethod setJavaMethod = (NativeJavaMethod) method;
+                            Method[] setMethods = setJavaMethod.getMethods();
+                            for (int pass = 1; pass <= 2 && setMethod == null; ++pass) {
+                                for (int i = 0; i < setMethods.length; ++i) {
+                                    if (setMethods[i].getReturnType() == void.class &&
+                                        (!isStatic || Modifier.isStatic(setMethods[i].getModifiers())) &&
+                                        (params = setMethods[i].getParameterTypes()) != null && 
+                                        params.length == 1 ) { 
+                                        
+                                        if ((pass == 1 && params[0] == type) ||
+                                            (pass == 2 && params[0].isAssignableFrom(type))) { 
+                                            setMethod = setMethods[i];
+                                            break;
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                    }
+                            
+                    // Make the property.
+                    BeanProperty bp = new BeanProperty(getMethods[0], setMethod);
+                    toAdd.put(beanPropertyName, bp);
+                }
+            }
+        }           
+        
+        // Add the new bean properties.
+        for (Enumeration e = toAdd.keys(); e.hasMoreElements();) {
+            String key = (String) e.nextElement();
+            Object value = toAdd.get(key);
+            ht.put(key, value);
+        }
+    }
+
+    Hashtable getFieldAndMethodsObjects(Scriptable scope, Object javaObject,
+                                        boolean isStatic) 
+    {
+        Hashtable ht = isStatic ? staticFieldAndMethods : fieldAndMethods;
+        if (ht == null)
+            return null;
+        int len = ht.size();
+        Hashtable result = new Hashtable(Math.max(len,1));
+        Enumeration e = ht.elements();
+        while (len-- > 0) {
+            FieldAndMethods fam = (FieldAndMethods) e.nextElement();
+            fam = (FieldAndMethods) fam.clone();
+            fam.setJavaObject(javaObject);
+            result.put(fam.getName(), fam);
+        }
+        return result;
+    }
+
+    Constructor[] getConstructors() {
+        return ctors;
+    }
+
+    static JavaMembers lookupClass(Scriptable scope, Class dynamicType,
+                                   Class staticType)
+    {
+        Class cl = dynamicType;
+        Hashtable ct = classTable;  // use local reference to avoid synchronize
+        JavaMembers members = (JavaMembers) ct.get(cl);
+        if (members != null)
+            return members;
+        if (staticType != null && staticType != dynamicType &&
+            !Modifier.isPublic(dynamicType.getModifiers()) &&
+            Modifier.isPublic(staticType.getModifiers()))
+        {
+            cl = staticType;
+            
+            // We can use the static type, and that is OK, but we'll trace
+            // back the java class chain here to look for something more suitable.
+            for (Class parentType = dynamicType; 
+                 parentType != null && parentType != ScriptRuntime.ObjectClass;
+                 parentType = parentType.getSuperclass())
+            {
+                if (Modifier.isPublic(parentType.getModifiers())) {
+                   cl = parentType;
+                   break;
+                }
+            }
+        }
+        try {
+            members = new JavaMembers(scope, cl);
+        } catch (SecurityException e) {
+            // Reflection may fail for objects that are in a restricted 
+            // access package (e.g. sun.*).  If we get a security
+            // exception, try again with the static type. Otherwise, 
+            // rethrow the exception.
+            if (cl != staticType)
+                members = new JavaMembers(scope, staticType);
+            else
+                throw e;
+        }
+        if (Context.isCachingEnabled) 
+            ct.put(cl, members);
+        return members;
+    }
+
+    RuntimeException reportMemberNotFound(String memberName) {
+        return Context.reportRuntimeError2(
+            "msg.java.member.not.found", cl.getName(), memberName);
+    }
+
+    static Hashtable classTable = new Hashtable();
+
+    private Class cl;
+    private Hashtable members;
+    private Hashtable fieldAndMethods;
+    private Hashtable staticMembers;
+    private Hashtable staticFieldAndMethods;
+    private Constructor[] ctors;
+}
+
+class BeanProperty {
+    BeanProperty(Method getter, Method setter) {
+        this.getter = getter;
+        this.setter = setter;
+    }
+    Method getter;
+    Method setter;
+}
+
+class FieldAndMethods extends NativeJavaMethod {
+
+    FieldAndMethods(Method[] methods, Field field, String name) {
+        super(methods);
+        this.field = field;
+        this.name = name;
+    }
+
+    void setJavaObject(Object javaObject) {
+        this.javaObject = javaObject;
+    }
+
+    String getName() {
+        if (field == null)
+            return name;
+        return field.getName();
+    }
+
+    Field getField() {
+        return field;
+    }
+    
+    public Object getDefaultValue(Class hint) {
+        if (hint == ScriptRuntime.FunctionClass)
+            return this;
+        Object rval;
+        Class type;
+        try {
+            rval = field.get(javaObject);
+            type = field.getType();
+        } catch (IllegalAccessException accEx) {
+            throw Context.reportRuntimeError1(
+                "msg.java.internal.private", getName());
+        }
+        rval = NativeJavaObject.wrap(this, rval, type);
+        if (rval instanceof Scriptable) {
+            rval = ((Scriptable) rval).getDefaultValue(hint);
+        }
+        return rval;
+    }
+
+    public Object clone() {
+        FieldAndMethods result = new FieldAndMethods(methods, field, name);
+        result.javaObject = javaObject;
+        return result;
+    }
+    
+    private Field field;
+    private Object javaObject;
+    private String name;
+}
diff --git a/src/org/mozilla/javascript/JavaScriptException.java b/src/org/mozilla/javascript/JavaScriptException.java
new file mode 100644
index 0000000..d7ca4f5
--- /dev/null
+++ b/src/org/mozilla/javascript/JavaScriptException.java
@@ -0,0 +1,114 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * The contents of this file are subject to the Netscape Public
+ * License Version 1.1 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.mozilla.org/NPL/
+ *
+ * Software distributed under the License is distributed on an "AS
+ * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express oqr
+ * implied. See the License for the specific language governing
+ * rights and limitations under the License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is Netscape
+ * Communications Corporation.  Portions created by Netscape are
+ * Copyright (C) 1997-1999 Netscape Communications Corporation. All
+ * Rights Reserved.
+ *
+ * Contributor(s): 
+ * Norris Boyd
+ *
+ * Alternatively, the contents of this file may be used under the
+ * terms of the GNU Public License (the "GPL"), in which case the
+ * provisions of the GPL are applicable instead of those above.
+ * If you wish to allow use of your version of this file only
+ * under the terms of the GPL and not to allow others to use your
+ * version of this file under the NPL, indicate your decision by
+ * deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL.  If you do not delete
+ * the provisions above, a recipient may use your version of this
+ * file under either the NPL or the GPL.
+ */
+
+// API class
+
+package org.mozilla.javascript;
+
+import java.lang.reflect.InvocationTargetException;
+
+/**
+ * Java reflection of JavaScript exceptions.  (Possibly wrapping a Java exception.)
+ *
+ * @author Mike McCabe
+ */
+public class JavaScriptException extends Exception {
+
+    public int line;
+    public String sourceFile;
+
+    /**
+     * Create a JavaScript exception wrapping the given JavaScript value.
+     *
+     * Instances of this class are thrown by the JavaScript 'throw' keyword.
+     *
+     * @param value the JavaScript value thrown.
+     */
+    public JavaScriptException(Object value) {
+        super(ScriptRuntime.toString(value));
+        line = Context.enter().interpreterLine;
+        sourceFile = Context.enter().interpreterSourceFile;
+        this.value = value;
+    }
+
+    /**
+     * Get the exception message.
+     *
+     * 

Will just convert the wrapped exception to a string. + */ + public String getMessage() { + return ScriptRuntime.toString(value); + } + + static JavaScriptException wrapException(Scriptable scope, + Throwable exn) + { + if (exn instanceof InvocationTargetException) + exn = ((InvocationTargetException)exn).getTargetException(); + if (exn instanceof JavaScriptException) + return (JavaScriptException)exn; + Object wrapper = NativeJavaObject.wrap(scope, exn, Throwable.class); + return new JavaScriptException(wrapper); + } + + /** + * Get the exception value originally thrown. This may be a + * JavaScript value (null, undefined, Boolean, Number, String, + * Scriptable or Function) or a Java exception value thrown from a + * host object or from Java called through LiveConnect. + * + * @return the value wrapped by this exception + */ + public Object getValue() { + if (value != null && value instanceof Wrapper) + // this will also catch NativeStrings... + return ((Wrapper)value).unwrap(); + else + return value; + } + + /** + * The JavaScript exception value. This value is not + * intended for general use; if the JavaScriptException wraps a + * Java exception, getScriptableValue may return a Scriptable + * wrapping the original Java exception object. + * + * We would prefer to go through a getter to encapsulate the value, + * however that causes the bizarre error "nanosecond timeout value + * out of range" on the MS JVM. + * @serial + */ + Object value; +} diff --git a/src/org/mozilla/javascript/Label.java b/src/org/mozilla/javascript/Label.java new file mode 100644 index 0000000..8e42d46 --- /dev/null +++ b/src/org/mozilla/javascript/Label.java @@ -0,0 +1,106 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * The contents of this file are subject to the Netscape Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express oqr + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is Rhino code, released + * May 6, 1999. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1997-1999 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * Roger Lawrence + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (the "GPL"), in which case the + * provisions of the GPL are applicable instead of those above. + * If you wish to allow use of your version of this file only + * under the terms of the GPL and not to allow others to use your + * version of this file under the NPL, indicate your decision by + * deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete + * the provisions above, a recipient may use your version of this + * file under either the NPL or the GPL. + */ + +package org.mozilla.javascript; + +public class Label { + + private static final int FIXUPTABLE_SIZE = 8; + + private static final boolean DEBUG = true; + + public Label() + { + itsPC = -1; + } + + public short getPC() + { + return itsPC; + } + + public void fixGotos(byte theCodeBuffer[]) + { + if (DEBUG) { + if ((itsPC == -1) && (itsFixupTable != null)) + throw new RuntimeException("Unlocated label"); + } + if (itsFixupTable != null) { + for (int i = 0; i < itsFixupTableTop; i++) { + int fixupSite = itsFixupTable[i]; + // -1 to get delta from instruction start + short offset = (short)(itsPC - (fixupSite - 1)); + theCodeBuffer[fixupSite++] = (byte)(offset >> 8); + theCodeBuffer[fixupSite] = (byte)offset; + } + } + itsFixupTable = null; + } + + public void setPC(short thePC) + { + if (DEBUG) { + if ((itsPC != -1) && (itsPC != thePC)) { + throw new RuntimeException("Duplicate label"); + } + } + + itsPC = thePC; + } + + public void addFixup(int fixupSite) + { + if (itsFixupTable == null) { + itsFixupTableTop = 1; + itsFixupTable = new int[FIXUPTABLE_SIZE]; + itsFixupTable[0] = fixupSite; + } + else { + if (itsFixupTableTop == itsFixupTable.length) { + int oldLength = itsFixupTable.length; + int newTable[] = new int[oldLength + FIXUPTABLE_SIZE]; + System.arraycopy(itsFixupTable, 0, newTable, 0, oldLength); + itsFixupTable = newTable; + } + itsFixupTable[itsFixupTableTop++] = fixupSite; + } + } + + private short itsPC = -1; + private int itsFixupTable[]; + private int itsFixupTableTop; + +} + diff --git a/src/org/mozilla/javascript/LabelTable.java b/src/org/mozilla/javascript/LabelTable.java new file mode 100644 index 0000000..43dd9ae --- /dev/null +++ b/src/org/mozilla/javascript/LabelTable.java @@ -0,0 +1,80 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * The contents of this file are subject to the Netscape Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express oqr + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is Rhino code, released + * May 6, 1999. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1997-1999 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * Roger Lawrence + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (the "GPL"), in which case the + * provisions of the GPL are applicable instead of those above. + * If you wish to allow use of your version of this file only + * under the terms of the GPL and not to allow others to use your + * version of this file under the NPL, indicate your decision by + * deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete + * the provisions above, a recipient may use your version of this + * file under either the NPL or the GPL. + */ + +package org.mozilla.javascript; + +public class LabelTable { + + private static final boolean DEBUGLABELS = false; + + private static final int LabelTableSize = 32; + protected Label itsLabelTable[]; + protected int itsLabelTableTop; + + public int acquireLabel() + { + if (itsLabelTable == null) { + itsLabelTable = new Label[LabelTableSize]; + itsLabelTable[0] = new Label(); + itsLabelTableTop = 1; + return 0x80000000; + } + else { + if (itsLabelTableTop == itsLabelTable.length) { + Label oldTable[] = itsLabelTable; + itsLabelTable = new Label[itsLabelTableTop * 2]; + System.arraycopy(oldTable, 0, itsLabelTable, 0, itsLabelTableTop); + } + itsLabelTable[itsLabelTableTop] = new Label(); + int result = itsLabelTableTop++; + return result | 0x80000000; + } + } + + public int markLabel(int theLabel, int pc) + { + if (DEBUGLABELS) { + if ((theLabel & 0x80000000) != 0x80000000) + throw new RuntimeException("Bad label, no biscuit"); + } + theLabel &= 0x7FFFFFFF; + if (DEBUGLABELS) { + System.out.println("Marking label " + theLabel + " at " + pc); + } + itsLabelTable[theLabel].setPC((short)pc); + return theLabel | 0x80000000; + } + +} diff --git a/src/org/mozilla/javascript/LazilyLoadedCtor.java b/src/org/mozilla/javascript/LazilyLoadedCtor.java new file mode 100644 index 0000000..37fd30c --- /dev/null +++ b/src/org/mozilla/javascript/LazilyLoadedCtor.java @@ -0,0 +1,133 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * The contents of this file are subject to the Netscape Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express oqr + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is Rhino code, released + * May 6, 1999. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1997-2000 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (the "GPL"), in which case the + * provisions of the GPL are applicable instead of those above. + * If you wish to allow use of your version of this file only + * under the terms of the GPL and not to allow others to use your + * version of this file under the NPL, indicate your decision by + * deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete + * the provisions above, a recipient may use your version of this + * file under either the NPL or the GPL. + */ + +package org.mozilla.javascript; + +import java.lang.reflect.*; + +/** + * Avoid loading classes unless they are used. + * + *

This improves startup time and average memory usage. + */ +public final class LazilyLoadedCtor { + + public LazilyLoadedCtor(ScriptableObject scope, + String ctorName, String className, boolean sealed) + { + + this.className = className; + this.ctorName = ctorName; + this.sealed = sealed; + + if (getter == null) { + Method[] all = FunctionObject.getMethodList(getClass()); + getter = FunctionObject.findMethods(all, "getProperty")[0]; + setter = FunctionObject.findMethods(all, "setProperty")[0]; + } + + try { + scope.defineProperty(ctorName, this, getter, setter, + ScriptableObject.DONTENUM); + } + catch (PropertyException e) { + throw WrappedException.wrapException(e); + } + } + + public Object getProperty(ScriptableObject obj) { + synchronized (obj) { + if (!isReplaced) { + boolean removeOnError = false; + + // Treat security exceptions as absence of object. + // They can be due to the following reasons: + // java.lang.RuntimePermission createClassLoader + // java.util.PropertyPermission + // org.mozilla.javascript.JavaAdapter read + + Class cl = null; + try { cl = Class.forName(className); } + catch (ClassNotFoundException ex) { removeOnError = true; } + catch (SecurityException ex) { removeOnError = true; } + + if (cl != null) { + try { + ScriptableObject.defineClass(obj, cl, sealed); + isReplaced = true; + } + catch (InstantiationException e) { + throw WrappedException.wrapException(e); + } + catch (IllegalAccessException e) { + throw WrappedException.wrapException(e); + } + catch (InvocationTargetException e) { + throw WrappedException.wrapException(e); + } + catch (ClassDefinitionException e) { + throw WrappedException.wrapException(e); + } + catch (PropertyException e) { + throw WrappedException.wrapException(e); + } + catch (SecurityException ex) { + removeOnError = true; + } + } + if (removeOnError) { + obj.delete(ctorName); + return Scriptable.NOT_FOUND; + } + } + } + // Get just added object + return obj.get(ctorName, obj); + } + + public Object setProperty(ScriptableObject obj, Object val) { + synchronized (obj) { + isReplaced = true; + return val; + } + } + + private static Method getter, setter; + + private String ctorName; + private String className; + private boolean sealed; + private boolean isReplaced; +} diff --git a/src/org/mozilla/javascript/LineBuffer.java b/src/org/mozilla/javascript/LineBuffer.java new file mode 100644 index 0000000..d3f0ec4 --- /dev/null +++ b/src/org/mozilla/javascript/LineBuffer.java @@ -0,0 +1,412 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * The contents of this file are subject to the Netscape Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express oqr + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is Rhino code, released + * May 6, 1999. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1997-1999 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * Mike McCabe + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (the "GPL"), in which case the + * provisions of the GPL are applicable instead of those above. + * If you wish to allow use of your version of this file only + * under the terms of the GPL and not to allow others to use your + * version of this file under the NPL, indicate your decision by + * deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete + * the provisions above, a recipient may use your version of this + * file under either the NPL or the GPL. + */ + +package org.mozilla.javascript; + +import java.io.Reader; +import java.io.IOException; + +/** + * An input buffer that combines fast character-based access with + * (slower) support for retrieving the text of the current line. It + * also supports building strings directly out of the internal buffer + * to support fast scanning with minimal object creation. + * + * Note that it is customized in several ways to support the + * TokenStream class, and should not be considered general. + * + * Credits to Kipp Hickman and John Bandhauer. + * + * @author Mike McCabe + */ +final class LineBuffer { + /* + * for smooth operation of getLine(), this should be greater than + * the length of any expected line. Currently, 256 is 3% slower + * than 4096 for large compiles, but seems safer given evaluateString. + * Strings for the scanner are are built with StringBuffers + * instead of directly out of the buffer whenever a string crosses + * a buffer boundary, so small buffer sizes will mean that more + * objects are created. + */ + static final int BUFLEN = 256; + + LineBuffer(Reader in, int lineno) { + this.in = in; + this.lineno = lineno; + } + + int read() throws IOException { + for(;;) { + if (end == offset && !fill()) + return -1; + + // Do only a bitmask + branch per character, at the cost of + // three branches per low-bits-only (or 2028/9) character. + if ((buffer[offset] & '\udfd0') == 0) { + if (buffer[offset] == '\r') { + // if the next character is a newline, skip past it. + if ((offset + 1) < end) { + if (buffer[offset + 1] == '\n') + offset++; + } else { + // set a flag for fill(), in case the first char of the + // next fill is a newline. + lastWasCR = true; + } + } + else + if ((buffer[offset] != '\n') + && (buffer[offset] != '\u2028') + && (buffer[offset] != '\u2029')) + { + if (Character.getType(buffer[offset]) + == Character.FORMAT) { + hadCFSinceStringStart = true; + offset++; + continue; + } + return (int) buffer[offset++]; + } + offset++; + prevStart = lineStart; + lineStart = offset; + lineno++; + return '\n'; + } + if ((buffer[offset] >= 128) + && (Character.getType(buffer[offset]) == Character.FORMAT)) { + hadCFSinceStringStart = true; + offset++; + } + else + break; + } + + return (int) buffer[offset++]; + } + + void unread() { + if (offset == 0) + // We can get here when we're asked to unread() an + // implicit EOF_CHAR. + + // This would also be wrong behavior in the general case, + // because a peek() could map a buffer.length offset to 0 + // in the process of a fill(), and leave it there. But + // the scanner never calls peek() or a failed match() + // followed by unread()... this would violate 1-character + // lookahead. So we're OK. + return; + offset--; + if ((buffer[offset] & '\ufff0') == 0 + && (buffer[offset] == '\r' || buffer[offset] == '\n')) { + // back off from the line start we presumably just registered... + lineStart = prevStart; + lineno--; + } + } + + int peek() throws IOException { + if (end == offset && !fill()) + return -1; + + if (buffer[offset] == '\r') + return '\n'; + + return buffer[offset]; + } + + boolean match(char c) throws IOException { + if (end == offset && !fill()) + return false; + + // This'd be a place where we'd need to map '\r' to '\n' and + // do other updates, but TokenStream never looks ahead for + // '\n', so we don't bother. + if (buffer[offset] == c) { + offset++; + return true; + } + return false; + } + + // Reconstruct a source line from the buffers. This can be slow... + String getLine() { + StringBuffer result = new StringBuffer(); + + int start = lineStart; + if (start >= offset) { + // the line begins somewhere in the other buffer; get that first. + if (otherStart < otherEnd) + // if a line ending was seen in the other buffer... otherwise + // just ignore this strange case. + result.append(otherBuffer, otherStart, + otherEnd - otherStart); + start = 0; + } + + // get the part of the line in the current buffer. + result.append(buffer, start, offset - start); + + // Get the remainder of the line. + int i = offset; + while(true) { + if (i == buffer.length) { + // we're out of buffer, let's just expand it. We do + // this instead of reading into a StringBuffer to + // preserve the stream for later reads. + char[] newBuffer = new char[buffer.length * 2]; + System.arraycopy(buffer, 0, newBuffer, 0, buffer.length); + buffer = newBuffer; + int charsRead = 0; + try { + charsRead = in.read(buffer, end, buffer.length - end); + } catch (IOException ioe) { + // ignore it, we're already displaying an error... + } + if (charsRead < 0) + break; + end += charsRead; + } + if (buffer[i] == '\r' || buffer[i] == '\n') + break; + i++; + } + + result.append(buffer, offset, i - offset); + return result.toString(); + } + + // Get the offset of the current character, relative to + // the line that getLine() returns. + int getOffset() { + if (lineStart >= offset) + // The line begins somewhere in the other buffer. + return offset + (otherEnd - otherStart); + else + return offset - lineStart; + } + + // Set a mark to indicate that the reader should begin + // accumulating characters for getString(). The string begins + // with the last character read. + void startString() { + if (offset == 0) { + // We can get here if startString is called after a peek() + // or failed match() with offset past the end of the + // buffer. + + // We're at the beginning of the buffer, and the previous character + // (which we want to include) is at the end of the last one, so + // we just go to StringBuffer mode. + stringSoFar = new StringBuffer(); + + stringSoFar.append(otherBuffer, otherEnd - 1, 1); + + stringStart = -1; // Set sentinel value. + hadCFSinceStringStart = ((otherBuffer[otherEnd - 1] >= 128) + && Character.getType(otherBuffer[otherEnd - 1]) + == Character.FORMAT); + } else { + // Support restarting strings + stringSoFar = null; + stringStart = offset - 1; + hadCFSinceStringStart = ((buffer[stringStart] >= 128) + && Character.getType(buffer[stringStart]) == Character.FORMAT); + } + + } + + // Get a string consisting of the characters seen since the last + // startString. + String getString() { + String result; + + /* + * There's one strange case here: If the character offset currently + * points to (which we never want to include in the string) is + * a newline, then if the previous character is a carriage return, + * we probably want to exclude that as well. If the offset is 0, + * then we hope that fill() handled excluding it from stringSoFar. + */ + int loseCR = (offset > 0 && + buffer[offset] == '\n' && buffer[offset - 1] == '\r') ? + 1 : 0; + + if (stringStart != -1) { + // String mark is valid, and in this buffer. + + result = new String(buffer, stringStart, + offset - stringStart - loseCR); + } else { + if (stringSoFar == null) + stringSoFar = new StringBuffer(); + // Exclude cr as well as nl of newline. If offset is 0, then + // hopefully fill() did the right thing. + result = (stringSoFar.append(buffer, 0, offset - loseCR)).toString(); + } + + stringStart = -1; + stringSoFar = null; + + if (hadCFSinceStringStart) { + char c[] = result.toCharArray(); + StringBuffer x = null; + for (int i = 0; i < c.length; i++) { + if (Character.getType(c[i]) == Character.FORMAT) { + if (x == null) { + x = new StringBuffer(); + x.append(c, 0, i); + } + } + else + if (x != null) x.append(c[i]); + } + if (x != null) result = x.toString(); + } + + return result; + } + + boolean fill() throws IOException { + // not sure I care... + if (end - offset != 0) + throw new IOException("fill of non-empty buffer"); + + // If there's a string currently being accumulated, save + // off the progress. + + /* + * Exclude an end-of-buffer carriage return. NOTE this is not + * fully correct in the general case, because we really only + * want to exclude the carriage return if it's followed by a + * linefeed at the beginning of the next buffer. But we fudge + * because the scanner doesn't do this. + */ + int loseCR = (offset > 0 && lastWasCR) ? 1 : 0; + + if (stringStart != -1) { + // The mark is in the current buffer, save off from the mark to the + // end. + stringSoFar = new StringBuffer(); + + stringSoFar.append(buffer, stringStart, end - stringStart - loseCR); + stringStart = -1; + } else if (stringSoFar != null) { + // the string began prior to the current buffer, so save the + // whole current buffer. + stringSoFar.append(buffer, 0, end - loseCR); + } + + // swap buffers + char[] tempBuffer = buffer; + buffer = otherBuffer; + otherBuffer = tempBuffer; + + // allocate the buffers lazily, in case we're handed a short string. + if (buffer == null) { + buffer = new char[BUFLEN]; + } + + // buffers have switched, so move the newline marker. + otherStart = lineStart; + otherEnd = end; + + // set lineStart to a sentinel value, unless this is the first + // time around. + prevStart = lineStart = (otherBuffer == null) ? 0 : buffer.length + 1; + + offset = 0; + end = in.read(buffer, 0, buffer.length); + if (end < 0) { + end = 0; + + // can't null buffers here, because a string might be retrieved + // out of the other buffer, and a 0-length string might be + // retrieved out of this one. + + hitEOF = true; + return false; + } + + // If the last character of the previous fill was a carriage return, + // then ignore a newline. + + // There's another bizzare special case here. If lastWasCR is + // true, and we see a newline, and the buffer length is + // 1... then we probably just read the last character of the + // file, and returning after advancing offset is not the right + // thing to do. Instead, we try to ignore the newline (and + // likely get to EOF for real) by doing yet another fill(). + if (lastWasCR) { + if (buffer[0] == '\n') { + offset++; + if (end == 1) + return fill(); + } + lineStart = offset; + lastWasCR = false; + } + return true; + } + + int getLineno() { return lineno; } + boolean eof() { return hitEOF; } + + private Reader in; + private char[] otherBuffer = null; + private char[] buffer = null; + + // Yes, there are too too many of these. + private int offset = 0; + private int end = 0; + private int otherEnd; + private int lineno; + + private int lineStart = 0; + private int otherStart = 0; + private int prevStart = 0; + + private boolean lastWasCR = false; + private boolean hitEOF = false; + + private int stringStart = -1; + private StringBuffer stringSoFar = null; + private boolean hadCFSinceStringStart = false; + +} + + diff --git a/src/org/mozilla/javascript/ListenerArray.java b/src/org/mozilla/javascript/ListenerArray.java new file mode 100644 index 0000000..3687b10 --- /dev/null +++ b/src/org/mozilla/javascript/ListenerArray.java @@ -0,0 +1,131 @@ +/* -*- Mode: java; tab-width: 4; indent-tabs-mode: 1; c-basic-offset: 4 -*- + * + * The contents of this file are subject to the Netscape Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express oqr + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is Rhino code, released + * May 6, 1999. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1997-1999 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * Igor Bukanov + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (the "GPL"), in which case the + * provisions of the GPL are applicable instead of those above. + * If you wish to allow use of your version of this file only + * under the terms of the GPL and not to allow others to use your + * version of this file under the NPL, indicate your decision by + * deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete + * the provisions above, a recipient may use your version of this + * file under either the NPL or the GPL. + */ + +/* Helper class to add/remove listeners from Object array */ + +package org.mozilla.javascript; + +/** + * Utility class to manage listeners array. + * A possible usage would be: +

+    private Object[] listeners;
+    ...
+    void addListener(ListenerType listener) {
+        synchronized (this) {
+            listeners = ListenerArray.add(listeners, listener);
+        }
+    }
+
+    void removeListener(ListenerType listener) {
+        synchronized (this) {
+            listeners = ListenerArray.remove(listeners, listener);
+        }
+    }
+
+ * Here is a thread safe while synchronization free example of event firing +
+    void fireEvent(EventType event) {
+        Object[] array = listeners;
+        if (array != null) {
+            for (int i = array.length; i-- != 0;) {
+                ((ListenerType)array[i]).onEvent(event);
+            }
+        }
+
+    }
+
+ + * or if listeners of different types can present in listeners array: +
+    void fireEvent(EventType event) {
+        Object[] array = listeners;
+        if (array != null) {
+            for (int i = array.length; i-- != 0;) {
+                Object obj = array[i];
+                if (obj instanceof ListenerType) {
+                    ((ListenerType)obj).onEvent(event);
+                }
+            }
+        }
+
+    }
+
+ */ +public class ListenerArray { + + /** Return newly allocated array that contains listener and all elements + ** from data array. + ** Note: listener is added to resulting array even if it is already + ** present in data */ + public static Object[] add(Object[] data, Object listener) { + if (data == null) { + data = new Object[1]; + } + else { + int N = data.length; + Object[] tmp = new Object[N + 1]; + System.arraycopy(data, 0, tmp, 1, N); + data = tmp; + } + data[0] = listener; + return data; + } + + /** Return a copy of data array with the first occurrence of listener + ** removed. + ** If listener is not present in data, simply return data. + ** Note: return null if listener is the single element + ** of data. */ + public static Object[] remove(Object[] data, Object listener) { + if (data != null) { + int N = data.length; + for (int i = 0; i != N; ++i) { + if (data[i] == listener) { + if (N == 1) { data = null; } + else { + Object[] tmp = new Object[N - 1]; + System.arraycopy(data, 0, tmp, 0, i); + System.arraycopy(data, i + 1, tmp, i, N - 1 - i); + data = tmp; + } + break; + } + } + } + return data; + } + +} diff --git a/src/org/mozilla/javascript/LocalVariable.java b/src/org/mozilla/javascript/LocalVariable.java new file mode 100644 index 0000000..bb673c1 --- /dev/null +++ b/src/org/mozilla/javascript/LocalVariable.java @@ -0,0 +1,79 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * The contents of this file are subject to the Netscape Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express oqr + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is Rhino code, released + * May 6, 1999. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1997-1999 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * Roger Lawrence + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (the "GPL"), in which case the + * provisions of the GPL are applicable instead of those above. + * If you wish to allow use of your version of this file only + * under the terms of the GPL and not to allow others to use your + * version of this file under the NPL, indicate your decision by + * deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete + * the provisions above, a recipient may use your version of this + * file under either the NPL or the GPL. + */ + +package org.mozilla.javascript; + +public class LocalVariable { + + public LocalVariable(String name, boolean isParameter) { + itsName = name; + itsIsParameter = isParameter; + } + + public void setIndex(int index){ itsIndex = index; } + public int getIndex() { return itsIndex; } + + public void setIsParameter() { itsIsParameter = true; } + public boolean isParameter() { return itsIsParameter; } + + public String getName() { return itsName; } + + /** + * Return the starting PC where this variable is live, or -1 + * if it is not a Java register. + */ + public int getStartPC() { + return -1; + } + + /** + * Return the Java register number or -1 if it is not a Java register. + */ + public short getJRegister() { + return -1; + } + + /** + * Return true if the local variable is a Java register with double type. + */ + public boolean isNumber() { + return false; + } + + private String itsName; + private int itsIndex = -1; + + private boolean itsIsParameter; +} diff --git a/src/org/mozilla/javascript/NativeArray.java b/src/org/mozilla/javascript/NativeArray.java new file mode 100644 index 0000000..37d81f8 --- /dev/null +++ b/src/org/mozilla/javascript/NativeArray.java @@ -0,0 +1,1147 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * The contents of this file are subject to the Netscape Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express oqr + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is Rhino code, released + * May 6, 1999. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1997-1999 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * Mike McCabe + * Igor Bukanov + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (the "GPL"), in which case the + * provisions of the GPL are applicable instead of those above. + * If you wish to allow use of your version of this file only + * under the terms of the GPL and not to allow others to use your + * version of this file under the NPL, indicate your decision by + * deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete + * the provisions above, a recipient may use your version of this + * file under either the NPL or the GPL. + */ + +package org.mozilla.javascript; +import java.util.Hashtable; + +/** + * This class implements the Array native object. + * @author Norris Boyd + * @author Mike McCabe + */ +public class NativeArray extends IdScriptable { + + /* + * Optimization possibilities and open issues: + * - Long vs. double schizophrenia. I suspect it might be better + * to use double throughout. + + * - Most array operations go through getElem or setElem (defined + * in this file) to handle the full 2^32 range; it might be faster + * to have versions of most of the loops in this file for the + * (infinitely more common) case of indices < 2^31. + + * - Functions that need a new Array call "new Array" in the + * current scope rather than using a hardwired constructor; + * "Array" could be redefined. It turns out that js calls the + * equivalent of "new Array" in the current scope, except that it + * always gets at least an object back, even when Array == null. + */ + + public static void init(Context cx, Scriptable scope, boolean sealed) { + NativeArray obj = new NativeArray(); + obj.prototypeFlag = true; + obj.addAsPrototype(MAX_PROTOTYPE_ID, cx, scope, sealed); + } + + /** + * Zero-parameter constructor: just used to create Array.prototype + */ + public NativeArray() { + dense = null; + this.length = 0; + } + + public NativeArray(long length) { + int intLength = (int) length; + if (intLength == length && intLength > 0) { + if (intLength > maximumDenseLength) + intLength = maximumDenseLength; + dense = new Object[intLength]; + for (int i=0; i < intLength; i++) + dense[i] = NOT_FOUND; + } + this.length = length; + } + + public NativeArray(Object[] array) { + dense = array; + this.length = array.length; + } + + public String getClassName() { + return "Array"; + } + + protected int getIdDefaultAttributes(int id) { + if (id == Id_length) { + return DONTENUM | PERMANENT; + } + return super.getIdDefaultAttributes(id); + } + + protected Object getIdValue(int id) { + if (id == Id_length) { + return wrap_double(length); + } + return super.getIdValue(id); + } + + protected void setIdValue(int id, Object value) { + if (id == Id_length) { + jsSet_length(value); return; + } + super.setIdValue(id, value); + } + + public int methodArity(int methodId) { + if (prototypeFlag) { + switch (methodId) { + case Id_constructor: return 1; + case Id_toString: return 0; + case Id_toLocaleString: return 1; + case Id_join: return 1; + case Id_reverse: return 0; + case Id_sort: return 1; + case Id_push: return 1; + case Id_pop: return 1; + case Id_shift: return 1; + case Id_unshift: return 1; + case Id_splice: return 1; + case Id_concat: return 1; + case Id_slice: return 1; + } + } + return super.methodArity(methodId); + } + + public Object execMethod + (int methodId, IdFunction f, + Context cx, Scriptable scope, Scriptable thisObj, Object[] args) + throws JavaScriptException + { + if (prototypeFlag) { + switch (methodId) { + case Id_constructor: + return jsConstructor(cx, scope, args, f, thisObj == null); + + case Id_toString: + return jsFunction_toString(cx, thisObj, args); + + case Id_toLocaleString: + return jsFunction_toLocaleString(cx, thisObj, args); + + case Id_join: + return jsFunction_join(cx, thisObj, args); + + case Id_reverse: + return jsFunction_reverse(cx, thisObj, args); + + case Id_sort: + return jsFunction_sort(cx, scope, thisObj, args); + + case Id_push: + return jsFunction_push(cx, thisObj, args); + + case Id_pop: + return jsFunction_pop(cx, thisObj, args); + + case Id_shift: + return jsFunction_shift(cx, thisObj, args); + + case Id_unshift: + return jsFunction_unshift(cx, thisObj, args); + + case Id_splice: + return jsFunction_splice(cx, scope, thisObj, args); + + case Id_concat: + return jsFunction_concat(cx, scope, thisObj, args); + + case Id_slice: + return jsFunction_slice(cx, thisObj, args); + } + } + return super.execMethod(methodId, f, cx, scope, thisObj, args); + } + + public Object get(int index, Scriptable start) { + if (dense != null && 0 <= index && index < dense.length) + return dense[index]; + return super.get(index, start); + } + + public boolean has(int index, Scriptable start) { + if (dense != null && 0 <= index && index < dense.length) + return dense[index] != NOT_FOUND; + return super.has(index, start); + } + + public void put(String id, Scriptable start, Object value) { + if (start == this) { + // only set the array length if given an array index (ECMA 15.4.0) + + // try to get an array index from id + double d = ScriptRuntime.toNumber(id); + + if (ScriptRuntime.toUint32(d) == d && + ScriptRuntime.numberToString(d, 10).equals(id) && + this.length <= d && d != 4294967295.0) + { + this.length = (long)d + 1; + } + } + super.put(id, start, value); + } + + public void put(int index, Scriptable start, Object value) { + if (start == this) { + // only set the array length if given an array index (ECMA 15.4.0) + if (this.length <= index) { + // avoid overflowing index! + this.length = (long)index + 1; + } + + if (dense != null && 0 <= index && index < dense.length) { + dense[index] = value; + return; + } + } + super.put(index, start, value); + } + + public void delete(int index) { + if (!isSealed()) { + if (dense != null && 0 <= index && index < dense.length) { + dense[index] = NOT_FOUND; + return; + } + } + super.delete(index); + } + + public Object[] getIds() { + Object[] superIds = super.getIds(); + if (dense == null) + return superIds; + int count = 0; + int last = dense.length; + if (last > length) + last = (int) length; + for (int i=last-1; i >= 0; i--) { + if (dense[i] != NOT_FOUND) + count++; + } + count += superIds.length; + Object[] result = new Object[count]; + System.arraycopy(superIds, 0, result, 0, superIds.length); + for (int i=last-1; i >= 0; i--) { + if (dense[i] != NOT_FOUND) + result[--count] = new Integer(i); + } + return result; + } + + public Object getDefaultValue(Class hint) { + if (hint == ScriptRuntime.NumberClass) { + Context cx = Context.getContext(); + if (cx.getLanguageVersion() == Context.VERSION_1_2) + return new Long(length); + } + return super.getDefaultValue(hint); + } + + /** + * See ECMA 15.4.1,2 + */ + private static Object jsConstructor(Context cx, Scriptable scope, + Object[] args, IdFunction ctorObj, + boolean inNewExpr) + throws JavaScriptException + { + if (!inNewExpr) { + // FunctionObject.construct will set up parent, proto + return ctorObj.construct(cx, scope, args); + } + if (args.length == 0) + return new NativeArray(); + + // Only use 1 arg as first element for version 1.2; for + // any other version (including 1.3) follow ECMA and use it as + // a length. + if (cx.getLanguageVersion() == cx.VERSION_1_2) { + return new NativeArray(args); + } + else { + if ((args.length > 1) || (!(args[0] instanceof Number))) + return new NativeArray(args); + else { + long len = ScriptRuntime.toUint32(args[0]); + if (len != (((Number)(args[0])).doubleValue())) + throw Context.reportRuntimeError0("msg.arraylength.bad"); + return new NativeArray(len); + } + } + } + + public long jsGet_length() { + return length; + } + + private void jsSet_length(Object val) { + /* XXX do we satisfy this? + * 15.4.5.1 [[Put]](P, V): + * 1. Call the [[CanPut]] method of A with name P. + * 2. If Result(1) is false, return. + * ? + */ + + if (!(val instanceof Number)) + throw Context.reportRuntimeError0("msg.arraylength.bad"); + + long longVal = ScriptRuntime.toUint32(val); + if (longVal != (((Number)val).doubleValue())) + throw Context.reportRuntimeError0("msg.arraylength.bad"); + + if (longVal < length) { + // remove all properties between longVal and length + if (length - longVal > 0x1000) { + // assume that the representation is sparse + Object[] e = getIds(); // will only find in object itself + for (int i=0; i < e.length; i++) { + if (e[i] instanceof String) { + // > MAXINT will appear as string + String id = (String) e[i]; + double d = ScriptRuntime.toNumber(id); + if (d == d && d < length) + delete(id); + continue; + } + int index = ((Number) e[i]).intValue(); + if (index >= longVal) + delete(index); + } + } else { + // assume a dense representation + for (long i=longVal; i < length; i++) { + // only delete if defined in the object itself + if (hasElem(this, i)) + ScriptRuntime.delete(this, new Long(i)); + } + } + } + length = longVal; + } + + /* Support for generic Array-ish objects. Most of the Array + * functions try to be generic; anything that has a length + * property is assumed to be an array. hasLengthProperty is + * needed in addition to getLengthProperty, because + * getLengthProperty always succeeds - tries to convert strings, etc. + */ + static double getLengthProperty(Scriptable obj) { + // These will both give numeric lengths within Uint32 range. + if (obj instanceof NativeString) + return (double)((NativeString)obj).jsGet_length(); + if (obj instanceof NativeArray) + return (double)((NativeArray)obj).jsGet_length(); + return ScriptRuntime.toUint32(ScriptRuntime + .getProp(obj, "length", obj)); + } + + static boolean hasLengthProperty(Object obj) { + if (!(obj instanceof Scriptable) || obj == Context.getUndefinedValue()) + return false; + if (obj instanceof NativeString || obj instanceof NativeArray) + return true; + Scriptable sobj = (Scriptable)obj; + + // XXX some confusion as to whether or not to walk to get the length + // property. Pending review of js/[new ecma submission] treatment + // of 'arrayness'. + + Object property = ScriptRuntime.getProp(sobj, "length", sobj); + return property instanceof Number; + } + + /* Utility functions to encapsulate index > Integer.MAX_VALUE + * handling. Also avoids unnecessary object creation that would + * be necessary to use the general ScriptRuntime.get/setElem + * functions... though this is probably premature optimization. + */ + private static boolean hasElem(Scriptable target, long index) { + return index > Integer.MAX_VALUE + ? target.has(Long.toString(index), target) + : target.has((int)index, target); + } + + private static Object getElem(Scriptable target, long index) { + if (index > Integer.MAX_VALUE) { + String id = Long.toString(index); + return ScriptRuntime.getElem(target, id, target); + } else { + return ScriptRuntime.getElem(target, (int)index); + } + } + + private static void setElem(Scriptable target, long index, Object value) { + if (index > Integer.MAX_VALUE) { + String id = Long.toString(index); + ScriptRuntime.setElem(target, id, value, target); + } else { + ScriptRuntime.setElem(target, (int)index, value); + } + } + + private static String jsFunction_toString(Context cx, Scriptable thisObj, + Object[] args) + throws JavaScriptException + { + return toStringHelper(cx, thisObj, + cx.getLanguageVersion() == cx.VERSION_1_2, + false); + } + + private static String jsFunction_toLocaleString(Context cx, + Scriptable thisObj, + Object[] args) + throws JavaScriptException + { + return toStringHelper(cx, thisObj, false, true); + } + + private static String toStringHelper(Context cx, Scriptable thisObj, + boolean toSource, boolean toLocale) + throws JavaScriptException + { + /* It's probably redundant to handle long lengths in this + * function; StringBuffers are limited to 2^31 in java. + */ + + long length = (long)getLengthProperty(thisObj); + + StringBuffer result = new StringBuffer(); + + if (cx.iterating == null) + cx.iterating = new Hashtable(31); + boolean iterating = cx.iterating.get(thisObj) == Boolean.TRUE; + + // whether to return '4,unquoted,5' or '[4, "quoted", 5]' + String separator; + + if (toSource) { + result.append('['); + separator = ", "; + } else { + separator = ","; + } + + boolean haslast = false; + long i = 0; + + if (!iterating) { + for (i = 0; i < length; i++) { + if (i > 0) + result.append(separator); + Object elem = getElem(thisObj, i); + if (elem == null || elem == Undefined.instance) { + haslast = false; + continue; + } + haslast = true; + + if (elem instanceof String) { + if (toSource) { + result.append('\"'); + result.append(ScriptRuntime.escapeString + (ScriptRuntime.toString(elem))); + result.append('\"'); + } else { + result.append(ScriptRuntime.toString(elem)); + } + } else { + /* wrap changes to cx.iterating in a try/finally + * so that the reference always gets removed, and + * we don't leak memory. Good place for weak + * references, if we had them. */ + try { + // stop recursion. + cx.iterating.put(thisObj, Boolean.TRUE); + if (toLocale && elem != Undefined.instance && + elem != null) + { + Scriptable obj = cx.toObject(elem, thisObj); + Object tls = ScriptRuntime.getProp(obj, + "toLocaleString", thisObj); + elem = ScriptRuntime.call(cx, tls, elem, + ScriptRuntime.emptyArgs); + } + result.append(ScriptRuntime.toString(elem)); + } finally { + cx.iterating.remove(thisObj); + } + } + } + } + + if (toSource) { + //for [,,].length behavior; we want toString to be symmetric. + if (!haslast && i > 0) + result.append(", ]"); + else + result.append(']'); + } + return result.toString(); + } + + /** + * See ECMA 15.4.4.3 + */ + private static String jsFunction_join(Context cx, Scriptable thisObj, + Object[] args) + { + StringBuffer result = new StringBuffer(); + String separator; + + double length = getLengthProperty(thisObj); + + // if no args, use "," as separator + if (args.length < 1) { + separator = ","; + } else { + separator = ScriptRuntime.toString(args[0]); + } + for (long i=0; i < length; i++) { + if (i > 0) + result.append(separator); + Object temp = getElem(thisObj, i); + if (temp == null || temp == Undefined.instance) + continue; + result.append(ScriptRuntime.toString(temp)); + } + return result.toString(); + } + + /** + * See ECMA 15.4.4.4 + */ + private static Scriptable jsFunction_reverse(Context cx, + Scriptable thisObj, + Object[] args) + { + long len = (long)getLengthProperty(thisObj); + + long half = len / 2; + for(long i=0; i < half; i++) { + long j = len - i - 1; + Object temp1 = getElem(thisObj, i); + Object temp2 = getElem(thisObj, j); + setElem(thisObj, i, temp2); + setElem(thisObj, j, temp1); + } + return thisObj; + } + + /** + * See ECMA 15.4.4.5 + */ + private static Scriptable jsFunction_sort(Context cx, Scriptable scope, + Scriptable thisObj, + Object[] args) + throws JavaScriptException + { + long length = (long)getLengthProperty(thisObj); + + Object compare; + if (args.length > 0 && Undefined.instance != args[0]) + // sort with given compare function + compare = args[0]; + else + // sort with default compare + compare = null; + + + // OPT: Would it make sense to use the extended sort for very small + // arrays? + + // Should we use the extended sort function, or the faster one? + if (length >= Integer.MAX_VALUE) { + qsort_extended(cx, compare, thisObj, 0, length - 1); + } else { + // copy the JS array into a working array, so it can be + // sorted cheaply. + Object[] working = new Object[(int)length]; + for (int i=0; i 0) { + length--; + + // Get the to-be-deleted property's value. + result = getElem(thisObj, (long)length); + + // We don't need to delete the last property, because + // setLength does that for us. + } else { + result = Context.getUndefinedValue(); + } + // necessary to match js even when length < 0; js pop will give a + // length property to any target it is called on. + ScriptRuntime.setProp(thisObj, "length", new Double(length), thisObj); + + return result; + } + + private static Object jsFunction_shift(Context cx, Scriptable thisObj, + Object[] args) + { + Object result; + double length = getLengthProperty(thisObj); + if (length > 0) { + long i = 0; + length--; + + // Get the to-be-deleted property's value. + result = getElem(thisObj, i); + + /* + * Slide down the array above the first element. Leave i + * set to point to the last element. + */ + if (length > 0) { + for (i = 1; i <= length; i++) { + Object temp = getElem(thisObj, i); + setElem(thisObj, i - 1, temp); + } + } + // We don't need to delete the last property, because + // setLength does that for us. + } else { + result = Context.getUndefinedValue(); + } + ScriptRuntime.setProp(thisObj, "length", new Double(length), thisObj); + return result; + } + + private static Object jsFunction_unshift(Context cx, Scriptable thisObj, + Object[] args) + { + Object result; + double length = (double)getLengthProperty(thisObj); + int argc = args.length; + + if (args.length > 0) { + /* Slide up the array to make room for args at the bottom */ + if (length > 0) { + for (long last = (long)length - 1; last >= 0; last--) { + Object temp = getElem(thisObj, last); + setElem(thisObj, last + argc, temp); + } + } + + /* Copy from argv to the bottom of the array. */ + for (int i = 0; i < args.length; i++) { + setElem(thisObj, i, args[i]); + } + + /* Follow Perl by returning the new array length. */ + length += args.length; + ScriptRuntime.setProp(thisObj, "length", + new Double(length), thisObj); + } + return new Long((long)length); + } + + private static Object jsFunction_splice(Context cx, Scriptable scope, + Scriptable thisObj, Object[] args) + { + /* create an empty Array to return. */ + scope = getTopLevelScope(scope); + Object result = ScriptRuntime.newObject(cx, scope, "Array", null); + int argc = args.length; + if (argc == 0) + return result; + double length = getLengthProperty(thisObj); + + /* Convert the first argument into a starting index. */ + double begin = ScriptRuntime.toInteger(args[0]); + double end; + double delta; + double count; + + if (begin < 0) { + begin += length; + if (begin < 0) + begin = 0; + } else if (begin > length) { + begin = length; + } + argc--; + + /* Convert the second argument from a count into a fencepost index. */ + delta = length - begin; + + if (args.length == 1) { + count = delta; + end = length; + } else { + count = ScriptRuntime.toInteger(args[1]); + if (count < 0) + count = 0; + else if (count > delta) + count = delta; + end = begin + count; + + argc--; + } + + long lbegin = (long)begin; + long lend = (long)end; + + /* If there are elements to remove, put them into the return value. */ + if (count > 0) { + if (count == 1 + && (cx.getLanguageVersion() == Context.VERSION_1_2)) + { + /* + * JS lacks "list context", whereby in Perl one turns the + * single scalar that's spliced out into an array just by + * assigning it to @single instead of $single, or by using it + * as Perl push's first argument, for instance. + * + * JS1.2 emulated Perl too closely and returned a non-Array for + * the single-splice-out case, requiring callers to test and + * wrap in [] if necessary. So JS1.3, default, and other + * versions all return an array of length 1 for uniformity. + */ + result = getElem(thisObj, lbegin); + } else { + for (long last = lbegin; last < lend; last++) { + Scriptable resultArray = (Scriptable)result; + Object temp = getElem(thisObj, last); + setElem(resultArray, last - lbegin, temp); + } + } + } else if (count == 0 + && cx.getLanguageVersion() == Context.VERSION_1_2) + { + /* Emulate C JS1.2; if no elements are removed, return undefined. */ + result = Context.getUndefinedValue(); + } + + /* Find the direction (up or down) to copy and make way for argv. */ + delta = argc - count; + + if (delta > 0) { + for (long last = (long)length - 1; last >= lend; last--) { + Object temp = getElem(thisObj, last); + setElem(thisObj, last + (long)delta, temp); + } + } else if (delta < 0) { + for (long last = lend; last < length; last++) { + Object temp = getElem(thisObj, last); + setElem(thisObj, last + (long)delta, temp); + } + } + + /* Copy from argv into the hole to complete the splice. */ + int argoffset = args.length - argc; + for (int i = 0; i < argc; i++) { + setElem(thisObj, lbegin + i, args[i + argoffset]); + } + + /* Update length in case we deleted elements from the end. */ + ScriptRuntime.setProp(thisObj, "length", + new Double(length + delta), thisObj); + return result; + } + + /* + * Python-esque sequence operations. + */ + private static Scriptable jsFunction_concat(Context cx, Scriptable scope, + Scriptable thisObj, + Object[] args) + { + /* Concat tries to keep the definition of an array as general + * as possible; if it finds that an object has a numeric + * 'length' property, then it treats that object as an array. + * This treats string atoms and string objects differently; as + * string objects have a length property and are accessible by + * index, they get exploded into arrays when added, while + * atomic strings are just added as strings. + */ + + // create an empty Array to return. + scope = getTopLevelScope(scope); + Scriptable result = ScriptRuntime.newObject(cx, scope, "Array", null); + double length; + long slot = 0; + + /* Put the target in the result array; only add it as an array + * if it looks like one. + */ + if (hasLengthProperty(thisObj)) { + length = getLengthProperty(thisObj); + + // Copy from the target object into the result + for (slot = 0; slot < length; slot++) { + Object temp = getElem(thisObj, slot); + setElem(result, slot, temp); + } + } else { + setElem(result, slot++, thisObj); + } + + /* Copy from the arguments into the result. If any argument + * has a numeric length property, treat it as an array and add + * elements separately; otherwise, just copy the argument. + */ + for (int i = 0; i < args.length; i++) { + if (hasLengthProperty(args[i])) { + // hasLengthProperty => instanceOf Scriptable. + Scriptable arg = (Scriptable)args[i]; + length = getLengthProperty(arg); + for (long j = 0; j < length; j++, slot++) { + Object temp = getElem(arg, j); + setElem(result, slot, temp); + } + } else { + setElem(result, slot++, args[i]); + } + } + return result; + } + + private Scriptable jsFunction_slice(Context cx, Scriptable thisObj, + Object[] args) + { + Scriptable scope = getTopLevelScope(this); + Scriptable result = ScriptRuntime.newObject(cx, scope, "Array", null); + double length = getLengthProperty(thisObj); + + double begin = 0; + double end = length; + + if (args.length > 0) { + begin = ScriptRuntime.toInteger(args[0]); + if (begin < 0) { + begin += length; + if (begin < 0) + begin = 0; + } else if (begin > length) { + begin = length; + } + + if (args.length > 1) { + end = ScriptRuntime.toInteger(args[1]); + if (end < 0) { + end += length; + if (end < 0) + end = 0; + } else if (end > length) { + end = length; + } + } + } + + long lbegin = (long)begin; + long lend = (long)end; + for (long slot = lbegin; slot < lend; slot++) { + Object temp = getElem(thisObj, slot); + setElem(result, slot - lbegin, temp); + } + + return result; + } + + protected int maxInstanceId() { return MAX_INSTANCE_ID; } + + protected String getIdName(int id) { + if (id == Id_length) { return "length"; } + + if (prototypeFlag) { + switch (id) { + case Id_constructor: return "constructor"; + case Id_toString: return "toString"; + case Id_toLocaleString: return "toLocaleString"; + case Id_join: return "join"; + case Id_reverse: return "reverse"; + case Id_sort: return "sort"; + case Id_push: return "push"; + case Id_pop: return "pop"; + case Id_shift: return "shift"; + case Id_unshift: return "unshift"; + case Id_splice: return "splice"; + case Id_concat: return "concat"; + case Id_slice: return "slice"; + } + } + return null; + } + + private static final int + Id_length = 1, + MAX_INSTANCE_ID = 1; + + protected int mapNameToId(String s) { + if (s.equals("length")) { return Id_length; } + else if (prototypeFlag) { + return toPrototypeId(s); + } + return 0; + } + +// #string_id_map# + + private static int toPrototypeId(String s) { + int id; +// #generated# Last update: 2001-04-23 11:46:01 GMT+02:00 + L0: { id = 0; String X = null; int c; + L: switch (s.length()) { + case 3: X="pop";id=Id_pop; break L; + case 4: c=s.charAt(0); + if (c=='j') { X="join";id=Id_join; } + else if (c=='p') { X="push";id=Id_push; } + else if (c=='s') { X="sort";id=Id_sort; } + break L; + case 5: c=s.charAt(1); + if (c=='h') { X="shift";id=Id_shift; } + else if (c=='l') { X="slice";id=Id_slice; } + break L; + case 6: c=s.charAt(0); + if (c=='c') { X="concat";id=Id_concat; } + else if (c=='l') { X="length";id=Id_length; } + else if (c=='s') { X="splice";id=Id_splice; } + break L; + case 7: c=s.charAt(0); + if (c=='r') { X="reverse";id=Id_reverse; } + else if (c=='u') { X="unshift";id=Id_unshift; } + break L; + case 8: X="toString";id=Id_toString; break L; + case 11: X="constructor";id=Id_constructor; break L; + case 14: X="toLocaleString";id=Id_toLocaleString; break L; + } + if (X!=null && X!=s && !X.equals(s)) id = 0; + } +// #/generated# + return id; + } + + private static final int + Id_constructor = MAX_INSTANCE_ID + 1, + Id_toString = MAX_INSTANCE_ID + 2, + Id_toLocaleString = MAX_INSTANCE_ID + 3, + Id_join = MAX_INSTANCE_ID + 4, + Id_reverse = MAX_INSTANCE_ID + 5, + Id_sort = MAX_INSTANCE_ID + 6, + Id_push = MAX_INSTANCE_ID + 7, + Id_pop = MAX_INSTANCE_ID + 8, + Id_shift = MAX_INSTANCE_ID + 9, + Id_unshift = MAX_INSTANCE_ID + 10, + Id_splice = MAX_INSTANCE_ID + 11, + Id_concat = MAX_INSTANCE_ID + 12, + Id_slice = MAX_INSTANCE_ID + 13, + + MAX_PROTOTYPE_ID = MAX_INSTANCE_ID + 13; + +// #/string_id_map# + + private long length; + private Object[] dense; + private static final int maximumDenseLength = 10000; + + private boolean prototypeFlag; +} diff --git a/src/org/mozilla/javascript/NativeBoolean.java b/src/org/mozilla/javascript/NativeBoolean.java new file mode 100644 index 0000000..9841b5f --- /dev/null +++ b/src/org/mozilla/javascript/NativeBoolean.java @@ -0,0 +1,168 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * The contents of this file are subject to the Netscape Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express oqr + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is Rhino code, released + * May 6, 1999. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1997-1999 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * Igor Bukanov + * Mike McCabe + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (the "GPL"), in which case the + * provisions of the GPL are applicable instead of those above. + * If you wish to allow use of your version of this file only + * under the terms of the GPL and not to allow others to use your + * version of this file under the NPL, indicate your decision by + * deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete + * the provisions above, a recipient may use your version of this + * file under either the NPL or the GPL. + */ + +package org.mozilla.javascript; + +/** + * This class implements the Boolean native object. + * See ECMA 15.6. + * @author Norris Boyd + */ +public class NativeBoolean extends IdScriptable { + + public static void init(Context cx, Scriptable scope, boolean sealed) { + NativeBoolean obj = new NativeBoolean(); + obj.prototypeFlag = true; + obj.addAsPrototype(MAX_PROTOTYPE_ID, cx, scope, sealed); + } + + /** + * Zero-parameter constructor: just used to create Boolean.prototype + */ + public NativeBoolean() { + } + + public NativeBoolean(boolean b) { + booleanValue = b; + } + + public String getClassName() { + return "Boolean"; + } + + public Object getDefaultValue(Class typeHint) { + // This is actually non-ECMA, but will be proposed + // as a change in round 2. + if (typeHint == ScriptRuntime.BooleanClass) + return wrap_boolean(booleanValue); + return super.getDefaultValue(typeHint); + } + + public int methodArity(int methodId) { + if (prototypeFlag) { + if (methodId == Id_constructor) return 1; + if (methodId == Id_toString) return 0; + if (methodId == Id_valueOf) return 0; + } + return super.methodArity(methodId); + } + + public Object execMethod + (int methodId, IdFunction f, + Context cx, Scriptable scope, Scriptable thisObj, Object[] args) + throws JavaScriptException + { + if (prototypeFlag) { + if (methodId == Id_constructor) { + return jsConstructor(args, thisObj == null); + } + else if (methodId == Id_toString) { + return realThis(thisObj, f).jsFunction_toString(); + } + else if (methodId == Id_valueOf) { + return wrap_boolean(realThis(thisObj, f).jsFunction_valueOf()); + } + } + + return super.execMethod(methodId, f, cx, scope, thisObj, args); + } + + private NativeBoolean realThis(Scriptable thisObj, IdFunction f) { + while (!(thisObj instanceof NativeBoolean)) { + thisObj = nextInstanceCheck(thisObj, f, true); + } + return (NativeBoolean)thisObj; + } + + + private Object jsConstructor(Object[] args, boolean inNewExpr) { + boolean b = ScriptRuntime.toBoolean(args, 0); + if (inNewExpr) { + // new Boolean(val) creates a new boolean object. + return new NativeBoolean(b); + } + + // Boolean(val) converts val to a boolean. + return wrap_boolean(b); + } + + private String jsFunction_toString() { + return booleanValue ? "true" : "false"; + } + + private boolean jsFunction_valueOf() { + return booleanValue; + } + + protected String getIdName(int id) { + if (prototypeFlag) { + if (id == Id_constructor) return "constructor"; + if (id == Id_toString) return "toString"; + if (id == Id_valueOf) return "valueOf"; + } + return null; + } + +// #string_id_map# + + protected int mapNameToId(String s) { + if (!prototypeFlag) { return 0; } + int id; +// #generated# Last update: 2001-04-23 10:38:18 CEST + L0: { id = 0; String X = null; + int s_length = s.length(); + if (s_length==7) { X="valueOf";id=Id_valueOf; } + else if (s_length==8) { X="toString";id=Id_toString; } + else if (s_length==11) { X="constructor";id=Id_constructor; } + if (X!=null && X!=s && !X.equals(s)) id = 0; + } +// #/generated# + return id; + } + + private static final int + Id_constructor = 1, + Id_toString = 2, + Id_valueOf = 3, + MAX_PROTOTYPE_ID = 3; + +// #/string_id_map# + + private boolean booleanValue; + + private boolean prototypeFlag; +} diff --git a/src/org/mozilla/javascript/NativeCall.java b/src/org/mozilla/javascript/NativeCall.java new file mode 100644 index 0000000..fdab7b6 --- /dev/null +++ b/src/org/mozilla/javascript/NativeCall.java @@ -0,0 +1,171 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * The contents of this file are subject to the Netscape Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express oqr + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is Rhino code, released + * May 6, 1999. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1997-1999 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (the "GPL"), in which case the + * provisions of the GPL are applicable instead of those above. + * If you wish to allow use of your version of this file only + * under the terms of the GPL and not to allow others to use your + * version of this file under the NPL, indicate your decision by + * deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete + * the provisions above, a recipient may use your version of this + * file under either the NPL or the GPL. + */ + +package org.mozilla.javascript; + +/** + * This class implements the activation object. + * + * See ECMA 10.1.6 + * + * @see org.mozilla.javascript.Arguments + * @author Norris Boyd + */ +public final class NativeCall extends IdScriptable { + + static void init(Context cx, Scriptable scope, boolean sealed) { + NativeCall obj = new NativeCall(); + obj.prototypeFlag = true; + obj.addAsPrototype(MAX_PROTOTYPE_ID, cx, scope, sealed); + } + + NativeCall(Context cx, Scriptable scope, NativeFunction funObj, + Scriptable thisObj, Object[] args) + { + this.funObj = funObj; + this.thisObj = thisObj; + + setParentScope(scope); + // leave prototype null + + // save current activation + this.caller = cx.currentActivation; + cx.currentActivation = this; + + this.originalArgs = (args == null) ? ScriptRuntime.emptyArgs : args; + + // initialize values of arguments + String[] argNames = funObj.argNames; + if (argNames != null) { + for (int i=0; i < funObj.argCount; i++) { + Object val = i < args.length ? args[i] + : Undefined.instance; + super.put(argNames[i], this, val); + } + } + + // initialize "arguments" property + super.put("arguments", this, new Arguments(this)); + } + + private NativeCall() { + } + + public String getClassName() { + return "Call"; + } + + private static Object jsConstructor(Context cx, Object[] args, + Function ctorObj, boolean inNewExpr) + { + if (!inNewExpr) { + throw Context.reportRuntimeError1("msg.only.from.new", "Call"); + } + ScriptRuntime.checkDeprecated(cx, "Call"); + NativeCall result = new NativeCall(); + result.setPrototype(getObjectPrototype(ctorObj)); + return result; + } + + NativeCall getActivation(Function f) { + NativeCall x = this; + do { + if (x.funObj == f) + return x; + x = x.caller; + } while (x != null); + return null; + } + + public Function getFunctionObject() { + return funObj; + } + + public Object[] getOriginalArguments() { + return originalArgs; + } + + public NativeCall getCaller() { + return caller; + } + + public Scriptable getThisObj() { + return thisObj; + } + + public int methodArity(int methodId) { + if (prototypeFlag) { + if (methodId == Id_constructor) return 1; + } + return super.methodArity(methodId); + } + + public Object execMethod + (int methodId, IdFunction f, + Context cx, Scriptable scope, Scriptable thisObj, Object[] args) + throws JavaScriptException + { + if (prototypeFlag) { + if (methodId == Id_constructor) { + return jsConstructor(cx, args, f, thisObj == null); + } + } + return super.execMethod(methodId, f, cx, scope, thisObj, args); + } + + protected String getIdName(int id) { + if (prototypeFlag) { + if (id == Id_constructor) return "constructor"; + } + return null; + } + + protected int mapNameToId(String s) { + if (!prototypeFlag) { return 0; } + return s.equals("constructor") ? Id_constructor : 0; + } + + private static final int + Id_constructor = 1, + MAX_PROTOTYPE_ID = 1; + + NativeCall caller; + NativeFunction funObj; + Scriptable thisObj; + Object[] originalArgs; + public int debugPC; + + private boolean prototypeFlag; +} diff --git a/src/org/mozilla/javascript/NativeDate.java b/src/org/mozilla/javascript/NativeDate.java new file mode 100644 index 0000000..77615ea --- /dev/null +++ b/src/org/mozilla/javascript/NativeDate.java @@ -0,0 +1,1707 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * The contents of this file are subject to the Netscape Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express oqr + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is Rhino code, released + * May 6, 1999. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1997-1999 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * Mike McCabe + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (the "GPL"), in which case the + * provisions of the GPL are applicable instead of those above. + * If you wish to allow use of your version of this file only + * under the terms of the GPL and not to allow others to use your + * version of this file under the NPL, indicate your decision by + * deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete + * the provisions above, a recipient may use your version of this + * file under either the NPL or the GPL. + */ + +package org.mozilla.javascript; + +import java.util.Date; +import java.util.TimeZone; +import java.util.Locale; +import java.text.NumberFormat; +import java.text.DateFormat; +import java.text.SimpleDateFormat; + +/** + * This class implements the Date native object. + * See ECMA 15.9. + * @author Mike McCabe + */ +public class NativeDate extends IdScriptable { + + public static void init(Context cx, Scriptable scope, boolean sealed) { + NativeDate obj = new NativeDate(); + obj.prototypeFlag = true; + + // Set the value of the prototype Date to NaN ('invalid date'); + obj.date = ScriptRuntime.NaN; + + obj.addAsPrototype(MAX_PROTOTYPE_ID, cx, scope, sealed); + } + + public NativeDate() { + if (thisTimeZone == null) { + // j.u.TimeZone is synchronized, so setting class statics from it + // should be OK. + thisTimeZone = java.util.TimeZone.getDefault(); + LocalTZA = thisTimeZone.getRawOffset(); + } + } + + public String getClassName() { + return "Date"; + } + + public Object getDefaultValue(Class typeHint) { + if (typeHint == null) + typeHint = ScriptRuntime.StringClass; + return super.getDefaultValue(typeHint); + } + + protected void fillConstructorProperties + (Context cx, IdFunction ctor, boolean sealed) + { + addIdFunctionProperty(ctor, ConstructorId_UTC, sealed); + addIdFunctionProperty(ctor, ConstructorId_parse, sealed); + super.fillConstructorProperties(cx, ctor, sealed); + } + + public int methodArity(int methodId) { + if (prototypeFlag) { + switch (methodId) { + case ConstructorId_UTC: return 1; + case ConstructorId_parse: return 1; + case Id_constructor: return 1; + case Id_toString: return 0; + case Id_toTimeString: return 0; + case Id_toDateString: return 0; + case Id_toLocaleString: return 0; + case Id_toLocaleTimeString: return 0; + case Id_toLocaleDateString: return 0; + case Id_toUTCString: return 0; + case Id_valueOf: return 0; + case Id_getTime: return 0; + case Id_getYear: return 0; + case Id_getFullYear: return 0; + case Id_getUTCFullYear: return 0; + case Id_getMonth: return 0; + case Id_getUTCMonth: return 0; + case Id_getDate: return 0; + case Id_getUTCDate: return 0; + case Id_getDay: return 0; + case Id_getUTCDay: return 0; + case Id_getHours: return 0; + case Id_getUTCHours: return 0; + case Id_getMinutes: return 0; + case Id_getUTCMinutes: return 0; + case Id_getSeconds: return 0; + case Id_getUTCSeconds: return 0; + case Id_getMilliseconds: return 0; + case Id_getUTCMilliseconds: return 0; + case Id_getTimezoneOffset: return 0; + case Id_setTime: return 1; + case Id_setMilliseconds: return 1; + case Id_setUTCMilliseconds: return 1; + case Id_setSeconds: return 2; + case Id_setUTCSeconds: return 2; + case Id_setMinutes: return 3; + case Id_setUTCMinutes: return 3; + case Id_setHours: return 4; + case Id_setUTCHours: return 4; + case Id_setDate: return 1; + case Id_setUTCDate: return 1; + case Id_setMonth: return 2; + case Id_setUTCMonth: return 2; + case Id_setFullYear: return 3; + case Id_setUTCFullYear: return 3; + case Id_setYear: return 1; + } + } + return super.methodArity(methodId); + } + + public Object execMethod + (int methodId, IdFunction f, + Context cx, Scriptable scope, Scriptable thisObj, Object[] args) + throws JavaScriptException + { + if (prototypeFlag) { + switch (methodId) { + case ConstructorId_UTC: + return wrap_double(jsStaticFunction_UTC(args)); + + case ConstructorId_parse: + return wrap_double(jsStaticFunction_parse + (ScriptRuntime.toString(args, 0))); + + case Id_constructor: + return jsConstructor(args, thisObj == null); + + case Id_toString: { + double t = realThis(thisObj, f, true).date; + return date_format(t, FORMATSPEC_FULL); + } + + case Id_toTimeString: { + double t = realThis(thisObj, f, true).date; + return date_format(t, FORMATSPEC_TIME); + } + + case Id_toDateString: { + double t = realThis(thisObj, f, true).date; + return date_format(t, FORMATSPEC_DATE); + } + + case Id_toLocaleString: { + double t = realThis(thisObj, f, true).date; + return jsFunction_toLocaleString(t); + } + + case Id_toLocaleTimeString: { + double t = realThis(thisObj, f, true).date; + return jsFunction_toLocaleTimeString(t); + } + + case Id_toLocaleDateString: { + double t = realThis(thisObj, f, true).date; + return jsFunction_toLocaleDateString(t); + } + + case Id_toUTCString: { + double t = realThis(thisObj, f, true).date; + if (t == t) { return jsFunction_toUTCString(t); } + return jsFunction_NaN_date_str; + } + + case Id_valueOf: + return wrap_double(realThis(thisObj, f, true).date); + + case Id_getTime: + return wrap_double(realThis(thisObj, f, true).date); + + case Id_getYear: { + double t = realThis(thisObj, f, true).date; + if (t == t) { t = jsFunction_getYear(cx, t); } + return wrap_double(t); + } + + case Id_getFullYear: { + double t = realThis(thisObj, f, true).date; + if (t == t) { t = YearFromTime(LocalTime(t)); } + return wrap_double(t); + } + + case Id_getUTCFullYear: { + double t = realThis(thisObj, f, true).date; + if (t == t) { t = YearFromTime(t); } + return wrap_double(t); + } + + case Id_getMonth: { + double t = realThis(thisObj, f, true).date; + if (t == t) { t = MonthFromTime(LocalTime(t)); } + return wrap_double(t); + } + + case Id_getUTCMonth: { + double t = realThis(thisObj, f, true).date; + if (t == t) { t = MonthFromTime(t); } + return wrap_double(t); + } + + case Id_getDate: { + double t = realThis(thisObj, f, true).date; + if (t == t) { t = DateFromTime(LocalTime(t)); } + return wrap_double(t); + } + + case Id_getUTCDate: { + double t = realThis(thisObj, f, true).date; + if (t == t) { t = DateFromTime(t); } + return wrap_double(t); + } + + case Id_getDay: { + double t = realThis(thisObj, f, true).date; + if (t == t) { t = WeekDay(LocalTime(t)); } + return wrap_double(t); + } + + case Id_getUTCDay: { + double t = realThis(thisObj, f, true).date; + if (t == t) { t = WeekDay(t); } + return wrap_double(t); + } + + case Id_getHours: { + double t = realThis(thisObj, f, true).date; + if (t == t) { t = HourFromTime(LocalTime(t)); } + return wrap_double(t); + } + + case Id_getUTCHours: { + double t = realThis(thisObj, f, true).date; + if (t == t) { t = HourFromTime(t); } + return wrap_double(t); + } + + case Id_getMinutes: { + double t = realThis(thisObj, f, true).date; + if (t == t) { t = MinFromTime(LocalTime(t)); } + return wrap_double(t); + } + + case Id_getUTCMinutes: { + double t = realThis(thisObj, f, true).date; + if (t == t) { t = MinFromTime(t); } + return wrap_double(t); + } + + case Id_getSeconds: { + double t = realThis(thisObj, f, true).date; + if (t == t) { t = SecFromTime(LocalTime(t)); } + return wrap_double(t); + } + + case Id_getUTCSeconds: { + double t = realThis(thisObj, f, true).date; + if (t == t) { t = SecFromTime(t); } + return wrap_double(t); + } + + case Id_getMilliseconds: { + double t = realThis(thisObj, f, true).date; + if (t == t) { t = msFromTime(LocalTime(t)); } + return wrap_double(t); + } + + case Id_getUTCMilliseconds: { + double t = realThis(thisObj, f, true).date; + if (t == t) { t = msFromTime(t); } + return wrap_double(t); + } + + case Id_getTimezoneOffset: { + double t = realThis(thisObj, f, true).date; + if (t == t) { t = jsFunction_getTimezoneOffset(t); } + return wrap_double(t); + } + + case Id_setTime: + return wrap_double(realThis(thisObj, f, true). + jsFunction_setTime(ScriptRuntime.toNumber(args, 0))); + + case Id_setMilliseconds: + return wrap_double(realThis(thisObj, f, false). + makeTime(args, 1, true)); + + case Id_setUTCMilliseconds: + return wrap_double(realThis(thisObj, f, false). + makeTime(args, 1, false)); + + case Id_setSeconds: + return wrap_double(realThis(thisObj, f, false). + makeTime(args, 2, true)); + + case Id_setUTCSeconds: + return wrap_double(realThis(thisObj, f, false). + makeTime(args, 2, false)); + + case Id_setMinutes: + return wrap_double(realThis(thisObj, f, false). + makeTime(args, 3, true)); + + case Id_setUTCMinutes: + return wrap_double(realThis(thisObj, f, false). + makeTime(args, 3, false)); + + case Id_setHours: + return wrap_double(realThis(thisObj, f, false). + makeTime(args, 4, true)); + + case Id_setUTCHours: + return wrap_double(realThis(thisObj, f, false). + makeTime(args, 4, false)); + + case Id_setDate: + return wrap_double(realThis(thisObj, f, false). + makeDate(args, 1, true)); + + case Id_setUTCDate: + return wrap_double(realThis(thisObj, f, false). + makeDate(args, 1, false)); + + case Id_setMonth: + return wrap_double(realThis(thisObj, f, false). + makeDate(args, 2, true)); + + case Id_setUTCMonth: + return wrap_double(realThis(thisObj, f, false). + makeDate(args, 2, false)); + + case Id_setFullYear: + return wrap_double(realThis(thisObj, f, false). + makeDate(args, 3, true)); + + case Id_setUTCFullYear: + return wrap_double(realThis(thisObj, f, false). + makeDate(args, 3, false)); + + case Id_setYear: + return wrap_double(realThis(thisObj, f, false). + jsFunction_setYear(ScriptRuntime.toNumber(args, 0))); + } + } + + return super.execMethod(methodId, f, cx, scope, thisObj, args); + } + + private NativeDate realThis(Scriptable thisObj, IdFunction f, + boolean readOnly) + { + while (!(thisObj instanceof NativeDate)) { + thisObj = nextInstanceCheck(thisObj, f, readOnly); + } + return (NativeDate)thisObj; + } + + /* ECMA helper functions */ + + private static final double HalfTimeDomain = 8.64e15; + private static final double HoursPerDay = 24.0; + private static final double MinutesPerHour = 60.0; + private static final double SecondsPerMinute = 60.0; + private static final double msPerSecond = 1000.0; + private static final double MinutesPerDay = (HoursPerDay * MinutesPerHour); + private static final double SecondsPerDay = (MinutesPerDay * SecondsPerMinute); + private static final double SecondsPerHour = (MinutesPerHour * SecondsPerMinute); + private static final double msPerDay = (SecondsPerDay * msPerSecond); + private static final double msPerHour = (SecondsPerHour * msPerSecond); + private static final double msPerMinute = (SecondsPerMinute * msPerSecond); + + private static double Day(double t) { + return Math.floor(t / msPerDay); + } + + private static double TimeWithinDay(double t) { + double result; + result = t % msPerDay; + if (result < 0) + result += msPerDay; + return result; + } + + private static int DaysInYear(int y) { + if (y % 4 == 0 && (y % 100 != 0 || y % 400 == 0)) + return 366; + else + return 365; + } + + + /* math here has to be f.p, because we need + * floor((1968 - 1969) / 4) == -1 + */ + private static double DayFromYear(double y) { + return ((365 * ((y)-1970) + Math.floor(((y)-1969)/4.0) + - Math.floor(((y)-1901)/100.0) + Math.floor(((y)-1601)/400.0))); + } + + private static double TimeFromYear(double y) { + return DayFromYear(y) * msPerDay; + } + + private static int YearFromTime(double t) { + int lo = (int) Math.floor((t / msPerDay) / 366) + 1970; + int hi = (int) Math.floor((t / msPerDay) / 365) + 1970; + int mid; + + /* above doesn't work for negative dates... */ + if (hi < lo) { + int temp = lo; + lo = hi; + hi = temp; + } + + /* Use a simple binary search algorithm to find the right + year. This seems like brute force... but the computation + of hi and lo years above lands within one year of the + correct answer for years within a thousand years of + 1970; the loop below only requires six iterations + for year 270000. */ + while (hi > lo) { + mid = (hi + lo) / 2; + if (TimeFromYear(mid) > t) { + hi = mid - 1; + } else { + if (TimeFromYear(mid) <= t) { + int temp = mid + 1; + if (TimeFromYear(temp) > t) { + return mid; + } + lo = mid + 1; + } + } + } + return lo; + } + + private static boolean InLeapYear(double t) { + return DaysInYear(YearFromTime(t)) == 366; + } + + private static int DayWithinYear(double t) { + int year = YearFromTime(t); + return (int) (Day(t) - DayFromYear(year)); + } + /* + * The following array contains the day of year for the first day of + * each month, where index 0 is January, and day 0 is January 1. + */ + + private static double DayFromMonth(int m, boolean leap) { + int day = m * 30; + + if (m >= 7) { day += m / 2 - 1; } + else if (m >= 2) { day += (m - 1) / 2 - 1; } + else { day += m; } + + if (leap && m >= 2) { ++day; } + + return day; + } + + private static int MonthFromTime(double t) { + int d, step; + + d = DayWithinYear(t); + + if (d < (step = 31)) + return 0; + + // Originally coded as step += (InLeapYear(t) ? 29 : 28); + // but some jits always returned 28! + if (InLeapYear(t)) + step += 29; + else + step += 28; + + if (d < step) + return 1; + if (d < (step += 31)) + return 2; + if (d < (step += 30)) + return 3; + if (d < (step += 31)) + return 4; + if (d < (step += 30)) + return 5; + if (d < (step += 31)) + return 6; + if (d < (step += 31)) + return 7; + if (d < (step += 30)) + return 8; + if (d < (step += 31)) + return 9; + if (d < (step += 30)) + return 10; + return 11; + } + + private static int DateFromTime(double t) { + int d, step, next; + + d = DayWithinYear(t); + if (d <= (next = 30)) + return d + 1; + step = next; + + // Originally coded as next += (InLeapYear(t) ? 29 : 28); + // but some jits always returned 28! + if (InLeapYear(t)) + next += 29; + else + next += 28; + + if (d <= next) + return d - step; + step = next; + if (d <= (next += 31)) + return d - step; + step = next; + if (d <= (next += 30)) + return d - step; + step = next; + if (d <= (next += 31)) + return d - step; + step = next; + if (d <= (next += 30)) + return d - step; + step = next; + if (d <= (next += 31)) + return d - step; + step = next; + if (d <= (next += 31)) + return d - step; + step = next; + if (d <= (next += 30)) + return d - step; + step = next; + if (d <= (next += 31)) + return d - step; + step = next; + if (d <= (next += 30)) + return d - step; + step = next; + + return d - step; + } + + private static int WeekDay(double t) { + double result; + result = Day(t) + 4; + result = result % 7; + if (result < 0) + result += 7; + return (int) result; + } + + private static double Now() { + return (double) System.currentTimeMillis(); + } + + /* Should be possible to determine the need for this dynamically + * if we go with the workaround... I'm not using it now, because I + * can't think of any clean way to make toLocaleString() and the + * time zone (comment) in toString match the generated string + * values. Currently it's wrong-but-consistent in all but the + * most recent betas of the JRE - seems to work in 1.1.7. + */ + private final static boolean TZO_WORKAROUND = false; + private static double DaylightSavingTA(double t) { + if (!TZO_WORKAROUND) { + Date date = new Date((long) t); + if (thisTimeZone.inDaylightTime(date)) + return msPerHour; + else + return 0; + } else { + /* Use getOffset if inDaylightTime() is broken, because it + * seems to work acceptably. We don't switch over to it + * entirely, because it requires (expensive) exploded date arguments, + * and the api makes it impossible to handle dst + * changeovers cleanly. + */ + + // Hardcode the assumption that the changeover always + // happens at 2:00 AM: + t += LocalTZA + (HourFromTime(t) <= 2 ? msPerHour : 0); + + int year = YearFromTime(t); + double offset = thisTimeZone.getOffset(year > 0 ? 1 : 0, + year, + MonthFromTime(t), + DateFromTime(t), + WeekDay(t), + (int)TimeWithinDay(t)); + + if ((offset - LocalTZA) != 0) + return msPerHour; + else + return 0; + // return offset - LocalTZA; + } + } + + private static double LocalTime(double t) { + return t + LocalTZA + DaylightSavingTA(t); + } + + public static double internalUTC(double t) { + return t - LocalTZA - DaylightSavingTA(t - LocalTZA); + } + + private static int HourFromTime(double t) { + double result; + result = Math.floor(t / msPerHour) % HoursPerDay; + if (result < 0) + result += HoursPerDay; + return (int) result; + } + + private static int MinFromTime(double t) { + double result; + result = Math.floor(t / msPerMinute) % MinutesPerHour; + if (result < 0) + result += MinutesPerHour; + return (int) result; + } + + private static int SecFromTime(double t) { + double result; + result = Math.floor(t / msPerSecond) % SecondsPerMinute; + if (result < 0) + result += SecondsPerMinute; + return (int) result; + } + + private static int msFromTime(double t) { + double result; + result = t % msPerSecond; + if (result < 0) + result += msPerSecond; + return (int) result; + } + + private static double MakeTime(double hour, double min, + double sec, double ms) + { + return ((hour * MinutesPerHour + min) * SecondsPerMinute + sec) + * msPerSecond + ms; + } + + private static double MakeDay(double year, double month, double date) { + double result; + boolean leap; + double yearday; + double monthday; + + year += Math.floor(month / 12); + + month = month % 12; + if (month < 0) + month += 12; + + leap = (DaysInYear((int) year) == 366); + + yearday = Math.floor(TimeFromYear(year) / msPerDay); + monthday = DayFromMonth((int) month, leap); + + result = yearday + + monthday + + date - 1; + return result; + } + + private static double MakeDate(double day, double time) { + return day * msPerDay + time; + } + + private static double TimeClip(double d) { + if (d != d || + d == Double.POSITIVE_INFINITY || + d == Double.NEGATIVE_INFINITY || + Math.abs(d) > HalfTimeDomain) + { + return ScriptRuntime.NaN; + } + if (d > 0.0) + return Math.floor(d + 0.); + else + return Math.ceil(d + 0.); + } + + /* end of ECMA helper functions */ + + /* find UTC time from given date... no 1900 correction! */ + public static double date_msecFromDate(double year, double mon, + double mday, double hour, + double min, double sec, + double msec) + { + double day; + double time; + double result; + + day = MakeDay(year, mon, mday); + time = MakeTime(hour, min, sec, msec); + result = MakeDate(day, time); + return result; + } + + + private static final int MAXARGS = 7; + private static double jsStaticFunction_UTC(Object[] args) { + double array[] = new double[MAXARGS]; + int loop; + double d; + + for (loop = 0; loop < MAXARGS; loop++) { + if (loop < args.length) { + d = ScriptRuntime.toNumber(args[loop]); + if (d != d || Double.isInfinite(d)) { + return ScriptRuntime.NaN; + } + array[loop] = ScriptRuntime.toInteger(args[loop]); + } else { + array[loop] = 0; + } + } + + /* adjust 2-digit years into the 20th century */ + if (array[0] >= 0 && array[0] <= 99) + array[0] += 1900; + + /* if we got a 0 for 'date' (which is out of range) + * pretend it's a 1. (So Date.UTC(1972, 5) works) */ + if (array[2] < 1) + array[2] = 1; + + d = date_msecFromDate(array[0], array[1], array[2], + array[3], array[4], array[5], array[6]); + d = TimeClip(d); + return d; + // return new Double(d); + } + + /* + * Use ported code from jsdate.c rather than the locale-specific + * date-parsing code from Java, to keep js and rhino consistent. + * Is this the right strategy? + */ + + /* for use by date_parse */ + + /* replace this with byte arrays? Cheaper? */ + private static String wtb[] = { + "am", "pm", + "monday", "tuesday", "wednesday", "thursday", "friday", + "saturday", "sunday", + "january", "february", "march", "april", "may", "june", + "july", "august", "september", "october", "november", "december", + "gmt", "ut", "utc", "est", "edt", "cst", "cdt", + "mst", "mdt", "pst", "pdt" + /* time zone table needs to be expanded */ + }; + + private static int ttb[] = { + -1, -2, 0, 0, 0, 0, 0, 0, 0, /* AM/PM */ + 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, + 10000 + 0, 10000 + 0, 10000 + 0, /* UT/UTC */ + 10000 + 5 * 60, 10000 + 4 * 60, /* EDT */ + 10000 + 6 * 60, 10000 + 5 * 60, + 10000 + 7 * 60, 10000 + 6 * 60, + 10000 + 8 * 60, 10000 + 7 * 60 + }; + + /* helper for date_parse */ + private static boolean date_regionMatches(String s1, int s1off, + String s2, int s2off, + int count) + { + boolean result = false; + /* return true if matches, otherwise, false */ + int s1len = s1.length(); + int s2len = s2.length(); + + while (count > 0 && s1off < s1len && s2off < s2len) { + if (Character.toLowerCase(s1.charAt(s1off)) != + Character.toLowerCase(s2.charAt(s2off))) + break; + s1off++; + s2off++; + count--; + } + + if (count == 0) { + result = true; + } + return result; + } + + private static double date_parseString(String s) { + double msec; + + int year = -1; + int mon = -1; + int mday = -1; + int hour = -1; + int min = -1; + int sec = -1; + char c = 0; + char si = 0; + int i = 0; + int n = -1; + double tzoffset = -1; + char prevc = 0; + int limit = 0; + boolean seenplusminus = false; + + if (s == null) // ??? Will s be null? + return ScriptRuntime.NaN; + limit = s.length(); + while (i < limit) { + c = s.charAt(i); + i++; + if (c <= ' ' || c == ',' || c == '-') { + if (i < limit) { + si = s.charAt(i); + if (c == '-' && '0' <= si && si <= '9') { + prevc = c; + } + } + continue; + } + if (c == '(') { /* comments) */ + int depth = 1; + while (i < limit) { + c = s.charAt(i); + i++; + if (c == '(') + depth++; + else if (c == ')') + if (--depth <= 0) + break; + } + continue; + } + if ('0' <= c && c <= '9') { + n = c - '0'; + while (i < limit && '0' <= (c = s.charAt(i)) && c <= '9') { + n = n * 10 + c - '0'; + i++; + } + + /* allow TZA before the year, so + * 'Wed Nov 05 21:49:11 GMT-0800 1997' + * works */ + + /* uses of seenplusminus allow : in TZA, so Java + * no-timezone style of GMT+4:30 works + */ + if ((prevc == '+' || prevc == '-')/* && year>=0 */) { + /* make ':' case below change tzoffset */ + seenplusminus = true; + + /* offset */ + if (n < 24) + n = n * 60; /* EG. "GMT-3" */ + else + n = n % 100 + n / 100 * 60; /* eg "GMT-0430" */ + if (prevc == '+') /* plus means east of GMT */ + n = -n; + if (tzoffset != 0 && tzoffset != -1) + return ScriptRuntime.NaN; + tzoffset = n; + } else if (n >= 70 || + (prevc == '/' && mon >= 0 && mday >= 0 && year < 0)) { + if (year >= 0) + return ScriptRuntime.NaN; + else if (c <= ' ' || c == ',' || c == '/' || i >= limit) + year = n < 100 ? n + 1900 : n; + else + return ScriptRuntime.NaN; + } else if (c == ':') { + if (hour < 0) + hour = /*byte*/ n; + else if (min < 0) + min = /*byte*/ n; + else + return ScriptRuntime.NaN; + } else if (c == '/') { + if (mon < 0) + mon = /*byte*/ n-1; + else if (mday < 0) + mday = /*byte*/ n; + else + return ScriptRuntime.NaN; + } else if (i < limit && c != ',' && c > ' ' && c != '-') { + return ScriptRuntime.NaN; + } else if (seenplusminus && n < 60) { /* handle GMT-3:30 */ + if (tzoffset < 0) + tzoffset -= n; + else + tzoffset += n; + } else if (hour >= 0 && min < 0) { + min = /*byte*/ n; + } else if (min >= 0 && sec < 0) { + sec = /*byte*/ n; + } else if (mday < 0) { + mday = /*byte*/ n; + } else { + return ScriptRuntime.NaN; + } + prevc = 0; + } else if (c == '/' || c == ':' || c == '+' || c == '-') { + prevc = c; + } else { + int st = i - 1; + int k; + while (i < limit) { + c = s.charAt(i); + if (!(('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z'))) + break; + i++; + } + if (i <= st + 1) + return ScriptRuntime.NaN; + for (k = wtb.length; --k >= 0;) + if (date_regionMatches(wtb[k], 0, s, st, i-st)) { + int action = ttb[k]; + if (action != 0) { + if (action < 0) { + /* + * AM/PM. Count 12:30 AM as 00:30, 12:30 PM as + * 12:30, instead of blindly adding 12 if PM. + */ + if (hour > 12 || hour < 0) { + return ScriptRuntime.NaN; + } else { + if (action == -1 && hour == 12) { // am + hour = 0; + } else if (action == -2 && hour != 12) {// pm + hour += 12; + } + } + } else if (action <= 13) { /* month! */ + if (mon < 0) { + mon = /*byte*/ (action - 2); + } else { + return ScriptRuntime.NaN; + } + } else { + tzoffset = action - 10000; + } + } + break; + } + if (k < 0) + return ScriptRuntime.NaN; + prevc = 0; + } + } + if (year < 0 || mon < 0 || mday < 0) + return ScriptRuntime.NaN; + if (sec < 0) + sec = 0; + if (min < 0) + min = 0; + if (hour < 0) + hour = 0; + if (tzoffset == -1) { /* no time zone specified, have to use local */ + double time; + time = date_msecFromDate(year, mon, mday, hour, min, sec, 0); + return internalUTC(time); + } + + msec = date_msecFromDate(year, mon, mday, hour, min, sec, 0); + msec += tzoffset * msPerMinute; + return msec; + } + + private static double jsStaticFunction_parse(String s) { + return date_parseString(s); + } + + private static final int FORMATSPEC_FULL = 0; + private static final int FORMATSPEC_DATE = 1; + private static final int FORMATSPEC_TIME = 2; + + private static String date_format(double t, int format) { + if (t != t) + return jsFunction_NaN_date_str; + + StringBuffer result = new StringBuffer(60); + double local = LocalTime(t); + + /* offset from GMT in minutes. The offset includes daylight savings, + if it applies. */ + int minutes = (int) Math.floor((LocalTZA + DaylightSavingTA(t)) + / msPerMinute); + /* map 510 minutes to 0830 hours */ + int offset = (minutes / 60) * 100 + minutes % 60; + + String dateStr = Integer.toString(DateFromTime(local)); + String hourStr = Integer.toString(HourFromTime(local)); + String minStr = Integer.toString(MinFromTime(local)); + String secStr = Integer.toString(SecFromTime(local)); + String offsetStr = Integer.toString(offset > 0 ? offset : -offset); + int year = YearFromTime(local); + String yearStr = Integer.toString(year > 0 ? year : -year); + + /* Tue Oct 31 09:41:40 GMT-0800 (PST) 2000 */ + /* Tue Oct 31 2000 */ + /* 09:41:40 GMT-0800 (PST) */ + + if (format != FORMATSPEC_TIME) { + result.append(days[WeekDay(local)]); + result.append(' '); + result.append(months[MonthFromTime(local)]); + if (dateStr.length() == 1) + result.append(" 0"); + else + result.append(' '); + result.append(dateStr); + result.append(' '); + } + + if (format != FORMATSPEC_DATE) { + if (hourStr.length() == 1) + result.append('0'); + result.append(hourStr); + if (minStr.length() == 1) + result.append(":0"); + else + result.append(':'); + result.append(minStr); + if (secStr.length() == 1) + result.append(":0"); + else + result.append(':'); + result.append(secStr); + if (offset > 0) + result.append(" GMT+"); + else + result.append(" GMT-"); + for (int i = offsetStr.length(); i < 4; i++) + result.append('0'); + result.append(offsetStr); + + if (timeZoneFormatter == null) + timeZoneFormatter = new java.text.SimpleDateFormat("zzz"); + + if (timeZoneFormatter != null) { + result.append(" ("); + java.util.Date date = new Date((long) t); + result.append(timeZoneFormatter.format(date)); + result.append(')'); + } + if (format != FORMATSPEC_TIME) + result.append(' '); + } + + if (format != FORMATSPEC_TIME) { + if (year < 0) + result.append('-'); + for (int i = yearStr.length(); i < 4; i++) + result.append('0'); + result.append(yearStr); + } + + return result.toString(); + } + + /* the javascript constructor */ + private static Object jsConstructor(Object[] args, boolean inNewExpr) { + // if called as a function, just return a string + // representing the current time. + if (!inNewExpr) + return date_format(Now(), FORMATSPEC_FULL); + + NativeDate obj = new NativeDate(); + + // if called as a constructor with no args, + // return a new Date with the current time. + if (args.length == 0) { + obj.date = Now(); + return obj; + } + + // if called with just one arg - + if (args.length == 1) { + double date; + if (args[0] instanceof Scriptable) + args[0] = ((Scriptable) args[0]).getDefaultValue(null); + if (!(args[0] instanceof String)) { + // if it's not a string, use it as a millisecond date + date = ScriptRuntime.toNumber(args[0]); + } else { + // it's a string; parse it. + String str = (String) args[0]; + date = date_parseString(str); + } + obj.date = TimeClip(date); + return obj; + } + + // multiple arguments; year, month, day etc. + double array[] = new double[MAXARGS]; + int loop; + double d; + + for (loop = 0; loop < MAXARGS; loop++) { + if (loop < args.length) { + d = ScriptRuntime.toNumber(args[loop]); + + if (d != d || Double.isInfinite(d)) { + obj.date = ScriptRuntime.NaN; + return obj; + } + array[loop] = ScriptRuntime.toInteger(args[loop]); + } else { + array[loop] = 0; + } + } + + /* adjust 2-digit years into the 20th century */ + if (array[0] >= 0 && array[0] <= 99) + array[0] += 1900; + + /* if we got a 0 for 'date' (which is out of range) + * pretend it's a 1 */ + if (array[2] < 1) + array[2] = 1; + + double day = MakeDay(array[0], array[1], array[2]); + double time = MakeTime(array[3], array[4], array[5], array[6]); + time = MakeDate(day, time); + time = internalUTC(time); + obj.date = TimeClip(time); + + return obj; + } + + /* constants for toString, toUTCString */ + private static String jsFunction_NaN_date_str = "Invalid Date"; + + private static String[] days = { + "Sun","Mon","Tue","Wed","Thu","Fri","Sat" + }; + + private static String[] months = { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" + }; + + private static String toLocale_helper(double t, + java.text.DateFormat formatter) + { + if (t != t) + return jsFunction_NaN_date_str; + + java.util.Date tempdate = new Date((long) t); + return formatter.format(tempdate); + } + + private static String jsFunction_toLocaleString(double date) { + if (localeDateTimeFormatter == null) + localeDateTimeFormatter = + DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG); + + return toLocale_helper(date, localeDateTimeFormatter); + } + + private static String jsFunction_toLocaleTimeString(double date) { + if (localeTimeFormatter == null) + localeTimeFormatter = DateFormat.getTimeInstance(DateFormat.LONG); + + return toLocale_helper(date, localeTimeFormatter); + } + + private static String jsFunction_toLocaleDateString(double date) { + if (localeDateFormatter == null) + localeDateFormatter = DateFormat.getDateInstance(DateFormat.LONG); + + return toLocale_helper(date, localeDateFormatter); + } + + private static String jsFunction_toUTCString(double date) { + StringBuffer result = new StringBuffer(60); + + String dateStr = Integer.toString(DateFromTime(date)); + String hourStr = Integer.toString(HourFromTime(date)); + String minStr = Integer.toString(MinFromTime(date)); + String secStr = Integer.toString(SecFromTime(date)); + int year = YearFromTime(date); + String yearStr = Integer.toString(year > 0 ? year : -year); + + result.append(days[WeekDay(date)]); + result.append(", "); + if (dateStr.length() == 1) + result.append('0'); + result.append(dateStr); + result.append(' '); + result.append(months[MonthFromTime(date)]); + if (year < 0) + result.append(" -"); + else + result.append(' '); + int i; + for (i = yearStr.length(); i < 4; i++) + result.append('0'); + result.append(yearStr); + + if (hourStr.length() == 1) + result.append(" 0"); + else + result.append(' '); + result.append(hourStr); + if (minStr.length() == 1) + result.append(":0"); + else + result.append(':'); + result.append(minStr); + if (secStr.length() == 1) + result.append(":0"); + else + result.append(':'); + result.append(secStr); + + result.append(" GMT"); + return result.toString(); + } + + private static double jsFunction_getYear(Context cx, double date) { + + int result = YearFromTime(LocalTime(date)); + + if (cx.hasFeature(Context.FEATURE_NON_ECMA_GET_YEAR)) { + if (result >= 1900 && result < 2000) { + result -= 1900; + } + } + else { + result -= 1900; + } + return result; + } + + private static double jsFunction_getTimezoneOffset(double date) { + return (date - LocalTime(date)) / msPerMinute; + } + + public double jsFunction_setTime(double time) { + this.date = TimeClip(time); + return this.date; + } + + private double makeTime(Object[] args, int maxargs, boolean local) { + int i; + double conv[] = new double[4]; + double hour, min, sec, msec; + double lorutime; /* Local or UTC version of date */ + + double time; + double result; + + double date = this.date; + + /* just return NaN if the date is already NaN */ + if (date != date) + return date; + + /* Satisfy the ECMA rule that if a function is called with + * fewer arguments than the specified formal arguments, the + * remaining arguments are set to undefined. Seems like all + * the Date.setWhatever functions in ECMA are only varargs + * beyond the first argument; this should be set to undefined + * if it's not given. This means that "d = new Date(); + * d.setMilliseconds()" returns NaN. Blech. + */ + if (args.length == 0) + args = ScriptRuntime.padArguments(args, 1); + + for (i = 0; i < args.length && i < maxargs; i++) { + conv[i] = ScriptRuntime.toNumber(args[i]); + + // limit checks that happen in MakeTime in ECMA. + if (conv[i] != conv[i] || Double.isInfinite(conv[i])) { + this.date = ScriptRuntime.NaN; + return this.date; + } + conv[i] = ScriptRuntime.toInteger(conv[i]); + } + + if (local) + lorutime = LocalTime(date); + else + lorutime = date; + + i = 0; + int stop = args.length; + + if (maxargs >= 4 && i < stop) + hour = conv[i++]; + else + hour = HourFromTime(lorutime); + + if (maxargs >= 3 && i < stop) + min = conv[i++]; + else + min = MinFromTime(lorutime); + + if (maxargs >= 2 && i < stop) + sec = conv[i++]; + else + sec = SecFromTime(lorutime); + + if (maxargs >= 1 && i < stop) + msec = conv[i++]; + else + msec = msFromTime(lorutime); + + time = MakeTime(hour, min, sec, msec); + result = MakeDate(Day(lorutime), time); + + if (local) + result = internalUTC(result); + date = TimeClip(result); + + this.date = date; + return date; + } + + private double jsFunction_setHours(Object[] args) { + return makeTime(args, 4, true); + } + + private double jsFunction_setUTCHours(Object[] args) { + return makeTime(args, 4, false); + } + + private double makeDate(Object[] args, int maxargs, boolean local) { + int i; + double conv[] = new double[3]; + double year, month, day; + double lorutime; /* local or UTC version of date */ + double result; + + double date = this.date; + + /* See arg padding comment in makeTime.*/ + if (args.length == 0) + args = ScriptRuntime.padArguments(args, 1); + + for (i = 0; i < args.length && i < maxargs; i++) { + conv[i] = ScriptRuntime.toNumber(args[i]); + + // limit checks that happen in MakeDate in ECMA. + if (conv[i] != conv[i] || Double.isInfinite(conv[i])) { + this.date = ScriptRuntime.NaN; + return this.date; + } + conv[i] = ScriptRuntime.toInteger(conv[i]); + } + + /* return NaN if date is NaN and we're not setting the year, + * If we are, use 0 as the time. */ + if (date != date) { + if (args.length < 3) { + return ScriptRuntime.NaN; + } else { + lorutime = 0; + } + } else { + if (local) + lorutime = LocalTime(date); + else + lorutime = date; + } + + i = 0; + int stop = args.length; + + if (maxargs >= 3 && i < stop) + year = conv[i++]; + else + year = YearFromTime(lorutime); + + if (maxargs >= 2 && i < stop) + month = conv[i++]; + else + month = MonthFromTime(lorutime); + + if (maxargs >= 1 && i < stop) + day = conv[i++]; + else + day = DateFromTime(lorutime); + + day = MakeDay(year, month, day); /* day within year */ + result = MakeDate(day, TimeWithinDay(lorutime)); + + if (local) + result = internalUTC(result); + + date = TimeClip(result); + + this.date = date; + return date; + } + + private double jsFunction_setYear(double year) { + double day, result; + if (year != year || Double.isInfinite(year)) { + this.date = ScriptRuntime.NaN; + return this.date; + } + + if (this.date != this.date) { + this.date = 0; + } else { + this.date = LocalTime(this.date); + } + + if (year >= 0 && year <= 99) + year += 1900; + + day = MakeDay(year, MonthFromTime(this.date), DateFromTime(this.date)); + result = MakeDate(day, TimeWithinDay(this.date)); + result = internalUTC(result); + + this.date = TimeClip(result); + return this.date; + } + + protected String getIdName(int id) { + if (prototypeFlag) { + switch (id) { + case ConstructorId_UTC: return "UTC"; + case ConstructorId_parse: return "parse"; + case Id_constructor: return "constructor"; + case Id_toString: return "toString"; + case Id_toTimeString: return "toTimeString"; + case Id_toDateString: return "toDateString"; + case Id_toLocaleString: return "toLocaleString"; + case Id_toLocaleTimeString: return "toLocaleTimeString"; + case Id_toLocaleDateString: return "toLocaleDateString"; + case Id_toUTCString: return "toUTCString"; + case Id_valueOf: return "valueOf"; + case Id_getTime: return "getTime"; + case Id_getYear: return "getYear"; + case Id_getFullYear: return "getFullYear"; + case Id_getUTCFullYear: return "getUTCFullYear"; + case Id_getMonth: return "getMonth"; + case Id_getUTCMonth: return "getUTCMonth"; + case Id_getDate: return "getDate"; + case Id_getUTCDate: return "getUTCDate"; + case Id_getDay: return "getDay"; + case Id_getUTCDay: return "getUTCDay"; + case Id_getHours: return "getHours"; + case Id_getUTCHours: return "getUTCHours"; + case Id_getMinutes: return "getMinutes"; + case Id_getUTCMinutes: return "getUTCMinutes"; + case Id_getSeconds: return "getSeconds"; + case Id_getUTCSeconds: return "getUTCSeconds"; + case Id_getMilliseconds: return "getMilliseconds"; + case Id_getUTCMilliseconds: return "getUTCMilliseconds"; + case Id_getTimezoneOffset: return "getTimezoneOffset"; + case Id_setTime: return "setTime"; + case Id_setMilliseconds: return "setMilliseconds"; + case Id_setUTCMilliseconds: return "setUTCMilliseconds"; + case Id_setSeconds: return "setSeconds"; + case Id_setUTCSeconds: return "setUTCSeconds"; + case Id_setMinutes: return "setMinutes"; + case Id_setUTCMinutes: return "setUTCMinutes"; + case Id_setHours: return "setHours"; + case Id_setUTCHours: return "setUTCHours"; + case Id_setDate: return "setDate"; + case Id_setUTCDate: return "setUTCDate"; + case Id_setMonth: return "setMonth"; + case Id_setUTCMonth: return "setUTCMonth"; + case Id_setFullYear: return "setFullYear"; + case Id_setUTCFullYear: return "setUTCFullYear"; + case Id_setYear: return "setYear"; + } + } + return null; + } + +// #string_id_map# + + protected int mapNameToId(String s) { + if (!prototypeFlag) { return 0; } + int id; +// #generated# Last update: 2001-04-22 23:46:59 CEST + L0: { id = 0; String X = null; int c; + L: switch (s.length()) { + case 6: X="getDay";id=Id_getDay; break L; + case 7: switch (s.charAt(3)) { + case 'D': c=s.charAt(0); + if (c=='g') { X="getDate";id=Id_getDate; } + else if (c=='s') { X="setDate";id=Id_setDate; } + break L; + case 'T': c=s.charAt(0); + if (c=='g') { X="getTime";id=Id_getTime; } + else if (c=='s') { X="setTime";id=Id_setTime; } + break L; + case 'Y': c=s.charAt(0); + if (c=='g') { X="getYear";id=Id_getYear; } + else if (c=='s') { X="setYear";id=Id_setYear; } + break L; + case 'u': X="valueOf";id=Id_valueOf; break L; + } break L; + case 8: c=s.charAt(0); + if (c=='g') { + c=s.charAt(7); + if (c=='h') { X="getMonth";id=Id_getMonth; } + else if (c=='s') { X="getHours";id=Id_getHours; } + } + else if (c=='s') { + c=s.charAt(7); + if (c=='h') { X="setMonth";id=Id_setMonth; } + else if (c=='s') { X="setHours";id=Id_setHours; } + } + else if (c=='t') { X="toString";id=Id_toString; } + break L; + case 9: X="getUTCDay";id=Id_getUTCDay; break L; + case 10: c=s.charAt(3); + if (c=='M') { + c=s.charAt(0); + if (c=='g') { X="getMinutes";id=Id_getMinutes; } + else if (c=='s') { X="setMinutes";id=Id_setMinutes; } + } + else if (c=='S') { + c=s.charAt(0); + if (c=='g') { X="getSeconds";id=Id_getSeconds; } + else if (c=='s') { X="setSeconds";id=Id_setSeconds; } + } + else if (c=='U') { + c=s.charAt(0); + if (c=='g') { X="getUTCDate";id=Id_getUTCDate; } + else if (c=='s') { X="setUTCDate";id=Id_setUTCDate; } + } + break L; + case 11: switch (s.charAt(3)) { + case 'F': c=s.charAt(0); + if (c=='g') { X="getFullYear";id=Id_getFullYear; } + else if (c=='s') { X="setFullYear";id=Id_setFullYear; } + break L; + case 'M': X="toGMTString";id=Id_toGMTString; break L; + case 'T': X="toUTCString";id=Id_toUTCString; break L; + case 'U': c=s.charAt(0); + if (c=='g') { + c=s.charAt(9); + if (c=='r') { X="getUTCHours";id=Id_getUTCHours; } + else if (c=='t') { X="getUTCMonth";id=Id_getUTCMonth; } + } + else if (c=='s') { + c=s.charAt(9); + if (c=='r') { X="setUTCHours";id=Id_setUTCHours; } + else if (c=='t') { X="setUTCMonth";id=Id_setUTCMonth; } + } + break L; + case 's': X="constructor";id=Id_constructor; break L; + } break L; + case 12: c=s.charAt(2); + if (c=='D') { X="toDateString";id=Id_toDateString; } + else if (c=='T') { X="toTimeString";id=Id_toTimeString; } + break L; + case 13: c=s.charAt(0); + if (c=='g') { + c=s.charAt(6); + if (c=='M') { X="getUTCMinutes";id=Id_getUTCMinutes; } + else if (c=='S') { X="getUTCSeconds";id=Id_getUTCSeconds; } + } + else if (c=='s') { + c=s.charAt(6); + if (c=='M') { X="setUTCMinutes";id=Id_setUTCMinutes; } + else if (c=='S') { X="setUTCSeconds";id=Id_setUTCSeconds; } + } + break L; + case 14: c=s.charAt(0); + if (c=='g') { X="getUTCFullYear";id=Id_getUTCFullYear; } + else if (c=='s') { X="setUTCFullYear";id=Id_setUTCFullYear; } + else if (c=='t') { X="toLocaleString";id=Id_toLocaleString; } + break L; + case 15: c=s.charAt(0); + if (c=='g') { X="getMilliseconds";id=Id_getMilliseconds; } + else if (c=='s') { X="setMilliseconds";id=Id_setMilliseconds; } + break L; + case 17: X="getTimezoneOffset";id=Id_getTimezoneOffset; break L; + case 18: c=s.charAt(0); + if (c=='g') { X="getUTCMilliseconds";id=Id_getUTCMilliseconds; } + else if (c=='s') { X="setUTCMilliseconds";id=Id_setUTCMilliseconds; } + else if (c=='t') { + c=s.charAt(8); + if (c=='D') { X="toLocaleDateString";id=Id_toLocaleDateString; } + else if (c=='T') { X="toLocaleTimeString";id=Id_toLocaleTimeString; } + } + break L; + } + if (X!=null && X!=s && !X.equals(s)) id = 0; + } +// #/generated# + return id; + } + + private static final int + ConstructorId_UTC = -2, + ConstructorId_parse = -1, + + Id_constructor = 1, + Id_toString = 2, + Id_toTimeString = 3, + Id_toDateString = 4, + Id_toLocaleString = 5, + Id_toLocaleTimeString = 6, + Id_toLocaleDateString = 7, + Id_toUTCString = 8, + Id_valueOf = 9, + Id_getTime = 10, + Id_getYear = 11, + Id_getFullYear = 12, + Id_getUTCFullYear = 13, + Id_getMonth = 14, + Id_getUTCMonth = 15, + Id_getDate = 16, + Id_getUTCDate = 17, + Id_getDay = 18, + Id_getUTCDay = 19, + Id_getHours = 20, + Id_getUTCHours = 21, + Id_getMinutes = 22, + Id_getUTCMinutes = 23, + Id_getSeconds = 24, + Id_getUTCSeconds = 25, + Id_getMilliseconds = 26, + Id_getUTCMilliseconds = 27, + Id_getTimezoneOffset = 28, + Id_setTime = 29, + Id_setMilliseconds = 30, + Id_setUTCMilliseconds = 31, + Id_setSeconds = 32, + Id_setUTCSeconds = 33, + Id_setMinutes = 34, + Id_setUTCMinutes = 35, + Id_setHours = 36, + Id_setUTCHours = 37, + Id_setDate = 38, + Id_setUTCDate = 39, + Id_setMonth = 40, + Id_setUTCMonth = 41, + Id_setFullYear = 42, + Id_setUTCFullYear = 43, + Id_setYear = 44, + + MAX_PROTOTYPE_ID = 44; + + private static final int + Id_toGMTString = Id_toUTCString; // Alias, see Ecma B.2.6 +// #/string_id_map# + + /* cached values */ + private static java.util.TimeZone thisTimeZone; + private static double LocalTZA; + private static java.text.DateFormat timeZoneFormatter; + private static java.text.DateFormat localeDateTimeFormatter; + private static java.text.DateFormat localeDateFormatter; + private static java.text.DateFormat localeTimeFormatter; + + private double date; + + private boolean prototypeFlag; +} + + diff --git a/src/org/mozilla/javascript/NativeError.java b/src/org/mozilla/javascript/NativeError.java new file mode 100644 index 0000000..982d0fc --- /dev/null +++ b/src/org/mozilla/javascript/NativeError.java @@ -0,0 +1,204 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * The contents of this file are subject to the Netscape Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express oqr + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is Rhino code, released + * May 6, 1999. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1997-1999 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * Igor Bukanov + * Roger Lawrence + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (the "GPL"), in which case the + * provisions of the GPL are applicable instead of those above. + * If you wish to allow use of your version of this file only + * under the terms of the GPL and not to allow others to use your + * version of this file under the NPL, indicate your decision by + * deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete + * the provisions above, a recipient may use your version of this + * file under either the NPL or the GPL. + */ + + +package org.mozilla.javascript; + +/** + * + * The class of error objects + * + * ECMA 15.11 + */ +public class NativeError extends IdScriptable { + + public static void init(Context cx, Scriptable scope, boolean sealed) { + NativeError obj = new NativeError(); + obj.prototypeFlag = true; + obj.messageValue = ""; + obj.nameValue = "Error"; + obj.addAsPrototype(MAX_PROTOTYPE_ID, cx, scope, sealed); + } + + protected int getIdDefaultAttributes(int id) { + if (id == Id_message || id == Id_name) { return EMPTY; } + return super.getIdDefaultAttributes(id); + } + + protected boolean hasIdValue(int id) { + if (id == Id_message) { return messageValue != NOT_FOUND; } + if (id == Id_name) { return nameValue != NOT_FOUND; } + return super.hasIdValue(id); + } + + protected Object getIdValue(int id) { + if (id == Id_message) { return messageValue; } + if (id == Id_name) { return nameValue; } + return super.getIdValue(id); + } + + protected void setIdValue(int id, Object value) { + if (id == Id_message) { messageValue = value; return; } + if (id == Id_name) { nameValue = value; return; } + super.setIdValue(id, value); + } + + protected void deleteIdValue(int id) { + if (id == Id_message) { messageValue = NOT_FOUND; return; } + if (id == Id_name) { nameValue = NOT_FOUND; return; } + super.deleteIdValue(id); + } + + public int methodArity(int methodId) { + if (prototypeFlag) { + if (methodId == Id_constructor) return 1; + if (methodId == Id_toString) return 0; + } + return super.methodArity(methodId); + } + + public Object execMethod + (int methodId, IdFunction f, + Context cx, Scriptable scope, Scriptable thisObj, Object[] args) + throws JavaScriptException + { + if (prototypeFlag) { + if (methodId == Id_constructor) { + return jsConstructor(cx, args, f, thisObj == null); + } + else if (methodId == Id_toString) { + return realThis(thisObj, f).toString(); + } + } + return super.execMethod(methodId, f, cx, scope, thisObj, args); + } + + private NativeError realThis(Scriptable thisObj, IdFunction f) { + while (!(thisObj instanceof NativeError)) { + thisObj = nextInstanceCheck(thisObj, f, true); + } + return (NativeError)thisObj; + } + + private static Object jsConstructor(Context cx, Object[] args, + Function funObj, boolean inNewExpr) + { + NativeError result = new NativeError(); + if (args.length >= 1) + result.messageValue = ScriptRuntime.toString(args[0]); + result.setPrototype(getClassPrototype(funObj, "Error")); + return result; + } + + public String getClassName() { + return "Error"; + } + + public String toString() { + return getName() + ": " + getMessage(); + } + + public String getName() { + Object val = nameValue; + return ScriptRuntime.toString(val != NOT_FOUND ? val + : Undefined.instance); + } + + public String getMessage() { + Object val = messageValue; + return ScriptRuntime.toString(val != NOT_FOUND ? val + : Undefined.instance); + } + + protected int maxInstanceId() { return MAX_INSTANCE_ID; } + + protected String getIdName(int id) { + if (id == Id_message) { return "message"; } + if (id == Id_name) { return "name"; } + if (prototypeFlag) { + if (id == Id_constructor) return "constructor"; + if (id == Id_toString) return "toString"; + } + return null; + } + +// #string_id_map# + + private static final int + Id_message = 1, + Id_name = 2, + + MAX_INSTANCE_ID = 2; + + protected int mapNameToId(String s) { + int id; +// #generated# Last update: 2001-05-19 21:55:23 CEST + L0: { id = 0; String X = null; + int s_length = s.length(); + if (s_length==4) { X="name";id=Id_name; } + else if (s_length==7) { X="message";id=Id_message; } + if (X!=null && X!=s && !X.equals(s)) id = 0; + } +// #/generated# +// #/string_id_map# + + if (id != 0 || !prototypeFlag) { return id; } + +// #string_id_map# +// #generated# Last update: 2001-05-19 21:55:23 CEST + L0: { id = 0; String X = null; + int s_length = s.length(); + if (s_length==8) { X="toString";id=Id_toString; } + else if (s_length==11) { X="constructor";id=Id_constructor; } + if (X!=null && X!=s && !X.equals(s)) id = 0; + } +// #/generated# + return id; + } + + private static final int + Id_constructor = MAX_INSTANCE_ID + 1, + Id_toString = MAX_INSTANCE_ID + 2, + + MAX_PROTOTYPE_ID = MAX_INSTANCE_ID + 2; + +// #/string_id_map# + + private Object messageValue = NOT_FOUND; + private Object nameValue = NOT_FOUND; + + private boolean prototypeFlag; +} diff --git a/src/org/mozilla/javascript/NativeFunction.java b/src/org/mozilla/javascript/NativeFunction.java new file mode 100644 index 0000000..dbc2e77 --- /dev/null +++ b/src/org/mozilla/javascript/NativeFunction.java @@ -0,0 +1,798 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * The contents of this file are subject to the Netscape Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express oqr + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is Rhino code, released + * May 6, 1999. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1997-1999 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * Igor Bukanov + * Roger Lawrence + * Mike McCabe + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (the "GPL"), in which case the + * provisions of the GPL are applicable instead of those above. + * If you wish to allow use of your version of this file only + * under the terms of the GPL and not to allow others to use your + * version of this file under the NPL, indicate your decision by + * deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete + * the provisions above, a recipient may use your version of this + * file under either the NPL or the GPL. + */ + +package org.mozilla.javascript; + +import java.util.Hashtable; + +/** + * This class implements the Function native object. + * See ECMA 15.3. + * @author Norris Boyd + */ +public class NativeFunction extends BaseFunction { + + private boolean nextIs(int i, int token) { + if (i + 1 < source.length()) + return source.charAt(i + 1) == token; + return false; + } + + // how much to indent + private final static int OFFSET = 4; + + // less how much for case labels + private final static int SETBACK = 2; + + // whether to do a debug print of the source information, when + // decompiling. + private static final boolean printSource = false; + + /** + * Decompile the source information associated with this js + * function/script back into a string. For the most part, this + * just means translating tokens back to their string + * representations; there's a little bit of lookahead logic to + * decide the proper spacing/indentation. Most of the work in + * mapping the original source to the prettyprinted decompiled + * version is done by the parser. + * + * Note that support for Context.decompileFunctionBody is hacked + * on through special cases; I suspect that js makes a distinction + * between function header and function body that rhino + * decompilation does not. + * + * @param cx Current context + * + * @param indent How much to indent the decompiled result + * + * @param justbody Whether the decompilation should omit the + * function header and trailing brace. + */ + + public String decompile(Context cx, int indent, boolean justbody) { + StringBuffer result = new StringBuffer(); + decompile(indent, true, justbody, result); + return result.toString(); + + } + + private void decompile(int indent, boolean toplevel, boolean justbody, + StringBuffer result) + { + if (source == null) { + if (!justbody) { + result.append("function "); + result.append(getFunctionName()); + result.append("() {\n\t"); + } + result.append("[native code]\n"); + if (!justbody) { + result.append("}\n"); + } + return; + } + + // Spew tokens in source, for debugging. + // as TYPE number char + if (printSource) { + System.err.println("length:" + source.length()); + for (int i = 0; i < source.length(); i++) { + // Note that tokenToName will fail unless Context.printTrees + // is true. + String tokenname = TokenStream.tokenToName(source.charAt(i)); + if (tokenname == null) + tokenname = "---"; + String pad = tokenname.length() > 7 + ? "\t" + : "\t\t"; + System.err.println + (tokenname + + pad + (int)source.charAt(i) + + "\t'" + ScriptRuntime.escapeString + (source.substring(i, i+1)) + + "'"); + } + System.err.println(); + } + + int i = 0; + + if (source.length() > 0) { + /* special-case FUNCTION as the first token; if it is, + * (and it's not followed by a NAME or LP) then we're + * decompiling a function (and not the toplevel script.) + + * FUNCTION appearing elsewhere is an escape that means we'll + * need to call toString of the given function (object). + + * If not at the top level, don't add an initial indent; + * let the caller do it, so functions as expressions look + * reasonable. */ + + if (toplevel) { + // add an initial newline to exactly match js. + if (!justbody) + result.append('\n'); + for (int j = 0; j < indent; j++) + result.append(' '); + } + + if (source.charAt(0) == TokenStream.FUNCTION + // make sure it's not a script that begins with a + // reference to a function definition. + && source.length() > 1 + && (source.charAt(1) == TokenStream.NAME + || source.charAt(1) == TokenStream.LP)) + { + if (!justbody) { + result.append("function "); + + /* version != 1.2 Function constructor behavior - if + * there's no function name in the source info, and + * the names[0] entry is the empty string, then it must + * have been created by the Function constructor; + * print 'anonymous' as the function name if the + * version (under which the function was compiled) is + * less than 1.2... or if it's greater than 1.2, because + * we need to be closer to ECMA. (ToSource, please?) + */ + if (nextIs(i, TokenStream.LP) + && this.version != Context.VERSION_1_2 + && this.functionName != null + && this.functionName.equals("anonymous")) + result.append("anonymous"); + i++; + } else { + /* Skip past the entire function header to the next EOL. + * Depends on how NAMEs are encoded. + */ + while (i < source.length() + && (source.charAt(i) != TokenStream.EOL + // the length char of a NAME sequence + // can look like an EOL. + || (i > 0 + && source.charAt(i-1) == TokenStream.NAME))) + { + i++; + } + // Skip past the EOL, too. + i++; + } + } + } + + while (i < source.length()) { + int stop; + switch(source.charAt(i)) { + case TokenStream.NAME: + case TokenStream.OBJECT: // re-wrapped in '/'s in parser... + /* NAMEs are encoded as NAME, (char) length, string... + * Note that lookahead for detecting labels depends on + * this encoding; change there if this changes. + + * Also change function-header skipping code above, + * used when decompling under decompileFunctionBody. + */ + i++; + stop = i + (int)source.charAt(i); + result.append(source.substring(i + 1, stop + 1)); + i = stop; + break; + + case TokenStream.NUMBER: + i++; + long lbits = 0; + switch(source.charAt(i)) { + case 'S': + i++; + result.append((int)source.charAt(i)); + break; + + case 'J': + i++; + lbits |= (long)source.charAt(i++) << 48; + lbits |= (long)source.charAt(i++) << 32; + lbits |= (long)source.charAt(i++) << 16; + lbits |= (long)source.charAt(i); + + result.append(lbits); + break; + case 'D': + i++; + + lbits |= (long)source.charAt(i++) << 48; + lbits |= (long)source.charAt(i++) << 32; + lbits |= (long)source.charAt(i++) << 16; + lbits |= (long)source.charAt(i); + + double dval = Double.longBitsToDouble(lbits); + result.append(ScriptRuntime.numberToString(dval, 10)); + break; + } + break; + + case TokenStream.STRING: + i++; + stop = i + (int)source.charAt(i); + result.append('"'); + result.append(ScriptRuntime.escapeString + (source.substring(i + 1, stop + 1))); + result.append('"'); + i = stop; + break; + + case TokenStream.PRIMARY: + i++; + switch(source.charAt(i)) { + case TokenStream.TRUE: + result.append("true"); + break; + + case TokenStream.FALSE: + result.append("false"); + break; + + case TokenStream.NULL: + result.append("null"); + break; + + case TokenStream.THIS: + result.append("this"); + break; + + case TokenStream.TYPEOF: + result.append("typeof"); + break; + + case TokenStream.VOID: + result.append("void"); + break; + + case TokenStream.UNDEFINED: + result.append("undefined"); + break; + } + break; + + case TokenStream.FUNCTION: { + /* decompile a FUNCTION token as an escape; call + * toString on the nth enclosed nested function, + * where n is given by the byte that follows. + */ + + i++; + int functionNumber = source.charAt(i); + if (nestedFunctions == null + || functionNumber > nestedFunctions.length) + { + String message; + if (functionName != null && functionName.length() > 0) { + message = Context.getMessage2 + ("msg.no.function.ref.found.in", + new Integer((int)source.charAt(i)), functionName); + } else { + message = Context.getMessage1 + ("msg.no.function.ref.found", + new Integer((int)source.charAt(i))); + } + throw Context.reportRuntimeError(message); + } + nestedFunctions[functionNumber]. + decompile(indent, false, false, result); + break; + } + case TokenStream.COMMA: + result.append(", "); + break; + + case TokenStream.LC: + if (nextIs(i, TokenStream.EOL)) + indent += OFFSET; + result.append('{'); + break; + + case TokenStream.RC: + /* don't print the closing RC if it closes the + * toplevel function and we're called from + * decompileFunctionBody. + */ + if (justbody && toplevel && i + 1 == source.length()) + break; + + if (nextIs(i, TokenStream.EOL)) + indent -= OFFSET; + if (nextIs(i, TokenStream.WHILE) + || nextIs(i, TokenStream.ELSE)) { + indent -= OFFSET; + result.append("} "); + } + else + result.append('}'); + break; + + case TokenStream.LP: + result.append('('); + break; + + case TokenStream.RP: + if (nextIs(i, TokenStream.LC)) + result.append(") "); + else + result.append(')'); + break; + + case TokenStream.LB: + result.append('['); + break; + + case TokenStream.RB: + result.append(']'); + break; + + case TokenStream.EOL: + result.append('\n'); + + /* add indent if any tokens remain, + * less setback if next token is + * a label, case or default. + */ + if (i + 1 < source.length()) { + int less = 0; + if (nextIs(i, TokenStream.CASE) + || nextIs(i, TokenStream.DEFAULT)) + less = SETBACK; + else if (nextIs(i, TokenStream.RC)) + less = OFFSET; + + /* elaborate check against label... skip past a + * following inlined NAME and look for a COLON. + * Depends on how NAME is encoded. + */ + else if (nextIs(i, TokenStream.NAME)) { + int skip = source.charAt(i + 2); + if (source.charAt(i + skip + 3) == TokenStream.COLON) + less = OFFSET; + } + + for (; less < indent; less++) + result.append(' '); + } + break; + + case TokenStream.DOT: + result.append('.'); + break; + + case TokenStream.NEW: + result.append("new "); + break; + + case TokenStream.DELPROP: + result.append("delete "); + break; + + case TokenStream.IF: + result.append("if "); + break; + + case TokenStream.ELSE: + result.append("else "); + break; + + case TokenStream.FOR: + result.append("for "); + break; + + case TokenStream.IN: + result.append(" in "); + break; + + case TokenStream.WITH: + result.append("with "); + break; + + case TokenStream.WHILE: + result.append("while "); + break; + + case TokenStream.DO: + result.append("do "); + break; + + case TokenStream.TRY: + result.append("try "); + break; + + case TokenStream.CATCH: + result.append("catch "); + break; + + case TokenStream.FINALLY: + result.append("finally "); + break; + + case TokenStream.THROW: + result.append("throw "); + break; + + case TokenStream.SWITCH: + result.append("switch "); + break; + + case TokenStream.BREAK: + if (nextIs(i, TokenStream.NAME)) + result.append("break "); + else + result.append("break"); + break; + + case TokenStream.CONTINUE: + if (nextIs(i, TokenStream.NAME)) + result.append("continue "); + else + result.append("continue"); + break; + + case TokenStream.CASE: + result.append("case "); + break; + + case TokenStream.DEFAULT: + result.append("default"); + break; + + case TokenStream.RETURN: + if (nextIs(i, TokenStream.SEMI)) + result.append("return"); + else + result.append("return "); + break; + + case TokenStream.VAR: + result.append("var "); + break; + + case TokenStream.SEMI: + if (nextIs(i, TokenStream.EOL)) + // statement termination + result.append(';'); + else + // separators in FOR + result.append("; "); + break; + + case TokenStream.ASSIGN: + i++; + switch(source.charAt(i)) { + case TokenStream.NOP: + result.append(" = "); + break; + + case TokenStream.ADD: + result.append(" += "); + break; + + case TokenStream.SUB: + result.append(" -= "); + break; + + case TokenStream.MUL: + result.append(" *= "); + break; + + case TokenStream.DIV: + result.append(" /= "); + break; + + case TokenStream.MOD: + result.append(" %= "); + break; + + case TokenStream.BITOR: + result.append(" |= "); + break; + + case TokenStream.BITXOR: + result.append(" ^= "); + break; + + case TokenStream.BITAND: + result.append(" &= "); + break; + + case TokenStream.LSH: + result.append(" <<= "); + break; + + case TokenStream.RSH: + result.append(" >>= "); + break; + + case TokenStream.URSH: + result.append(" >>>= "); + break; + } + break; + + case TokenStream.HOOK: + result.append(" ? "); + break; + + case TokenStream.OBJLIT: + // pun OBJLIT to mean colon in objlit property initialization. + // this needs to be distinct from COLON in the general case + // to distinguish from the colon in a ternary... which needs + // different spacing. + result.append(':'); + break; + + case TokenStream.COLON: + if (nextIs(i, TokenStream.EOL)) + // it's the end of a label + result.append(':'); + else + // it's the middle part of a ternary + result.append(" : "); + break; + + case TokenStream.OR: + result.append(" || "); + break; + + case TokenStream.AND: + result.append(" && "); + break; + + case TokenStream.BITOR: + result.append(" | "); + break; + + case TokenStream.BITXOR: + result.append(" ^ "); + break; + + case TokenStream.BITAND: + result.append(" & "); + break; + + case TokenStream.EQOP: + i++; + switch(source.charAt(i)) { + case TokenStream.SHEQ: + /* + * Emulate the C engine; if we're under version + * 1.2, then the == operator behaves like the === + * operator (and the source is generated by + * decompiling a === opcode), so print the === + * operator as ==. + */ + result.append(this.version == Context.VERSION_1_2 ? " == " + : " === "); + break; + + case TokenStream.SHNE: + result.append(this.version == Context.VERSION_1_2 ? " != " + : " !== "); + break; + + case TokenStream.EQ: + result.append(" == "); + break; + + case TokenStream.NE: + result.append(" != "); + break; + } + break; + + case TokenStream.RELOP: + i++; + switch(source.charAt(i)) { + case TokenStream.LE: + result.append(" <= "); + break; + + case TokenStream.LT: + result.append(" < "); + break; + + case TokenStream.GE: + result.append(" >= "); + break; + + case TokenStream.GT: + result.append(" > "); + break; + + case TokenStream.INSTANCEOF: + result.append(" instanceof "); + break; + } + break; + + case TokenStream.SHOP: + i++; + switch(source.charAt(i)) { + case TokenStream.LSH: + result.append(" << "); + break; + + case TokenStream.RSH: + result.append(" >> "); + break; + + case TokenStream.URSH: + result.append(" >>> "); + break; + } + break; + + case TokenStream.UNARYOP: + i++; + switch(source.charAt(i)) { + case TokenStream.TYPEOF: + result.append("typeof "); + break; + + case TokenStream.VOID: + result.append("void "); + break; + + case TokenStream.NOT: + result.append('!'); + break; + + case TokenStream.BITNOT: + result.append('~'); + break; + + case TokenStream.ADD: + result.append('+'); + break; + + case TokenStream.SUB: + result.append('-'); + break; + } + break; + + case TokenStream.INC: + result.append("++"); + break; + + case TokenStream.DEC: + result.append("--"); + break; + + case TokenStream.ADD: + result.append(" + "); + break; + + case TokenStream.SUB: + result.append(" - "); + break; + + case TokenStream.MUL: + result.append(" * "); + break; + + case TokenStream.DIV: + result.append(" / "); + break; + + case TokenStream.MOD: + result.append(" % "); + break; + + default: + // If we don't know how to decompile it, raise an exception. + throw new RuntimeException("Unknown token " + + source.charAt(i)); + } + i++; + } + + // add that trailing newline if it's an outermost function. + if (toplevel && !justbody) + result.append('\n'); + } + + public int getLength() { + Context cx = Context.getContext(); + if (cx != null && cx.getLanguageVersion() != Context.VERSION_1_2) + return argCount; + NativeCall activation = getActivation(cx); + if (activation == null) + return argCount; + return activation.getOriginalArguments().length; + } + + public int getArity() { + return argCount; + } + + public String getFunctionName() { + if (functionName == null) + return ""; + if (functionName.equals("anonymous")) { + Context cx = Context.getCurrentContext(); + if (cx != null && cx.getLanguageVersion() == Context.VERSION_1_2) + return ""; + } + return functionName; + } + + /** + * For backwards compatibility keep an old method name used by + * Batik and possibly others. + */ + public String jsGet_name() { + return getFunctionName(); + } + + /** + * The "argsNames" array has the following information: + * argNames[0] through argNames[argCount - 1]: the names of the parameters + * argNames[argCount] through argNames[args.length-1]: the names of the + * variables declared in var statements + */ + protected String[] argNames; + protected short argCount; + protected short version; + + /** + * An encoded representation of the function source, for + * decompiling. Needs to be visible (only) to generated + * subclasses of NativeFunction. + */ + protected String source; + + /** + * An array of NativeFunction values for each nested function. + * Used internally, and also for decompiling nested functions. + */ + public NativeFunction[] nestedFunctions; + + // For all generated subclass objects debug_level is set to 0 or higher. + // So, if debug_level remains -1 in some object, then that object is + // known to have not been generated. + public int debug_level = -1; + public String debug_srcName; +} + diff --git a/src/org/mozilla/javascript/NativeGlobal.java b/src/org/mozilla/javascript/NativeGlobal.java new file mode 100644 index 0000000..6f7df59 --- /dev/null +++ b/src/org/mozilla/javascript/NativeGlobal.java @@ -0,0 +1,843 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * The contents of this file are subject to the Netscape Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express oqr + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is Rhino code, released + * May 6, 1999. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1997-1999 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * Igor Bukanov + * Mike McCabe + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (the "GPL"), in which case the + * provisions of the GPL are applicable instead of those above. + * If you wish to allow use of your version of this file only + * under the terms of the GPL and not to allow others to use your + * version of this file under the NPL, indicate your decision by + * deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete + * the provisions above, a recipient may use your version of this + * file under either the NPL or the GPL. + */ + +package org.mozilla.javascript; + +import java.io.StringReader; +import java.io.IOException; +import java.lang.reflect.Method; + +/** + * This class implements the global native object (function and value + * properties only). + * + * See ECMA 15.1.[12]. + * + * @author Mike Shaver + */ + +public class NativeGlobal implements IdFunctionMaster { + + public static void init(Context cx, Scriptable scope, boolean sealed) { + NativeGlobal obj = new NativeGlobal(); + obj.scopeSlaveFlag = true; + + for (int id = 1; id <= LAST_SCOPE_FUNCTION_ID; ++id) { + String name = getMethodName(id); + IdFunction f = new IdFunction(obj, name, id); + f.setParentScope(scope); + if (sealed) { f.sealObject(); } + ScriptableObject.defineProperty(scope, name, f, + ScriptableObject.DONTENUM); + } + + ScriptableObject.defineProperty(scope, "NaN", + ScriptRuntime.NaNobj, + ScriptableObject.DONTENUM); + ScriptableObject.defineProperty(scope, "Infinity", + new Double(Double.POSITIVE_INFINITY), + ScriptableObject.DONTENUM); + /* + ScriptableObject.defineProperty(scope, "undefined", + Undefined.instance, + ScriptableObject.DONTENUM); + */ + + String[] errorMethods = { "ConversionError", + "EvalError", + "RangeError", + "ReferenceError", + "SyntaxError", + "TypeError", + "URIError" + }; + + /* + Each error constructor gets its own Error object as a prototype, + with the 'name' property set to the name of the error. + */ + for (int i = 0; i < errorMethods.length; i++) { + String name = errorMethods[i]; + IdFunction ctor = new IdFunction(obj, name, Id_new_CommonError); + ctor.setFunctionType(IdFunction.FUNCTION_AND_CONSTRUCTOR); + ctor.setParentScope(scope); + ScriptableObject.defineProperty(scope, name, ctor, + ScriptableObject.DONTENUM); + + Scriptable errorProto = ScriptRuntime.newObject + (cx, scope, "Error", ScriptRuntime.emptyArgs); + + errorProto.put("name", errorProto, name); + ctor.put("prototype", ctor, errorProto); + if (sealed) { + ctor.sealObject(); + if (errorProto instanceof ScriptableObject) { + ((ScriptableObject)errorProto).sealObject(); + } + } + } + } + + public Object execMethod(int methodId, IdFunction function, Context cx, + Scriptable scope, Scriptable thisObj, + Object[] args) + throws JavaScriptException + { + if (scopeSlaveFlag) { + switch (methodId) { + case Id_decodeURI: + return js_decodeURI(cx, args); + + case Id_decodeURIComponent: + return js_decodeURIComponent(cx, args); + + case Id_encodeURI: + return js_encodeURI(cx, args); + + case Id_encodeURIComponent: + return js_encodeURIComponent(cx, args); + + case Id_escape: + return js_escape(cx, args); + + case Id_eval: + return js_eval(cx, scope, args); + + case Id_isFinite: + return js_isFinite(cx, args); + + case Id_isNaN: + return js_isNaN(cx, args); + + case Id_parseFloat: + return js_parseFloat(cx, args); + + case Id_parseInt: + return js_parseInt(cx, args); + + case Id_unescape: + return js_unescape(cx, args); + + case Id_new_CommonError: + return new_CommonError(function, cx, scope, args); + } + } + throw IdFunction.onBadMethodId(this, methodId); + } + + public int methodArity(int methodId) { + if (scopeSlaveFlag) { + switch (methodId) { + case Id_decodeURI: return 1; + case Id_decodeURIComponent: return 1; + case Id_encodeURI: return 1; + case Id_encodeURIComponent: return 1; + case Id_escape: return 1; + case Id_eval: return 1; + case Id_isFinite: return 1; + case Id_isNaN: return 1; + case Id_parseFloat: return 1; + case Id_parseInt: return 2; + case Id_unescape: return 1; + + case Id_new_CommonError: return 1; + } + } + return -1; + } + + private static String getMethodName(int methodId) { + switch (methodId) { + case Id_decodeURI: return "decodeURI"; + case Id_decodeURIComponent: return "decodeURIComponent"; + case Id_encodeURI: return "encodeURI"; + case Id_encodeURIComponent: return "encodeURIComponent"; + case Id_escape: return "escape"; + case Id_eval: return "eval"; + case Id_isFinite: return "isFinite"; + case Id_isNaN: return "isNaN"; + case Id_parseFloat: return "parseFloat"; + case Id_parseInt: return "parseInt"; + case Id_unescape: return "unescape"; + } + return null; + } + + /** + * The global method parseInt, as per ECMA-262 15.1.2.2. + */ + private Object js_parseInt(Context cx, Object[] args) { + String s = ScriptRuntime.toString(args, 0); + int radix = ScriptRuntime.toInt32(args, 1); + + int len = s.length(); + if (len == 0) + return ScriptRuntime.NaNobj; + + boolean negative = false; + int start = 0; + char c; + do { + c = s.charAt(start); + if (!Character.isWhitespace(c)) + break; + start++; + } while (start < len); + + if (c == '+' || (negative = (c == '-'))) + start++; + + final int NO_RADIX = -1; + if (radix == 0) { + radix = NO_RADIX; + } else if (radix < 2 || radix > 36) { + return ScriptRuntime.NaNobj; + } else if (radix == 16 && len - start > 1 && s.charAt(start) == '0') { + c = s.charAt(start+1); + if (c == 'x' || c == 'X') + start += 2; + } + + if (radix == NO_RADIX) { + radix = 10; + if (len - start > 1 && s.charAt(start) == '0') { + c = s.charAt(start+1); + if (c == 'x' || c == 'X') { + radix = 16; + start += 2; + } else if (c != '.') { + radix = 8; + start++; + } + } + } + + double d = ScriptRuntime.stringToNumber(s, start, radix); + return new Double(negative ? -d : d); + } + + /** + * The global method parseFloat, as per ECMA-262 15.1.2.3. + * + * @param cx unused + * @param thisObj unused + * @param args the arguments to parseFloat, ignoring args[>=1] + * @param funObj unused + */ + private Object js_parseFloat(Context cx, Object[] args) { + if (args.length < 1) + return ScriptRuntime.NaNobj; + String s = ScriptRuntime.toString(args[0]); + int len = s.length(); + if (len == 0) + return ScriptRuntime.NaNobj; + + int i; + char c; + // Scan forward to the first digit or . + for (i=0; TokenStream.isJSSpace(c = s.charAt(i)) && i+1 < len; i++) + /* empty */ + ; + + int start = i; + + if (c == '+' || c == '-') + c = s.charAt(++i); + + if (c == 'I') { + // check for "Infinity" + double d; + if (i+8 <= len && s.substring(i, i+8).equals("Infinity")) + d = s.charAt(start) == '-' ? Double.NEGATIVE_INFINITY + : Double.POSITIVE_INFINITY; + else + return ScriptRuntime.NaNobj; + return new Double(d); + } + + // Find the end of the legal bit + int decimal = -1; + int exponent = -1; + for (; i < len; i++) { + switch (s.charAt(i)) { + case '.': + if (decimal != -1) // Only allow a single decimal point. + break; + decimal = i; + continue; + + case 'e': + case 'E': + if (exponent != -1) + break; + exponent = i; + continue; + + case '+': + case '-': + // Only allow '+' or '-' after 'e' or 'E' + if (exponent != i-1) + break; + continue; + + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + continue; + + default: + break; + } + break; + } + s = s.substring(start, i); + try { + return Double.valueOf(s); + } + catch (NumberFormatException ex) { + return ScriptRuntime.NaNobj; + } + } + + /** + * The global method escape, as per ECMA-262 15.1.2.4. + + * Includes code for the 'mask' argument supported by the C escape + * method, which used to be part of the browser imbedding. Blame + * for the strange constant names should be directed there. + */ + + private Object js_escape(Context cx, Object[] args) { + final int + URL_XALPHAS = 1, + URL_XPALPHAS = 2, + URL_PATH = 4; + + String s = ScriptRuntime.toString(args, 0); + + int mask = URL_XALPHAS | URL_XPALPHAS | URL_PATH; + if (args.length > 1) { // the 'mask' argument. Non-ECMA. + double d = ScriptRuntime.toNumber(args[1]); + if (d != d || ((mask = (int) d) != d) || + 0 != (mask & ~(URL_XALPHAS | URL_XPALPHAS | URL_PATH))) + { + String message = Context.getMessage0("msg.bad.esc.mask"); + cx.reportError(message); + // do the ecma thing, in case reportError returns. + mask = URL_XALPHAS | URL_XPALPHAS | URL_PATH; + } + } + + StringBuffer R = new StringBuffer(); + for (int k = 0; k < s.length(); k++) { + int c = s.charAt(k), d; + if (mask != 0 && ((c >= '0' && c <= '9') || + (c >= 'A' && c <= 'Z') || + (c >= 'a' && c <= 'z') || + c == '@' || c == '*' || c == '_' || + c == '-' || c == '.' || + ((c == '/' || c == '+') && mask > 3))) + R.append((char)c); + else if (c < 256) { + if (c == ' ' && mask == URL_XPALPHAS) { + R.append('+'); + } else { + R.append('%'); + R.append(hex_digit_to_char(c >>> 4)); + R.append(hex_digit_to_char(c & 0xF)); + } + } else { + R.append('%'); + R.append('u'); + R.append(hex_digit_to_char(c >>> 12)); + R.append(hex_digit_to_char((c & 0xF00) >>> 8)); + R.append(hex_digit_to_char((c & 0xF0) >>> 4)); + R.append(hex_digit_to_char(c & 0xF)); + } + } + return R.toString(); + } + + private static char hex_digit_to_char(int x) { + return (char)(x <= 9 ? x + '0' : x + ('A' - 10)); + } + + /** + * The global unescape method, as per ECMA-262 15.1.2.5. + */ + + private Object js_unescape(Context cx, Object[] args) + { + String s = ScriptRuntime.toString(args, 0); + int firstEscapePos = s.indexOf('%'); + if (firstEscapePos >= 0) { + int L = s.length(); + char[] buf = s.toCharArray(); + int destination = firstEscapePos; + for (int k = firstEscapePos; k != L;) { + char c = buf[k]; + ++k; + if (c == '%' && k != L) { + int end, start; + if (buf[k] == 'u') { + start = k + 1; + end = k + 5; + } else { + start = k; + end = k + 2; + } + if (end <= L) { + int x = 0; + for (int i = start; i != end; ++i) { + x = (x << 4) | TokenStream.xDigitToInt(buf[i]); + } + if (x >= 0) { + c = (char)x; + k = end; + } + } + } + buf[destination] = c; + ++destination; + } + s = new String(buf, 0, destination); + } + return s; + } + + /** + * The global method isNaN, as per ECMA-262 15.1.2.6. + */ + + private Object js_isNaN(Context cx, Object[] args) { + if (args.length < 1) + return Boolean.TRUE; + double d = ScriptRuntime.toNumber(args[0]); + return (d != d) ? Boolean.TRUE : Boolean.FALSE; + } + + private Object js_isFinite(Context cx, Object[] args) { + if (args.length < 1) + return Boolean.FALSE; + double d = ScriptRuntime.toNumber(args[0]); + return (d != d || d == Double.POSITIVE_INFINITY || + d == Double.NEGATIVE_INFINITY) + ? Boolean.FALSE + : Boolean.TRUE; + } + + private Object js_eval(Context cx, Scriptable scope, Object[] args) + throws JavaScriptException + { + String m = ScriptRuntime.getMessage1("msg.cant.call.indirect", "eval"); + throw NativeGlobal.constructError(cx, "EvalError", m, scope); + } + + /** + * The eval function property of the global object. + * + * See ECMA 15.1.2.1 + */ + public static Object evalSpecial(Context cx, Scriptable scope, + Object thisArg, Object[] args, + String filename, int lineNumber) + throws JavaScriptException + { + throw NativeGlobal.constructError(cx, "EvalError", "XWT does not allow eval()", scope); + /* + if (args.length < 1) + return Undefined.instance; + Object x = args[0]; + if (!(x instanceof String)) { + String message = Context.getMessage0("msg.eval.nonstring"); + Context.reportWarning(message); + return x; + } + int[] linep = { lineNumber }; + if (filename == null) { + filename = Context.getSourcePositionFromStack(linep); + if (filename == null) { + filename = ""; + linep[0] = 1; + } + } + filename += "(eval)"; + + try { + StringReader in = new StringReader((String) x); + Object securityDomain = cx.getSecurityDomainForStackDepth(3); + + // Compile the reader with opt level of -1 to force interpreter + // mode. + int oldOptLevel = cx.getOptimizationLevel(); + cx.setOptimizationLevel(-1); + Script script = cx.compileReader(scope, in, filename, linep[0], + securityDomain); + cx.setOptimizationLevel(oldOptLevel); + + // if the compile fails, an error has been reported by the + // compiler, but we need to stop execution to avoid + // infinite looping on while(true) { eval('foo bar') } - + // so we throw an EvaluatorException. + if (script == null) { + String message = Context.getMessage0("msg.syntax"); + throw new EvaluatorException(message); + } + + InterpretedScript is = (InterpretedScript) script; + is.itsData.itsFromEvalCode = true; + Object result = is.call(cx, scope, (Scriptable) thisArg, null); + + return result; + } + catch (IOException ioe) { + // should never happen since we just made the Reader from a String + throw new RuntimeException("unexpected io exception"); + } + */ + } + + + /** + * The NativeError functions + * + * See ECMA 15.11.6 + */ + public static EcmaError constructError(Context cx, + String error, + String message, + Object scope) + { + int[] linep = { 0 }; + String filename = cx.getSourcePositionFromStack(linep); + return constructError(cx, error, message, scope, + filename, linep[0], 0, null); + } + + static EcmaError typeError0(String messageId, Object scope) { + return constructError(Context.getContext(), "TypeError", + ScriptRuntime.getMessage0(messageId), scope); + } + + static EcmaError typeError1(String messageId, Object arg1, Object scope) { + return constructError(Context.getContext(), "TypeError", + ScriptRuntime.getMessage1(messageId, arg1), scope); + } + + /** + * The NativeError functions + * + * See ECMA 15.11.6 + */ + public static EcmaError constructError(Context cx, + String error, + String message, + Object scope, + String sourceName, + int lineNumber, + int columnNumber, + String lineSource) + { + Scriptable scopeObject; + try { + scopeObject = (Scriptable) scope; + } + catch (ClassCastException x) { + throw new RuntimeException(x.toString()); + } + + Object args[] = { message }; + try { + Object errorObject = cx.newObject(scopeObject, error, args); + return new EcmaError((NativeError)errorObject, sourceName, + lineNumber, columnNumber, lineSource); + } + catch (PropertyException x) { + throw new RuntimeException(x.toString()); + } + catch (JavaScriptException x) { + throw new RuntimeException(x.toString()); + } + catch (NotAFunctionException x) { + throw new RuntimeException(x.toString()); + } + } + + /** + * The implementation of all the ECMA error constructors (SyntaxError, + * TypeError, etc.) + */ + private Object new_CommonError(IdFunction ctorObj, Context cx, + Scriptable scope, Object[] args) + { + Scriptable newInstance = new NativeError(); + newInstance.setPrototype((Scriptable)(ctorObj.get("prototype", ctorObj))); + newInstance.setParentScope(scope); + if (args.length > 0) + newInstance.put("message", newInstance, args[0]); + return newInstance; + } + + /* + * ECMA 3, 15.1.3 URI Handling Function Properties + * + * The following are implementations of the algorithms + * given in the ECMA specification for the hidden functions + * 'Encode' and 'Decode'. + */ + private static String encode(Context cx, String str, String unescapedSet) { + int j, k = 0, L; + char C, C2; + int V; + char utf8buf[] = new char[6]; + StringBuffer R; + + R = new StringBuffer(); + + while (k < str.length()) { + C = str.charAt(k); + if (unescapedSet.indexOf(C) != -1) { + R.append(C); + } else { + if ((C >= 0xDC00) && (C <= 0xDFFF)) { + throw cx.reportRuntimeError0("msg.bad.uri"); + } + if ((C < 0xD800) || (C > 0xDBFF)) + V = C; + else { + k++; + if (k == str.length()) { + throw cx.reportRuntimeError0("msg.bad.uri"); + } + C2 = str.charAt(k); + if ((C2 < 0xDC00) || (C2 > 0xDFFF)) { + throw cx.reportRuntimeError0("msg.bad.uri"); + } + V = ((C - 0xD800) << 10) + (C2 - 0xDC00) + 0x10000; + } + L = oneUcs4ToUtf8Char(utf8buf, V); + for (j = 0; j < L; j++) { + R.append('%'); + if (utf8buf[j] < 16) + R.append('0'); + R.append(Integer.toHexString(utf8buf[j])); + } + } + k++; + } + return R.toString(); + } + + private static boolean isHex(char c) { + return ((c >= '0' && c <= '9') + || (c >= 'a' && c <= 'f') + || (c >= 'A' && c <= 'F')); + } + + private static int unHex(char c) { + if (c >= '0' && c <= '9') + return c - '0'; + else + if (c >= 'a' && c <= 'f') + return c - 'a' + 10; + else + return c - 'A' +10; + } + + private static String decode(Context cx, String str, String reservedSet) { + int start, k = 0; + char C, H; + int V; + int B; + char[] octets = new char[6]; + StringBuffer R; + int j, n; + + R = new StringBuffer(); + + while (k < str.length()) { + C = str.charAt(k); + if (C == '%') { + start = k; + if ((k + 2) >= str.length()) + throw cx.reportRuntimeError0("msg.bad.uri"); + if (!isHex(str.charAt(k + 1)) || !isHex(str.charAt(k + 2))) + throw cx.reportRuntimeError0("msg.bad.uri"); + B = unHex(str.charAt(k + 1)) * 16 + unHex(str.charAt(k + 2)); + k += 2; + if ((B & 0x80) == 0) + C = (char)B; + else { + n = 1; + while ((B & (0x80 >>> n)) != 0) n++; + if ((n == 1) || (n > 6)) + throw cx.reportRuntimeError0("msg.bad.uri"); + octets[0] = (char)B; + if ((k + 3 * (n - 1)) >= str.length()) + throw cx.reportRuntimeError0("msg.bad.uri"); + for (j = 1; j < n; j++) { + k++; + if (str.charAt(k) != '%') + throw cx.reportRuntimeError0("msg.bad.uri"); + if (!isHex(str.charAt(k + 1)) + || !isHex(str.charAt(k + 2))) + throw cx.reportRuntimeError0("msg.bad.uri"); + B = unHex(str.charAt(k + 1)) * 16 + + unHex(str.charAt(k + 2)); + if ((B & 0xC0) != 0x80) + throw cx.reportRuntimeError0("msg.bad.uri"); + k += 2; + octets[j] = (char)B; + } + V = utf8ToOneUcs4Char(octets, n); + if (V >= 0x10000) { + V -= 0x10000; + if (V > 0xFFFFF) + throw cx.reportRuntimeError0("msg.bad.uri"); + C = (char)((V & 0x3FF) + 0xDC00); + H = (char)((V >>> 10) + 0xD800); + R.append(H); + } + else + C = (char)V; + } + if (reservedSet.indexOf(C) != -1) { + for (int x = 0; x < (k - start + 1); x++) + R.append(str.charAt(start + x)); + } + else + R.append(C); + } + else + R.append(C); + k++; + } + return R.toString(); + } + + private static String uriReservedPlusPound = ";/?:@&=+$,#"; + private static String uriUnescaped = + "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_.!~*'()"; + + private String js_decodeURI(Context cx, Object[] args) { + String str = ScriptRuntime.toString(args, 0); + return decode(cx, str, uriReservedPlusPound); + } + + private String js_decodeURIComponent(Context cx, Object[] args) { + String str = ScriptRuntime.toString(args, 0); + return decode(cx, str, ""); + } + + private Object js_encodeURI(Context cx, Object[] args) { + String str = ScriptRuntime.toString(args, 0); + return encode(cx, str, uriReservedPlusPound + uriUnescaped); + } + + private String js_encodeURIComponent(Context cx, Object[] args) { + String str = ScriptRuntime.toString(args, 0); + return encode(cx, str, uriUnescaped); + } + + /* Convert one UCS-4 char and write it into a UTF-8 buffer, which must be + * at least 6 bytes long. Return the number of UTF-8 bytes of data written. + */ + private static int oneUcs4ToUtf8Char(char[] utf8Buffer, int ucs4Char) { + int utf8Length = 1; + + //JS_ASSERT(ucs4Char <= 0x7FFFFFFF); + if ((ucs4Char & ~0x7F) == 0) + utf8Buffer[0] = (char)ucs4Char; + else { + int i; + int a = ucs4Char >>> 11; + utf8Length = 2; + while (a != 0) { + a >>>= 5; + utf8Length++; + } + i = utf8Length; + while (--i > 0) { + utf8Buffer[i] = (char)((ucs4Char & 0x3F) | 0x80); + ucs4Char >>>= 6; + } + utf8Buffer[0] = (char)(0x100 - (1 << (8-utf8Length)) + ucs4Char); + } + return utf8Length; + } + + + /* Convert a utf8 character sequence into a UCS-4 character and return that + * character. It is assumed that the caller already checked that the sequence is valid. + */ + private static int utf8ToOneUcs4Char(char[] utf8Buffer, int utf8Length) { + int ucs4Char; + int k = 0; + //JS_ASSERT(utf8Length >= 1 && utf8Length <= 6); + if (utf8Length == 1) { + ucs4Char = utf8Buffer[0]; + // JS_ASSERT(!(ucs4Char & 0x80)); + } else { + //JS_ASSERT((*utf8Buffer & (0x100 - (1 << (7-utf8Length)))) == (0x100 - (1 << (8-utf8Length)))); + ucs4Char = utf8Buffer[k++] & ((1<<(7-utf8Length))-1); + while (--utf8Length > 0) { + //JS_ASSERT((*utf8Buffer & 0xC0) == 0x80); + ucs4Char = ucs4Char<<6 | (utf8Buffer[k++] & 0x3F); + } + } + return ucs4Char; + } + + private static final int + Id_decodeURI = 1, + Id_decodeURIComponent = 2, + Id_encodeURI = 3, + Id_encodeURIComponent = 4, + Id_escape = 5, + Id_eval = 6, + Id_isFinite = 7, + Id_isNaN = 8, + Id_parseFloat = 9, + Id_parseInt = 10, + Id_unescape = 11, + + LAST_SCOPE_FUNCTION_ID = 11, + + Id_new_CommonError = 12; + + private boolean scopeSlaveFlag; + +} diff --git a/src/org/mozilla/javascript/NativeJavaArray.java b/src/org/mozilla/javascript/NativeJavaArray.java new file mode 100644 index 0000000..42e2d2e --- /dev/null +++ b/src/org/mozilla/javascript/NativeJavaArray.java @@ -0,0 +1,155 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * The contents of this file are subject to the Netscape Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express oqr + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is Rhino code, released + * May 6, 1999. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1997-1999 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * Frank Mitchell + * Mike Shaver + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (the "GPL"), in which case the + * provisions of the GPL are applicable instead of those above. + * If you wish to allow use of your version of this file only + * under the terms of the GPL and not to allow others to use your + * version of this file under the NPL, indicate your decision by + * deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete + * the provisions above, a recipient may use your version of this + * file under either the NPL or the GPL. + */ + +package org.mozilla.javascript; + +import java.lang.reflect.Array; + +/** + * This class reflects Java arrays into the JavaScript environment. + * + * @author Mike Shaver + * @see NativeJavaClass + * @see NativeJavaObject + * @see NativeJavaPackage + */ + +public class NativeJavaArray extends NativeJavaObject { + + public String getClassName() { + return "JavaArray"; + } + + public static NativeJavaArray wrap(Scriptable scope, Object array) { + return new NativeJavaArray(scope, array); + } + + public Object unwrap() { + return array; + } + + public NativeJavaArray(Scriptable scope, Object array) { + super(scope, null, ScriptRuntime.ObjectClass); + Class cl = array.getClass(); + if (!cl.isArray()) { + throw new RuntimeException("Array expected"); + } + this.array = array; + this.length = Array.getLength(array); + this.cls = cl.getComponentType(); + } + + public boolean has(String id, Scriptable start) { + return id.equals("length") || super.has(id, start); + } + + public boolean has(int index, Scriptable start) { + return 0 <= index && index < length; + } + + public Object get(String id, Scriptable start) { + if (id.equals("length")) + return new Integer(length); + Object result = super.get(id, start); + if (result == NOT_FOUND && + !ScriptRuntime.hasProp(getPrototype(), id)) + { + throw Context.reportRuntimeError2( + "msg.java.member.not.found", array.getClass().getName(), id); + } + return result; + } + + public Object get(int index, Scriptable start) { + if (0 <= index && index < length) + return NativeJavaObject.wrap(this, Array.get(array, index), cls); + return Undefined.instance; + } + + public void put(String id, Scriptable start, Object value) { + // Ignore assignments to "length"--it's readonly. + if (!id.equals("length")) + super.put(id, start, value); + } + + public void put(int index, Scriptable start, Object value) { + if (0 <= index && index < length) { + Array.set(array, index, NativeJavaObject.coerceType(cls, value)); + return; + } + super.put(index, start, value); + } + + public Object getDefaultValue(Class hint) { + if (hint == null || hint == ScriptRuntime.StringClass) + return array.toString(); + if (hint == ScriptRuntime.BooleanClass) + return Boolean.TRUE; + if (hint == ScriptRuntime.NumberClass) + return ScriptRuntime.NaNobj; + return this; + } + + public Object[] getIds() { + Object[] result = new Object[length]; + int i = length; + while (--i >= 0) + result[i] = new Integer(i); + return result; + } + + public boolean hasInstance(Scriptable value) { + if (!(value instanceof Wrapper)) + return false; + Object instance = ((Wrapper)value).unwrap(); + return cls.isInstance(instance); + } + + public Scriptable getPrototype() { + if (prototype == null) { + prototype = + ScriptableObject.getClassPrototype(this.getParentScope(), + "Array"); + } + return prototype; + } + + Object array; + int length; + Class cls; + Scriptable prototype; +} diff --git a/src/org/mozilla/javascript/NativeJavaClass.java b/src/org/mozilla/javascript/NativeJavaClass.java new file mode 100644 index 0000000..8b46c94 --- /dev/null +++ b/src/org/mozilla/javascript/NativeJavaClass.java @@ -0,0 +1,275 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * The contents of this file are subject to the Netscape Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express oqr + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is Rhino code, released + * May 6, 1999. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1997-1999 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * Frank Mitchell + * Mike Shaver + * Kurt Westerfeld + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (the "GPL"), in which case the + * provisions of the GPL are applicable instead of those above. + * If you wish to allow use of your version of this file only + * under the terms of the GPL and not to allow others to use your + * version of this file under the NPL, indicate your decision by + * deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete + * the provisions above, a recipient may use your version of this + * file under either the NPL or the GPL. + */ + +package org.mozilla.javascript; + +import java.lang.reflect.*; +import java.util.Hashtable; + +/** + * This class reflects Java classes into the JavaScript environment, mainly + * for constructors and static members. We lazily reflect properties, + * and currently do not guarantee that a single j.l.Class is only + * reflected once into the JS environment, although we should. + * The only known case where multiple reflections + * are possible occurs when a j.l.Class is wrapped as part of a + * method return or property access, rather than by walking the + * Packages/java tree. + * + * @author Mike Shaver + * @see NativeJavaArray + * @see NativeJavaObject + * @see NativeJavaPackage + */ + +public class NativeJavaClass extends NativeJavaObject implements Function { + + public NativeJavaClass(Scriptable scope, Class cl) { + super(scope, cl, JavaMembers.lookupClass(scope, cl, cl)); + fieldAndMethods = members.getFieldAndMethodsObjects(this, javaObject, + true); + } + + public String getClassName() { + return "JavaClass"; + } + + public boolean has(String name, Scriptable start) { + return members.has(name, true); + } + + public Object get(String name, Scriptable start) { + // When used as a constructor, ScriptRuntime.newObject() asks + // for our prototype to create an object of the correct type. + // We don't really care what the object is, since we're returning + // one constructed out of whole cloth, so we return null. + + if (name.equals("prototype")) + return null; + + Object result = Scriptable.NOT_FOUND; + + if (fieldAndMethods != null) { + result = fieldAndMethods.get(name); + if (result != null) + return result; + } + + if (members.has(name, true)) { + result = members.get(this, name, javaObject, true); + } else { + // experimental: look for nested classes by appending $name to current class' name. + try { + String nestedName = getClassObject().getName() + '$' + name; + Class nestedClass = ScriptRuntime.loadClassName(nestedName); + Scriptable nestedValue = wrap(ScriptableObject.getTopLevelScope(this), nestedClass); + nestedValue.setParentScope(this); + result = nestedValue; + } catch (ClassNotFoundException ex) { + throw members.reportMemberNotFound(name); + } catch (IllegalArgumentException e) { + throw members.reportMemberNotFound(name); + } + } + + return result; + } + + public void put(String name, Scriptable start, Object value) { + members.put(this, name, javaObject, value, true); + } + + public Object[] getIds() { + return members.getIds(true); + } + + public Class getClassObject() { + return (Class) super.unwrap(); + } + + // XXX ?? + public static NativeJavaClass wrap(Scriptable scope, Class cls) { + return new NativeJavaClass(scope, cls); + } + + public Object getDefaultValue(Class hint) { + if (hint == null || hint == ScriptRuntime.StringClass) + return this.toString(); + if (hint == ScriptRuntime.BooleanClass) + return Boolean.TRUE; + if (hint == ScriptRuntime.NumberClass) + return ScriptRuntime.NaNobj; + return this; + } + + public Object call(Context cx, Scriptable scope, Scriptable thisObj, + Object[] args) + throws JavaScriptException + { + // If it looks like a "cast" of an object to this class type, + // walk the prototype chain to see if there's a wrapper of a + // object that's an instanceof this class. + if (args.length == 1 && args[0] instanceof Scriptable) { + Class c = getClassObject(); + Scriptable p = (Scriptable) args[0]; + do { + if (p instanceof Wrapper) { + Object o = ((Wrapper) p).unwrap(); + if (c.isInstance(o)) + return p; + } + p = p.getPrototype(); + } while (p != null); + } + return construct(cx, scope, args); + } + + public Scriptable construct(Context cx, Scriptable scope, Object[] args) + throws JavaScriptException + { + Class classObject = getClassObject(); + int modifiers = classObject.getModifiers(); + if (! (Modifier.isInterface(modifiers) || + Modifier.isAbstract(modifiers))) + { + Constructor[] ctors = members.getConstructors(); + Member member = NativeJavaMethod.findFunction(ctors, args); + Constructor ctor = (Constructor) member; + if (ctor == null) { + String sig = NativeJavaMethod.scriptSignature(args); + throw Context.reportRuntimeError2( + "msg.no.java.ctor", classObject.getName(), sig); + } + + // Found the constructor, so try invoking it. + return NativeJavaClass.constructSpecific(cx, scope, + this, ctor, args); + } else { + Scriptable topLevel = ScriptableObject.getTopLevelScope(this); + String msg = ""; + try { + // trying to construct an interface; use JavaAdapter to + // construct a new class on the fly that implements this + // interface. + Object v = topLevel.get("JavaAdapter", topLevel); + if (v != NOT_FOUND) { + Function f = (Function) v; + Object[] adapterArgs = { this, args[0] }; + return (Scriptable) f.construct(cx, topLevel, + adapterArgs); + } + } catch (Exception ex) { + // fall through to error + String m = ex.getMessage(); + if (m != null) + msg = m; + } + throw Context.reportRuntimeError2( + "msg.cant.instantiate", msg, classObject.getName()); + } + } + + public static Scriptable constructSpecific(Context cx, + Scriptable scope, + Scriptable thisObj, + Constructor ctor, + Object[] args) + throws JavaScriptException + { + Scriptable topLevel = ScriptableObject.getTopLevelScope(thisObj); + Class classObject = ctor.getDeclaringClass(); + + Class[] paramTypes = ctor.getParameterTypes(); + for (int i = 0; i < args.length; i++) { + args[i] = NativeJavaObject.coerceType(paramTypes[i], args[i]); + } + try { + // we need to force this to be wrapped, because construct _has_ + // to return a scriptable + return + (Scriptable) NativeJavaObject.wrap(topLevel, + ctor.newInstance(args), + classObject); + + } catch (InstantiationException instEx) { + throw Context.reportRuntimeError2( + "msg.cant.instantiate", + instEx.getMessage(), classObject.getName()); + } catch (IllegalArgumentException argEx) { + String signature = NativeJavaMethod.scriptSignature(args); + String ctorString = ctor.toString(); + throw Context.reportRuntimeError3( + "msg.bad.ctor.sig", argEx.getMessage(), ctorString, signature); + } catch (InvocationTargetException e) { + throw JavaScriptException.wrapException(scope, e); + } catch (IllegalAccessException accessEx) { + throw Context.reportRuntimeError1( + "msg.java.internal.private", accessEx.getMessage()); + } + } + + public String toString() { + return "[JavaClass " + getClassObject().getName() + "]"; + } + + /** + * Determines if prototype is a wrapped Java object and performs + * a Java "instanceof". + * Exception: if value is an instance of NativeJavaClass, it isn't + * considered an instance of the Java class; this forestalls any + * name conflicts between java.lang.Class's methods and the + * static methods exposed by a JavaNativeClass. + */ + public boolean hasInstance(Scriptable value) { + + if (value instanceof Wrapper && + !(value instanceof NativeJavaClass)) { + Object instance = ((Wrapper)value).unwrap(); + + return getClassObject().isInstance(instance); + } + + // value wasn't something we understand + return false; + } + + private Hashtable fieldAndMethods; + + // beard: need a scope for finding top-level prototypes. + private Scriptable parent; +} diff --git a/src/org/mozilla/javascript/NativeJavaConstructor.java b/src/org/mozilla/javascript/NativeJavaConstructor.java new file mode 100644 index 0000000..4f2fbfc --- /dev/null +++ b/src/org/mozilla/javascript/NativeJavaConstructor.java @@ -0,0 +1,86 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * The contents of this file are subject to the Netscape Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express oqr + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is Rhino code, released + * May 6, 1999. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1997-1999 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * Frank Mitchell + * Mike Shaver + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (the "GPL"), in which case the + * provisions of the GPL are applicable instead of those above. + * If you wish to allow use of your version of this file only + * under the terms of the GPL and not to allow others to use your + * version of this file under the NPL, indicate your decision by + * deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete + * the provisions above, a recipient may use your version of this + * file under either the NPL or the GPL. + */ + +package org.mozilla.javascript; + +import java.lang.reflect.*; + +/** + * This class reflects a single Java constructor into the JavaScript + * environment. It satisfies a request for an overloaded constructor, + * as introduced in LiveConnect 3. + * All NativeJavaConstructors behave as JSRef `bound' methods, in that they + * always construct the same NativeJavaClass regardless of any reparenting + * that may occur. + * + * @author Frank Mitchell + * @see NativeJavaMethod + * @see NativeJavaPackage + * @see NativeJavaClass + */ + +public class NativeJavaConstructor extends NativeFunction implements Function { + + public NativeJavaConstructor(Constructor ctor) { + this.constructor = ctor; + this.functionName = "" + NativeJavaMethod.signature(ctor); + } + + public Object call(Context cx, Scriptable scope, Scriptable thisObj, + Object[] args) + throws JavaScriptException + { + // Find a method that matches the types given. + if (constructor == null) { + throw new RuntimeException("No constructor defined for call"); + } + + return NativeJavaClass.constructSpecific(cx, scope, + this, constructor, args); + } + + public String toString() { + return "[JavaConstructor " + constructor.getName() + "]"; + } + + Constructor getConstructor() { + return constructor; + } + + Constructor constructor; +} + diff --git a/src/org/mozilla/javascript/NativeJavaMethod.java b/src/org/mozilla/javascript/NativeJavaMethod.java new file mode 100644 index 0000000..8d3e88d --- /dev/null +++ b/src/org/mozilla/javascript/NativeJavaMethod.java @@ -0,0 +1,517 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * The contents of this file are subject to the Netscape Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express oqr + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is Rhino code, released + * May 6, 1999. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1997-1999 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * Frank Mitchell + * Mike Shaver + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (the "GPL"), in which case the + * provisions of the GPL are applicable instead of those above. + * If you wish to allow use of your version of this file only + * under the terms of the GPL and not to allow others to use your + * version of this file under the NPL, indicate your decision by + * deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete + * the provisions above, a recipient may use your version of this + * file under either the NPL or the GPL. + */ + +package org.mozilla.javascript; + +import java.lang.reflect.*; + +/** + * This class reflects Java methods into the JavaScript environment. It + * handles overloading of methods, and method/field name conflicts. + * All NativeJavaMethods behave as JSRef `bound' methods, in that they + * always operate on the object underlying the original NativeJavaObject + * parent regardless of any reparenting that may occur. + * + * @author Mike Shaver + * @see NativeJavaArray + * @see NativeJavaPackage + * @see NativeJavaClass + */ + +public class NativeJavaMethod extends NativeFunction implements Function { + + public NativeJavaMethod() { + this.functionName = null; + } + + public NativeJavaMethod(Method[] methods) { + this.methods = methods; + this.functionName = methods[0].getName(); + } + + public NativeJavaMethod(Method method, String name) { + this.methods = new Method[1]; + this.methods[0] = method; + this.functionName = name; + } + + public void add(Method method) { + if (functionName == null) { + functionName = method.getName(); + } else if (!functionName.equals(method.getName())) { + throw new RuntimeException("internal method name mismatch"); + } + // XXX a more intelligent growth algorithm would be nice + int len = methods == null ? 0 : methods.length; + Method[] newMeths = new Method[len + 1]; + for (int i = 0; i < len; i++) + newMeths[i] = methods[i]; + newMeths[len] = method; + methods = newMeths; + } + + static String scriptSignature(Object value) { + if (value == null) { + return "null"; + } + else { + Class type = value.getClass(); + if (type == ScriptRuntime.UndefinedClass) + return "undefined"; + if (type == ScriptRuntime.BooleanClass) + return "boolean"; + if (type == ScriptRuntime.StringClass) + return "string"; + if (ScriptRuntime.NumberClass.isAssignableFrom(type)) + return "number"; + if (value instanceof Wrapper) { + return ((Wrapper)value).unwrap().getClass().getName(); + } + if (value instanceof Scriptable) { + if (value instanceof Function) + return "function"; + return "object"; + } + return javaSignature(type); + } + } + + static String scriptSignature(Object[] values) { + StringBuffer sig = new StringBuffer(); + for (int i = 0; i < values.length; i++) { + if (i != 0) + sig.append(','); + sig.append(scriptSignature(values[i])); + } + return sig.toString(); + } + + static String javaSignature(Class type) { + if (type == null) { + return "null"; + } + else if (type.isArray()) { + return javaSignature(type.getComponentType()) + "[]"; + } + return type.getName(); + } + + static String javaSignature(Class[] types) { + StringBuffer sig = new StringBuffer(); + for (int i = 0; i < types.length; i++) { + if (i != 0) + sig.append(','); + sig.append(javaSignature(types[i])); + } + return sig.toString(); + } + + static String signature(Member member) { + Class paramTypes[]; + + if (member instanceof Method) { + paramTypes = ((Method) member).getParameterTypes(); + return member.getName() + "(" + javaSignature(paramTypes) + ")"; + } + else { + paramTypes = ((Constructor) member).getParameterTypes(); + return "(" + javaSignature(paramTypes) + ")"; + } + } + + public String decompile(Context cx, int indent, boolean justbody) { + StringBuffer sb = new StringBuffer(); + if (!justbody) { + sb.append("function "); + sb.append(getFunctionName()); + sb.append("() {"); + } + sb.append("/*\n"); + toString(sb); + sb.append(justbody ? "*/\n" : "*/}\n"); + return sb.toString(); + } + + public String toString() { + StringBuffer sb = new StringBuffer(); + toString(sb); + return sb.toString(); + } + + private void toString(StringBuffer sb) { + for (int i=0; i < methods.length; i++) { + sb.append(javaSignature(methods[i].getReturnType())); + sb.append(' '); + sb.append(signature(methods[i])); + sb.append('\n'); + } + } + + public Object call(Context cx, Scriptable scope, Scriptable thisObj, + Object[] args) + throws JavaScriptException + { + // Find a method that matches the types given. + if (methods.length == 0) { + throw new RuntimeException("No methods defined for call"); + } + + Method meth = (Method) findFunction(methods, args); + if (meth == null) { + Class c = methods[0].getDeclaringClass(); + String sig = c.getName() + "." + functionName + "(" + + scriptSignature(args) + ")"; + throw Context.reportRuntimeError1("msg.java.no_such_method", sig); + } + + // OPT: already retrieved in findFunction, so we should inline that + // OPT: or pass it back somehow + Class paramTypes[] = meth.getParameterTypes(); + + // First, we marshall the args. + for (int i = 0; i < args.length; i++) { + args[i] = NativeJavaObject.coerceType(paramTypes[i], args[i]); + } + Object javaObject; + if (Modifier.isStatic(meth.getModifiers())) { + javaObject = null; // don't need an object + } else { + Scriptable o = thisObj; + while (!(o instanceof Wrapper)) { + o = o.getPrototype(); + if (o == null) { + throw Context.reportRuntimeError1( + "msg.nonjava.method", functionName); + } + } + javaObject = ((Wrapper) o).unwrap(); + } + try { + if (debug) { + printDebug("Calling ", meth, args); + } + + Object retval = meth.invoke(javaObject, args); + Class staticType = meth.getReturnType(); + + if (debug) { + Class actualType = (retval == null) ? null : retval.getClass(); + System.err.println(" ----- Returned " + retval + + " actual = " + actualType + + " expect = " + staticType); + } + + Object wrapped = NativeJavaObject.wrap(scope, retval, staticType); + + if (debug) { + Class actualType = (wrapped == null) ? null : wrapped.getClass(); + System.err.println(" ----- Wrapped as " + wrapped + + " class = " + actualType); + } + + if (wrapped == Undefined.instance) + return wrapped; + if (wrapped == null && staticType == Void.TYPE) + return Undefined.instance; + return wrapped; + } catch (IllegalAccessException accessEx) { + throw Context.reportRuntimeError(accessEx.getMessage()); + } catch (InvocationTargetException e) { + throw JavaScriptException.wrapException(scope, e); + } + } + + /** + * Find the correct function to call given the set of methods + * or constructors and the arguments. + * If no function can be found to call, return null. + */ + static Member findFunction(Member[] methodsOrCtors, Object[] args) { + if (methodsOrCtors.length == 0) + return null; + boolean hasMethods = methodsOrCtors[0] instanceof Method; + if (Context.useJSObject && + NativeJavaObject.jsObjectClass != null) + { + try { + for (int i = 0; i < args.length; i++) { + if (NativeJavaObject.jsObjectClass.isInstance(args[i])) + args[i] = NativeJavaObject.jsObjectGetScriptable.invoke( + args[i], ScriptRuntime.emptyArgs); + } + } + catch (InvocationTargetException e) { + // Just abandon conversion from JSObject + } + catch (IllegalAccessException e) { + // Just abandon conversion from JSObject + } + } + + Member bestFit = null; + Class[] bestFitTypes = null; + + java.util.Vector ambiguousMethods = null; + + for (int i = 0; i < methodsOrCtors.length; i++) { + Member member = methodsOrCtors[i]; + Class paramTypes[] = hasMethods + ? ((Method) member).getParameterTypes() + : ((Constructor) member).getParameterTypes(); + if (paramTypes.length != args.length) { + continue; + } + if (bestFitTypes == null) { + int j; + for (j = 0; j < paramTypes.length; j++) { + if (!NativeJavaObject.canConvert(args[j], paramTypes[j])) { + if (debug) printDebug("Rejecting (args can't convert) ", + member, args); + break; + } + } + if (j == paramTypes.length) { + if (debug) printDebug("Found ", member, args); + bestFit = member; + bestFitTypes = paramTypes; + } + } + else { + int preference = + NativeJavaMethod.preferSignature(args, + paramTypes, + bestFitTypes); + if (preference == PREFERENCE_AMBIGUOUS) { + if (debug) printDebug("Deferring ", member, args); + // add to "ambiguity list" + if (ambiguousMethods == null) + ambiguousMethods = new java.util.Vector(); + ambiguousMethods.addElement(member); + } + else if (preference == PREFERENCE_FIRST_ARG) { + if (debug) printDebug("Substituting ", member, args); + bestFit = member; + bestFitTypes = paramTypes; + } + else { + if (preference == PREFERENCE_EQUAL && + Modifier.isStatic(bestFit.getModifiers()) && + bestFit.getDeclaringClass().isAssignableFrom( + member.getDeclaringClass())) + { + // On some JVMs, Class.getMethods will return all + // static methods of the class heirarchy, even if + // a derived class's parameters match exactly. + // We want to call the dervied class's method. + if (debug) printDebug("Rejecting (overridden static)", + member, args); + bestFit = member; + bestFitTypes = paramTypes; + } else { + if (debug) printDebug("Rejecting ", member, args); + } + } + } + } + + if (ambiguousMethods == null) + return bestFit; + + // Compare ambiguous methods with best fit, in case + // the current best fit removes the ambiguities. + for (int i = ambiguousMethods.size() - 1; i >= 0 ; i--) { + Member member = (Member)ambiguousMethods.elementAt(i); + Class paramTypes[] = hasMethods + ? ((Method) member).getParameterTypes() + : ((Constructor) member).getParameterTypes(); + int preference = + NativeJavaMethod.preferSignature(args, + paramTypes, + bestFitTypes); + + if (preference == PREFERENCE_FIRST_ARG) { + if (debug) printDebug("Substituting ", member, args); + bestFit = member; + bestFitTypes = paramTypes; + ambiguousMethods.removeElementAt(i); + } + else if (preference == PREFERENCE_SECOND_ARG) { + if (debug) printDebug("Rejecting ", member, args); + ambiguousMethods.removeElementAt(i); + } + else { + if (debug) printDebug("UNRESOLVED: ", member, args); + } + } + + if (ambiguousMethods.size() > 0) { + // PENDING: report remaining ambiguity + StringBuffer buf = new StringBuffer(); + boolean isCtor = (bestFit instanceof Constructor); + + ambiguousMethods.addElement(bestFit); + + for (int i = 0; i < ambiguousMethods.size(); i++) { + if (i != 0) { + buf.append(", "); + } + Member member = (Member)ambiguousMethods.elementAt(i); + if (!isCtor) { + Class rtnType = ((Method)member).getReturnType(); + buf.append(rtnType); + buf.append(' '); + } + buf.append(NativeJavaMethod.signature(member)); + } + + String errMsg; + if (isCtor) { + Object errArgs[] = { + bestFit.getName(), + NativeJavaMethod.scriptSignature(args), + buf.toString() + }; + errMsg = + Context.getMessage("msg.constructor.ambiguous", errArgs); + } + else { + Object errArgs[] = { + bestFit.getDeclaringClass().getName(), + bestFit.getName(), + NativeJavaMethod.scriptSignature(args), + buf.toString() + }; + errMsg = Context.getMessage("msg.method.ambiguous", errArgs); + } + + throw + Context.reportRuntimeError(errMsg); + } + + return bestFit; + } + + /** Types are equal */ + static final int PREFERENCE_EQUAL = 0; + static final int PREFERENCE_FIRST_ARG = 1; + static final int PREFERENCE_SECOND_ARG = 2; + /** No clear "easy" conversion */ + static final int PREFERENCE_AMBIGUOUS = 3; + + /** + * Determine which of two signatures is the closer fit. + * Returns one of PREFERENCE_EQUAL, PREFERENCE_FIRST_ARG, + * PREFERENCE_SECOND_ARG, or PREFERENCE_AMBIGUOUS. + */ + public static int preferSignature(Object[] args, + Class[] sig1, Class[] sig2) + { + int preference = 0; + + for (int j = 0; j < args.length; j++) { + Class type1 = sig1[j]; + Class type2 = sig2[j]; + + if (type1 == type2) { + continue; + } + + preference |= + NativeJavaMethod.preferConversion(args[j], + type1, + type2); + + if (preference == PREFERENCE_AMBIGUOUS) { + break; + } + } + return preference; + } + + + /** + * Determine which of two types is the easier conversion. + * Returns one of PREFERENCE_EQUAL, PREFERENCE_FIRST_ARG, + * PREFERENCE_SECOND_ARG, or PREFERENCE_AMBIGUOUS. + */ + public static int preferConversion(Object fromObj, + Class toClass1, Class toClass2) { + + int rank1 = + NativeJavaObject.getConversionWeight(fromObj, toClass1); + int rank2 = + NativeJavaObject.getConversionWeight(fromObj, toClass2); + + if (rank1 == NativeJavaObject.CONVERSION_NONTRIVIAL && + rank2 == NativeJavaObject.CONVERSION_NONTRIVIAL) { + + if (toClass1.isAssignableFrom(toClass2)) { + return PREFERENCE_SECOND_ARG; + } + else if (toClass2.isAssignableFrom(toClass1)) { + return PREFERENCE_FIRST_ARG; + } + } + else { + if (rank1 < rank2) { + return PREFERENCE_FIRST_ARG; + } + else if (rank1 > rank2) { + return PREFERENCE_SECOND_ARG; + } + } + return PREFERENCE_AMBIGUOUS; + } + + Method[] getMethods() { + return methods; + } + + private static final boolean debug = false; + + private static void printDebug(String msg, Member member, Object[] args) { + if (debug) { + System.err.println(" ----- " + msg + + member.getDeclaringClass().getName() + + "." + signature(member) + + " for arguments (" + scriptSignature(args) + ")"); + } + } + + Method methods[]; +} + diff --git a/src/org/mozilla/javascript/NativeJavaObject.java b/src/org/mozilla/javascript/NativeJavaObject.java new file mode 100644 index 0000000..283252f --- /dev/null +++ b/src/org/mozilla/javascript/NativeJavaObject.java @@ -0,0 +1,926 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * The contents of this file are subject to the Netscape Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express oqr + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is Rhino code, released + * May 6, 1999. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1997-2000 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * Igor Bukanov + * Frank Mitchell + * Mike Shaver + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (the "GPL"), in which case the + * provisions of the GPL are applicable instead of those above. + * If you wish to allow use of your version of this file only + * under the terms of the GPL and not to allow others to use your + * version of this file under the NPL, indicate your decision by + * deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete + * the provisions above, a recipient may use your version of this + * file under either the NPL or the GPL. + */ + +package org.mozilla.javascript; + +import java.lang.reflect.*; +import java.util.Hashtable; +import java.util.Enumeration; + +/** + * This class reflects non-Array Java objects into the JavaScript environment. It + * reflect fields directly, and uses NativeJavaMethod objects to reflect (possibly + * overloaded) methods.

+ * + * @author Mike Shaver + * @see NativeJavaArray + * @see NativeJavaPackage + * @see NativeJavaClass + */ + +public class NativeJavaObject implements Scriptable, Wrapper { + + public NativeJavaObject(Scriptable scope, Object javaObject, + JavaMembers members) + { + this.parent = scope; + this.javaObject = javaObject; + this.members = members; + } + + public NativeJavaObject(Scriptable scope, Object javaObject, + Class staticType) + { + this.parent = scope; + this.javaObject = javaObject; + Class dynamicType = javaObject != null ? javaObject.getClass() + : staticType; + members = JavaMembers.lookupClass(scope, dynamicType, staticType); + fieldAndMethods = members.getFieldAndMethodsObjects(this, javaObject, false); + } + + public boolean has(String name, Scriptable start) { + return members.has(name, false); + } + + public boolean has(int index, Scriptable start) { + return false; + } + + public Object get(String name, Scriptable start) { + if (fieldAndMethods != null) { + Object result = fieldAndMethods.get(name); + if (result != null) { + return result; + } + } + // TODO: passing 'this' as the scope is bogus since it has + // no parent scope + return members.get(this, name, javaObject, false); + } + + public Object get(int index, Scriptable start) { + throw members.reportMemberNotFound(Integer.toString(index)); + } + + public void put(String name, Scriptable start, Object value) { + // We could be asked to modify the value of a property in the + // prototype. Since we can't add a property to a Java object, + // we modify it in the prototype rather than copy it down. + if (prototype == null || members.has(name, false)) + members.put(this, name, javaObject, value, false); + else + prototype.put(name, prototype, value); + } + + public void put(int index, Scriptable start, Object value) { + throw members.reportMemberNotFound(Integer.toString(index)); + } + + public boolean hasInstance(Scriptable value) { + // This is an instance of a Java class, so always return false + return false; + } + + public void delete(String name) { + } + + public void delete(int index) { + } + + public Scriptable getPrototype() { + if (prototype == null && javaObject.getClass() == ScriptRuntime.StringClass) { + return ScriptableObject.getClassPrototype(parent, "String"); + } + return prototype; + } + + /** + * Sets the prototype of the object. + */ + public void setPrototype(Scriptable m) { + prototype = m; + } + + /** + * Returns the parent (enclosing) scope of the object. + */ + public Scriptable getParentScope() { + return parent; + } + + /** + * Sets the parent (enclosing) scope of the object. + */ + public void setParentScope(Scriptable m) { + parent = m; + } + + public Object[] getIds() { + return members.getIds(false); + } + + public static Object wrap(Scriptable scope, Object obj, Class staticType) + { + if (obj == null) + return obj; + Context cx = Context.getCurrentContext(); + if (cx != null && cx.wrapHandler != null) { + Object result = cx.wrapHandler.wrap(scope, obj, staticType); + if (result != null) + return result; + } + Class cls = obj.getClass(); + if (staticType != null && staticType.isPrimitive()) { + if (staticType == Void.TYPE) + return Undefined.instance; + if (staticType == Character.TYPE) + return new Integer((int) ((Character) obj).charValue()); + return obj; + } + if (cls.isArray()) + return NativeJavaArray.wrap(scope, obj); + if (obj instanceof Scriptable) + return obj; + if (Context.useJSObject && jsObjectClass != null && + staticType != jsObjectClass && jsObjectClass.isInstance(obj)) + { + try { + return jsObjectGetScriptable.invoke(obj, ScriptRuntime.emptyArgs); + } + catch (InvocationTargetException e) { + // Just abandon conversion from JSObject + } + catch (IllegalAccessException e) { + // Just abandon conversion from JSObject + } + } + return new NativeJavaObject(scope, obj, staticType); + } + + public Object unwrap() { + return javaObject; + } + + public String getClassName() { + return "JavaObject"; + } + + Function getConverter(String converterName) { + Object converterFunction = get(converterName, this); + if (converterFunction instanceof Function) { + return (Function) converterFunction; + } + return null; + } + + Object callConverter(Function converterFunction) + throws JavaScriptException + { + Function f = (Function) converterFunction; + return f.call(Context.getContext(), f.getParentScope(), + this, ScriptRuntime.emptyArgs); + } + + Object callConverter(String converterName) + throws JavaScriptException + { + Function converter = getConverter(converterName); + if (converter == null) { + return javaObject.toString(); + } + return callConverter(converter); + } + + public Object getDefaultValue(Class hint) { + if (hint == null || hint == ScriptRuntime.StringClass) + return javaObject.toString(); + try { + if (hint == ScriptRuntime.BooleanClass) + return callConverter("booleanValue"); + if (hint == ScriptRuntime.NumberClass) { + return callConverter("doubleValue"); + } + // fall through to error message + } catch (JavaScriptException jse) { + // fall through to error message + } + throw Context.reportRuntimeError0("msg.default.value"); + } + + + /** + * Determine whether we can/should convert between the given type and the + * desired one. This should be superceded by a conversion-cost calculation + * function, but for now I'll hide behind precedent. + */ + public static boolean canConvert(Object fromObj, Class to) { + int weight = NativeJavaObject.getConversionWeight(fromObj, to); + + return (weight < CONVERSION_NONE); + } + + static final int JSTYPE_UNDEFINED = 0; // undefined type + static final int JSTYPE_NULL = 1; // null + static final int JSTYPE_BOOLEAN = 2; // boolean + static final int JSTYPE_NUMBER = 3; // number + static final int JSTYPE_STRING = 4; // string + static final int JSTYPE_JAVA_CLASS = 5; // JavaClass + static final int JSTYPE_JAVA_OBJECT = 6; // JavaObject + static final int JSTYPE_JAVA_ARRAY = 7; // JavaArray + static final int JSTYPE_OBJECT = 8; // Scriptable + + public static final byte CONVERSION_TRIVIAL = 1; + public static final byte CONVERSION_NONTRIVIAL = 0; + public static final byte CONVERSION_NONE = 99; + + /** + * Derive a ranking based on how "natural" the conversion is. + * The special value CONVERSION_NONE means no conversion is possible, + * and CONVERSION_NONTRIVIAL signals that more type conformance testing + * is required. + * Based on + * + * "preferred method conversions" from Live Connect 3 + */ + public static int getConversionWeight(Object fromObj, Class to) { + int fromCode = NativeJavaObject.getJSTypeCode(fromObj); + + int result = CONVERSION_NONE; + + switch (fromCode) { + + case JSTYPE_UNDEFINED: + if (to == ScriptRuntime.StringClass || + to == ScriptRuntime.ObjectClass) { + result = 1; + } + break; + + case JSTYPE_NULL: + if (!to.isPrimitive()) { + result = 1; + } + break; + + case JSTYPE_BOOLEAN: + // "boolean" is #1 + if (to == Boolean.TYPE) { + result = 1; + } + else if (to == ScriptRuntime.BooleanClass) { + result = 2; + } + else if (to == ScriptRuntime.ObjectClass) { + result = 3; + } + else if (to == ScriptRuntime.StringClass) { + result = 4; + } + break; + + case JSTYPE_NUMBER: + if (to.isPrimitive()) { + if (to == Double.TYPE) { + result = 1; + } + else if (to != Boolean.TYPE) { + result = 1 + NativeJavaObject.getSizeRank(to); + } + } + else { + if (to == ScriptRuntime.StringClass) { + // native numbers are #1-8 + result = 9; + } + else if (to == ScriptRuntime.ObjectClass) { + result = 10; + } + else if (ScriptRuntime.NumberClass.isAssignableFrom(to)) { + // "double" is #1 + result = 2; + } + } + break; + + case JSTYPE_STRING: + if (to == ScriptRuntime.StringClass) { + result = 1; + } + else if (to == ScriptRuntime.ObjectClass) { + result = 2; + } + else if (to.isPrimitive() && to != Boolean.TYPE) { + if (to == Character.TYPE) { + result = 3; + } + else { + result = 4; + } + } + break; + + case JSTYPE_JAVA_CLASS: + if (to == ScriptRuntime.ClassClass) { + result = 1; + } + else if (Context.useJSObject && jsObjectClass != null && + jsObjectClass.isAssignableFrom(to)) { + result = 2; + } + else if (to == ScriptRuntime.ObjectClass) { + result = 3; + } + else if (to == ScriptRuntime.StringClass) { + result = 4; + } + break; + + case JSTYPE_JAVA_OBJECT: + case JSTYPE_JAVA_ARRAY: + if (to == ScriptRuntime.StringClass) { + result = 2; + } + else if (to.isPrimitive() && to != Boolean.TYPE) { + result = + (fromCode == JSTYPE_JAVA_ARRAY) ? + CONVERSION_NONTRIVIAL : + 2 + NativeJavaObject.getSizeRank(to); + } + else { + Object javaObj = fromObj; + if (javaObj instanceof Wrapper) { + javaObj = ((Wrapper)javaObj).unwrap(); + } + if (to.isInstance(javaObj)) { + result = CONVERSION_NONTRIVIAL; + } + } + break; + + case JSTYPE_OBJECT: + // Other objects takes #1-#3 spots + if (Context.useJSObject && jsObjectClass != null && + jsObjectClass.isAssignableFrom(to)) { + result = 1; + } + else if (fromObj instanceof NativeArray && to.isArray()) { + // This is a native array conversion to a java array + // Array conversions are all equal, and preferable to object + // and string conversion, per LC3. + result = 1; + } + else if (to == ScriptRuntime.ObjectClass) { + result = 2; + } + else if (to == ScriptRuntime.StringClass) { + result = 3; + } + else if (to.isPrimitive() || to != Boolean.TYPE) { + result = 3 + NativeJavaObject.getSizeRank(to); + } + break; + } + + return result; + + } + + static int getSizeRank(Class aType) { + if (aType == Double.TYPE) { + return 1; + } + else if (aType == Float.TYPE) { + return 2; + } + else if (aType == Long.TYPE) { + return 3; + } + else if (aType == Integer.TYPE) { + return 4; + } + else if (aType == Short.TYPE) { + return 5; + } + else if (aType == Character.TYPE) { + return 6; + } + else if (aType == Byte.TYPE) { + return 7; + } + else if (aType == Boolean.TYPE) { + return CONVERSION_NONE; + } + else { + return 8; + } + } + + static int getJSTypeCode(Object value) { + if (value == null) { + return JSTYPE_NULL; + } + else if (value == Undefined.instance) { + return JSTYPE_UNDEFINED; + } + else if (value instanceof Scriptable) { + if (value instanceof NativeJavaClass) { + return JSTYPE_JAVA_CLASS; + } + else if (value instanceof NativeJavaArray) { + return JSTYPE_JAVA_ARRAY; + } + else if (value instanceof NativeJavaObject) { + return JSTYPE_JAVA_OBJECT; + } + else { + return JSTYPE_OBJECT; + } + } + else { + Class valueClass = value.getClass(); + + if (valueClass == ScriptRuntime.StringClass) { + return JSTYPE_STRING; + } + else if (valueClass == ScriptRuntime.BooleanClass) { + return JSTYPE_BOOLEAN; + } + else if (value instanceof Number) { + return JSTYPE_NUMBER; + } + else if (valueClass == ScriptRuntime.ClassClass) { + return JSTYPE_JAVA_CLASS; + } + else if (valueClass.isArray()) { + return JSTYPE_JAVA_ARRAY; + } + else { + return JSTYPE_JAVA_OBJECT; + } + } + } + + /** + * Type-munging for field setting and method invocation. + * Conforms to LC3 specification + */ + public static Object coerceType(Class type, Object value) { + if (value != null && value.getClass() == type) { + return value; + } + + switch (NativeJavaObject.getJSTypeCode(value)) { + + case JSTYPE_NULL: + // raise error if type.isPrimitive() + if (type.isPrimitive()) { + reportConversionError(value, type); + } + return null; + + case JSTYPE_UNDEFINED: + if (type == ScriptRuntime.StringClass || + type == ScriptRuntime.ObjectClass) { + return "undefined"; + } + else { + reportConversionError("undefined", type); + } + break; + + case JSTYPE_BOOLEAN: + // Under LC3, only JS Booleans can be coerced into a Boolean value + if (type == Boolean.TYPE || + type == ScriptRuntime.BooleanClass || + type == ScriptRuntime.ObjectClass) { + return value; + } + else if (type == ScriptRuntime.StringClass) { + return value.toString(); + } + else { + reportConversionError(value, type); + } + break; + + case JSTYPE_NUMBER: + if (type == ScriptRuntime.StringClass) { + return ScriptRuntime.toString(value); + } + else if (type == ScriptRuntime.ObjectClass) { + return coerceToNumber(Double.TYPE, value); + } + else if ((type.isPrimitive() && type != Boolean.TYPE) || + ScriptRuntime.NumberClass.isAssignableFrom(type)) { + return coerceToNumber(type, value); + } + else { + reportConversionError(value, type); + } + break; + + case JSTYPE_STRING: + if (type == ScriptRuntime.StringClass || + type == ScriptRuntime.ObjectClass) { + return value; + } + else if (type == Character.TYPE || + type == ScriptRuntime.CharacterClass) { + // Special case for converting a single char string to a + // character + // Placed here because it applies *only* to JS strings, + // not other JS objects converted to strings + if (((String)value).length() == 1) { + return new Character(((String)value).charAt(0)); + } + else { + return coerceToNumber(type, value); + } + } + else if ((type.isPrimitive() && type != Boolean.TYPE) || + ScriptRuntime.NumberClass.isAssignableFrom(type)) { + return coerceToNumber(type, value); + } + else { + reportConversionError(value, type); + } + break; + + case JSTYPE_JAVA_CLASS: + if (Context.useJSObject && jsObjectClass != null && + (type == ScriptRuntime.ObjectClass || + jsObjectClass.isAssignableFrom(type))) { + return coerceToJSObject(type, (Scriptable)value); + } + else { + if (value instanceof Wrapper) { + value = ((Wrapper)value).unwrap(); + } + + if (type == ScriptRuntime.ClassClass || + type == ScriptRuntime.ObjectClass) { + return value; + } + else if (type == ScriptRuntime.StringClass) { + return value.toString(); + } + else { + reportConversionError(value, type); + } + } + break; + + case JSTYPE_JAVA_OBJECT: + case JSTYPE_JAVA_ARRAY: + if (type.isPrimitive()) { + if (type == Boolean.TYPE) { + reportConversionError(value, type); + } + return coerceToNumber(type, value); + } + else { + if (value instanceof Wrapper) { + value = ((Wrapper)value).unwrap(); + } + if (type == ScriptRuntime.StringClass) { + return value.toString(); + } + else { + if (type.isInstance(value)) { + return value; + } + else { + reportConversionError(value, type); + } + } + } + break; + + case JSTYPE_OBJECT: + if (Context.useJSObject && jsObjectClass != null && + (type == ScriptRuntime.ObjectClass || + jsObjectClass.isAssignableFrom(type))) { + return coerceToJSObject(type, (Scriptable)value); + } + else if (type == ScriptRuntime.StringClass) { + return ScriptRuntime.toString(value); + } + else if (type.isPrimitive()) { + if (type == Boolean.TYPE) { + reportConversionError(value, type); + } + return coerceToNumber(type, value); + } + else if (type.isInstance(value)) { + return value; + } + else if (type.isArray() && value instanceof NativeArray) { + // Make a new java array, and coerce the JS array components + // to the target (component) type. + NativeArray array = (NativeArray) value; + long length = array.jsGet_length(); + Class arrayType = type.getComponentType(); + Object Result = Array.newInstance(arrayType, (int)length); + for (int i = 0 ; i < length ; ++i) { + try { + Array.set(Result, i, coerceType(arrayType, + array.get(i, array))); + } + catch (EvaluatorException ee) { + reportConversionError(value, type); + } + } + + return Result; + } + else if (value instanceof Wrapper) { + value = ((Wrapper)value).unwrap(); + if (type.isInstance(value)) + return value; + reportConversionError(value, type); + } + else { + reportConversionError(value, type); + } + break; + } + + return value; + } + + static Object coerceToJSObject(Class type, Scriptable value) { + // If JSObject compatibility is enabled, and the method wants it, + // wrap the Scriptable value in a JSObject. + + if (ScriptRuntime.ScriptableClass.isAssignableFrom(type)) + return value; + + try { + Object ctorArgs[] = { value }; + return jsObjectCtor.newInstance(ctorArgs); + } catch (InstantiationException instEx) { + throw new EvaluatorException("error generating JSObject wrapper for " + + value); + } catch (IllegalArgumentException argEx) { + throw new EvaluatorException("JSObject constructor doesn't want [Scriptable]!"); + } catch (InvocationTargetException e) { + throw WrappedException.wrapException(e.getTargetException()); + } catch (IllegalAccessException accessEx) { + throw new EvaluatorException("JSObject constructor is protected/private!"); + } + } + + static Object coerceToNumber(Class type, Object value) { + Class valueClass = value.getClass(); + + // Character + if (type == Character.TYPE || type == ScriptRuntime.CharacterClass) { + if (valueClass == ScriptRuntime.CharacterClass) { + return value; + } + return new Character((char)toInteger(value, + ScriptRuntime.CharacterClass, + (double)Character.MIN_VALUE, + (double)Character.MAX_VALUE)); + } + + // Double, Float + if (type == ScriptRuntime.ObjectClass || + type == ScriptRuntime.DoubleClass || type == Double.TYPE) { + return valueClass == ScriptRuntime.DoubleClass + ? value + : new Double(toDouble(value)); + } + + if (type == ScriptRuntime.FloatClass || type == Float.TYPE) { + if (valueClass == ScriptRuntime.FloatClass) { + return value; + } + else { + double number = toDouble(value); + if (Double.isInfinite(number) || Double.isNaN(number) + || number == 0.0) { + return new Float((float)number); + } + else { + double absNumber = Math.abs(number); + if (absNumber < (double)Float.MIN_VALUE) { + return new Float((number > 0.0) ? +0.0 : -0.0); + } + else if (absNumber > (double)Float.MAX_VALUE) { + return new Float((number > 0.0) ? + Float.POSITIVE_INFINITY : + Float.NEGATIVE_INFINITY); + } + else { + return new Float((float)number); + } + } + } + } + + // Integer, Long, Short, Byte + if (type == ScriptRuntime.IntegerClass || type == Integer.TYPE) { + if (valueClass == ScriptRuntime.IntegerClass) { + return value; + } + else { + return new Integer((int)toInteger(value, + ScriptRuntime.IntegerClass, + (double)Integer.MIN_VALUE, + (double)Integer.MAX_VALUE)); + } + } + + if (type == ScriptRuntime.LongClass || type == Long.TYPE) { + if (valueClass == ScriptRuntime.LongClass) { + return value; + } + else { + /* Long values cannot be expressed exactly in doubles. + * We thus use the largest and smallest double value that + * has a value expressible as a long value. We build these + * numerical values from their hexidecimal representations + * to avoid any problems caused by attempting to parse a + * decimal representation. + */ + final double max = Double.longBitsToDouble(0x43dfffffffffffffL); + final double min = Double.longBitsToDouble(0xc3e0000000000000L); + return new Long(toInteger(value, + ScriptRuntime.LongClass, + min, + max)); + } + } + + if (type == ScriptRuntime.ShortClass || type == Short.TYPE) { + if (valueClass == ScriptRuntime.ShortClass) { + return value; + } + else { + return new Short((short)toInteger(value, + ScriptRuntime.ShortClass, + (double)Short.MIN_VALUE, + (double)Short.MAX_VALUE)); + } + } + + if (type == ScriptRuntime.ByteClass || type == Byte.TYPE) { + if (valueClass == ScriptRuntime.ByteClass) { + return value; + } + else { + return new Byte((byte)toInteger(value, + ScriptRuntime.ByteClass, + (double)Byte.MIN_VALUE, + (double)Byte.MAX_VALUE)); + } + } + + return new Double(toDouble(value)); + } + + + static double toDouble(Object value) { + if (value instanceof Number) { + return ((Number)value).doubleValue(); + } + else if (value instanceof String) { + return ScriptRuntime.toNumber((String)value); + } + else if (value instanceof Scriptable) { + if (value instanceof Wrapper) { + // XXX: optimize tail-recursion? + return toDouble(((Wrapper)value).unwrap()); + } + else { + return ScriptRuntime.toNumber(value); + } + } + else { + Method meth; + try { + meth = value.getClass().getMethod("doubleValue", null); + } + catch (NoSuchMethodException e) { + meth = null; + } + catch (SecurityException e) { + meth = null; + } + if (meth != null) { + try { + return ((Number)meth.invoke(value, null)).doubleValue(); + } + catch (IllegalAccessException e) { + // XXX: ignore, or error message? + reportConversionError(value, Double.TYPE); + } + catch (InvocationTargetException e) { + // XXX: ignore, or error message? + reportConversionError(value, Double.TYPE); + } + } + return ScriptRuntime.toNumber(value.toString()); + } + } + + static long toInteger(Object value, Class type, double min, double max) { + double d = toDouble(value); + + if (Double.isInfinite(d) || Double.isNaN(d)) { + // Convert to string first, for more readable message + reportConversionError(ScriptRuntime.toString(value), type); + } + + if (d > 0.0) { + d = Math.floor(d); + } + else { + d = Math.ceil(d); + } + + if (d < min || d > max) { + // Convert to string first, for more readable message + reportConversionError(ScriptRuntime.toString(value), type); + } + return (long)d; + } + + static void reportConversionError(Object value, Class type) { + throw Context.reportRuntimeError2 + ("msg.conversion.not.allowed", + value.toString(), NativeJavaMethod.javaSignature(type)); + } + + public static void initJSObject() { + // if netscape.javascript.JSObject is in the CLASSPATH, enable JSObject + // compatability wrappers + jsObjectClass = null; + try { + jsObjectClass = Class.forName("netscape.javascript.JSObject"); + Class ctorParms[] = { ScriptRuntime.ScriptableClass }; + jsObjectCtor = jsObjectClass.getConstructor(ctorParms); + jsObjectGetScriptable = jsObjectClass.getMethod("getScriptable", + new Class[0]); + } catch (ClassNotFoundException classEx) { + // jsObjectClass already null + } catch (NoSuchMethodException methEx) { + // jsObjectClass already null + } + } + + /** + * The prototype of this object. + */ + protected Scriptable prototype; + + /** + * The parent scope of this object. + */ + protected Scriptable parent; + + protected Object javaObject; + protected JavaMembers members; + private Hashtable fieldAndMethods; + static Class jsObjectClass; + static Constructor jsObjectCtor; + static Method jsObjectGetScriptable; +} + diff --git a/src/org/mozilla/javascript/NativeJavaPackage.java b/src/org/mozilla/javascript/NativeJavaPackage.java new file mode 100644 index 0000000..5a8c652 --- /dev/null +++ b/src/org/mozilla/javascript/NativeJavaPackage.java @@ -0,0 +1,240 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * The contents of this file are subject to the Netscape Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express oqr + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is Rhino code, released + * May 6, 1999. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1997-1999 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * Frank Mitchell + * Mike Shaver + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (the "GPL"), in which case the + * provisions of the GPL are applicable instead of those above. + * If you wish to allow use of your version of this file only + * under the terms of the GPL and not to allow others to use your + * version of this file under the NPL, indicate your decision by + * deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete + * the provisions above, a recipient may use your version of this + * file under either the NPL or the GPL. + */ + +package org.mozilla.javascript; + +import java.lang.reflect.*; + +/** + * This class reflects Java packages into the JavaScript environment. We + * lazily reflect classes and subpackages, and use a caching/sharing + * system to ensure that members reflected into one JavaPackage appear + * in all other references to the same package (as with Packages.java.lang + * and java.lang). + * + * @author Mike Shaver + * @see NativeJavaArray + * @see NativeJavaObject + * @see NativeJavaClass + */ + +public class NativeJavaPackage extends ScriptableObject { + + // we know these are packages so we can skip the class check + // note that this is ok even if the package isn't present. + static final String[] commonPackages = { + "java.lang", + "java.lang.reflect", + "java.io", + "java.math", + "java.util", + "java.util.zip", + "java.text", + "java.text.resources", + "java.applet", + }; + + public static Scriptable init(Scriptable scope) + throws PropertyException + { + NativeJavaPackage packages = new NativeJavaPackage(""); + packages.setPrototype(getObjectPrototype(scope)); + packages.setParentScope(scope); + + // We want to get a real alias, and not a distinct JavaPackage + // with the same packageName, so that we share classes and packages + // that are underneath. + NativeJavaPackage javaAlias = (NativeJavaPackage)packages.get("java", + packages); + + // It's safe to downcast here since initStandardObjects takes + // a ScriptableObject. + ScriptableObject global = (ScriptableObject) scope; + + global.defineProperty("Packages", packages, ScriptableObject.DONTENUM); + global.defineProperty("java", javaAlias, ScriptableObject.DONTENUM); + + for (int i = 0; i < commonPackages.length; i++) + packages.forcePackage(commonPackages[i]); + + if (Context.useJSObject) + NativeJavaObject.initJSObject(); + + Method[] m = FunctionObject.findMethods(NativeJavaPackage.class, + "jsFunction_getClass"); + FunctionObject f = new FunctionObject("getClass", m[0], global); + global.defineProperty("getClass", f, ScriptableObject.DONTENUM); + + // I think I'm supposed to return the prototype, but I don't have one. + return packages; + } + + // set up a name which is known to be a package so we don't + // need to look for a class by that name + void forcePackage(String name) { + NativeJavaPackage pkg; + int end = name.indexOf('.'); + if (end == -1) + end = name.length(); + + String id = name.substring(0, end); + Object cached = super.get(id, this); + if (cached != null && cached instanceof NativeJavaPackage) { + pkg = (NativeJavaPackage) cached; + } else { + String newPackage = packageName.length() == 0 + ? id + : packageName + "." + id; + pkg = new NativeJavaPackage(newPackage); + pkg.setParentScope(this); + pkg.setPrototype(this.prototype); + super.put(id, this, pkg); + } + if (end < name.length()) + pkg.forcePackage(name.substring(end+1)); + } + + public NativeJavaPackage(String packageName) { + this.packageName = packageName; + } + + public String getClassName() { + return "JavaPackage"; + } + + public boolean has(String id, int index, Scriptable start) { + return true; + } + + public void put(String id, Scriptable start, Object value) { + // Can't add properties to Java packages. Sorry. + } + + public void put(int index, Scriptable start, Object value) { + throw Context.reportRuntimeError0("msg.pkg.int"); + } + + public Object get(String id, Scriptable start) { + return getPkgProperty(id, start, true); + } + + public Object get(int index, Scriptable start) { + return NOT_FOUND; + } + + synchronized Object getPkgProperty(String name, Scriptable start, + boolean createPkg) + { + Object cached = super.get(name, start); + if (cached != NOT_FOUND) + return cached; + + String newPackage = packageName.length() == 0 + ? name + : packageName + "." + name; + Context cx = Context.getContext(); + SecuritySupport ss = cx.getSecuritySupport(); + Scriptable newValue; + try { + /* + if (ss != null && !ss.visibleToScripts(newPackage)) + */ + throw new ClassNotFoundException(); + /* + Class newClass = ScriptRuntime.loadClassName(newPackage); + newValue = NativeJavaClass.wrap(getTopLevelScope(this), newClass); + newValue.setParentScope(this); + newValue.setPrototype(this.prototype); + */ + } catch (ClassNotFoundException ex) { + if (createPkg) { + NativeJavaPackage pkg = new NativeJavaPackage(newPackage); + pkg.setParentScope(this); + pkg.setPrototype(this.prototype); + newValue = pkg; + } else { + newValue = null; + } + } + if (newValue != null) { + // Make it available for fast lookup and sharing of + // lazily-reflected constructors and static members. + super.put(name, start, newValue); + } + return newValue; + } + + public Object getDefaultValue(Class ignored) { + return toString(); + } + + public String toString() { + return "[JavaPackage " + packageName + "]"; + } + + public static Scriptable jsFunction_getClass(Context cx, + Scriptable thisObj, + Object[] args, + Function funObj) + { + if (args.length > 0 && args[0] instanceof Wrapper) { + Scriptable result = getTopLevelScope(thisObj); + Class cl = ((Wrapper) args[0]).unwrap().getClass(); + // Evaluate the class name by getting successive properties of + // the string to find the appropriate NativeJavaClass object + String name = "Packages." + cl.getName(); + int offset = 0; + for (;;) { + int index = name.indexOf('.', offset); + String propName = index == -1 + ? name.substring(offset) + : name.substring(offset, index); + Object prop = result.get(propName, result); + if (!(prop instanceof Scriptable)) + break; // fall through to error + result = (Scriptable) prop; + if (index == -1) + return result; + offset = index+1; + } + } + throw Context.reportRuntimeError( + Context.getMessage0("msg.not.java.obj")); + } + + private String packageName; +} diff --git a/src/org/mozilla/javascript/NativeMath.java b/src/org/mozilla/javascript/NativeMath.java new file mode 100644 index 0000000..a6e58c6 --- /dev/null +++ b/src/org/mozilla/javascript/NativeMath.java @@ -0,0 +1,405 @@ +/* -*- Mode: java; tab-width: 4; indent-tabs-mode: 1; c-basic-offset: 4 -*- + * + * The contents of this file are subject to the Netscape Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express oqr + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is Rhino code, released + * May 6, 1999. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1997-1999 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * Igor Bukanov + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (the "GPL"), in which case the + * provisions of the GPL are applicable instead of those above. + * If you wish to allow use of your version of this file only + * under the terms of the GPL and not to allow others to use your + * version of this file under the NPL, indicate your decision by + * deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete + * the provisions above, a recipient may use your version of this + * file under either the NPL or the GPL. + */ + +package org.mozilla.javascript; + +/** + * This class implements the Math native object. + * See ECMA 15.8. + * @author Norris Boyd + */ + +public class NativeMath extends IdScriptable +{ + public static void init(Context cx, Scriptable scope, boolean sealed) { + NativeMath obj = new NativeMath(); + obj.setSealFunctionsFlag(sealed); + obj.setFunctionParametrs(cx); + obj.setPrototype(getObjectPrototype(scope)); + obj.setParentScope(scope); + if (sealed) { obj.sealObject(); } + ScriptableObject.defineProperty(scope, "Math", obj, + ScriptableObject.DONTENUM); + } + + public String getClassName() { return "Math"; } + + protected int getIdDefaultAttributes(int id) { + if (id > LAST_METHOD_ID) { + return DONTENUM | READONLY | PERMANENT; + } + return super.getIdDefaultAttributes(id); + } + + protected Object getIdValue(int id) { + if (id > LAST_METHOD_ID) { + return cacheIdValue(id, wrap_double(getField(id))); + } + return super.getIdValue(id); + } + + private double getField(int fieldId) { + switch (fieldId) { + case Id_E: return E; + case Id_PI: return PI; + case Id_LN10: return LN10; + case Id_LN2: return LN2; + case Id_LOG2E: return LOG2E; + case Id_LOG10E: return LOG10E; + case Id_SQRT1_2: return SQRT1_2; + case Id_SQRT2: return SQRT2; + } + return 0; // Unreachable + } + + public int methodArity(int methodId) { + switch (methodId) { + case Id_abs: return 1; + case Id_acos: return 1; + case Id_asin: return 1; + case Id_atan: return 1; + case Id_atan2: return 2; + case Id_ceil: return 1; + case Id_cos: return 1; + case Id_exp: return 1; + case Id_floor: return 1; + case Id_log: return 1; + case Id_max: return 2; + case Id_min: return 2; + case Id_pow: return 2; + case Id_random: return 0; + case Id_round: return 1; + case Id_sin: return 1; + case Id_sqrt: return 1; + case Id_tan: return 1; + } + return super.methodArity(methodId); + } + + public Object execMethod + (int methodId, IdFunction f, + Context cx, Scriptable scope, Scriptable thisObj, Object[] args) + throws JavaScriptException + { + switch (methodId) { + case Id_abs: return wrap_double + (js_abs(ScriptRuntime.toNumber(args, 0))); + + case Id_acos: return wrap_double + (js_acos(ScriptRuntime.toNumber(args, 0))); + + case Id_asin: return wrap_double + (js_asin(ScriptRuntime.toNumber(args, 0))); + + case Id_atan: return wrap_double + (js_atan(ScriptRuntime.toNumber(args, 0))); + + case Id_atan2: return wrap_double + (js_atan2(ScriptRuntime.toNumber(args, 0), + ScriptRuntime.toNumber(args, 1))); + + case Id_ceil: return wrap_double + (js_ceil(ScriptRuntime.toNumber(args, 0))); + + case Id_cos: return wrap_double + (js_cos(ScriptRuntime.toNumber(args, 0))); + + case Id_exp: return wrap_double + (js_exp(ScriptRuntime.toNumber(args, 0))); + + case Id_floor: return wrap_double + (js_floor(ScriptRuntime.toNumber(args, 0))); + + case Id_log: return wrap_double + (js_log(ScriptRuntime.toNumber(args, 0))); + + case Id_max: return wrap_double + (js_max(args)); + + case Id_min: return wrap_double + (js_min(args)); + + case Id_pow: return wrap_double + (js_pow(ScriptRuntime.toNumber(args, 0), + ScriptRuntime.toNumber(args, 1))); + + case Id_random: return wrap_double + (js_random()); + + case Id_round: return wrap_double + (js_round(ScriptRuntime.toNumber(args, 0))); + + case Id_sin: return wrap_double + (js_sin(ScriptRuntime.toNumber(args, 0))); + + case Id_sqrt: return wrap_double + (js_sqrt(ScriptRuntime.toNumber(args, 0))); + + case Id_tan: return wrap_double + (js_tan(ScriptRuntime.toNumber(args, 0))); + } + return super.execMethod(methodId, f, cx, scope, thisObj, args); + } + + private double js_abs(double x) { + // abs(-0.0) should be 0.0, but -0.0 < 0.0 == false + return (x == 0.0) ? 0.0 : (x < 0.0) ? -x : x; + } + + private double js_acos(double x) { + return (x == x && -1.0 <= x && x <= 1.0) ? Math.acos(x) : Double.NaN; + } + + private double js_asin(double x) { + return (x == x && -1.0 <= x && x <= 1.0) ? Math.asin(x) : Double.NaN; + } + + private double js_atan(double x) { return Math.atan(x); } + + private double js_atan2(double x, double y) { return Math.atan2(x, y); } + + private double js_ceil(double x) { return Math.ceil(x); } + + private double js_cos(double x) { return Math.cos(x); } + + private double js_exp(double x) { + return (x == Double.POSITIVE_INFINITY) ? x + : (x == Double.NEGATIVE_INFINITY) ? 0.0 + : Math.exp(x); + } + + private double js_floor(double x) { return Math.floor(x); } + + private double js_log(double x) { + // Java's log(<0) = -Infinity; we need NaN + return (x < 0) ? Double.NaN : Math.log(x); + } + + private double js_max(Object[] args) { + double result = Double.NEGATIVE_INFINITY; + if (args.length == 0) + return result; + for (int i = 0; i < args.length; i++) { + double d = ScriptRuntime.toNumber(args[i]); + if (d != d) return d; + // if (result < d) result = d; does not work due to -0.0 >= +0.0 + result = Math.max(result, d); + } + return result; + } + + private double js_min(Object[] args) { + double result = Double.POSITIVE_INFINITY; + if (args.length == 0) + return result; + for (int i = 0; i < args.length; i++) { + double d = ScriptRuntime.toNumber(args[i]); + if (d != d) return d; + // if (result > d) result = d; does not work due to -0.0 >= +0.0 + result = Math.min(result, d); + } + return result; + } + + private double js_pow(double x, double y) { + if (y == 0) return 1.0; // Java's pow(NaN, 0) = NaN; we need 1 + if ((x == 0) && (y < 0)) { + if (1 / x > 0) { + // x is +0, Java is -oo, we need +oo + return Double.POSITIVE_INFINITY; + } + /* if x is -0 and y is an odd integer, -oo */ + int y_int = (int)y; + if (y_int == y && (y_int & 0x1) != 0) + return Double.NEGATIVE_INFINITY; + return Double.POSITIVE_INFINITY; + } + return Math.pow(x, y); + } + + private double js_random() { return Math.random(); } + + private double js_round(double d) { + if (d != d) + return d; // NaN + if (d == Double.POSITIVE_INFINITY || d == Double.NEGATIVE_INFINITY) + return d; + long l = Math.round(d); + if (l == 0) { + // We must propagate the sign of d into the result + if (d < 0.0) + return ScriptRuntime.negativeZero; + return d == 0.0 ? d : 0.0; + } + return (double) l; + } + + private double js_sin(double x) { return Math.sin(x); } + + private double js_sqrt(double x) { return Math.sqrt(x); } + + private double js_tan(double x) { return Math.tan(x); } + + protected int maxInstanceId() { return MAX_INSTANCE_ID; } + + protected String getIdName(int id) { + switch (id) { + case Id_abs: return "abs"; + case Id_acos: return "acos"; + case Id_asin: return "asin"; + case Id_atan: return "atan"; + case Id_atan2: return "atan2"; + case Id_ceil: return "ceil"; + case Id_cos: return "cos"; + case Id_exp: return "exp"; + case Id_floor: return "floor"; + case Id_log: return "log"; + case Id_max: return "max"; + case Id_min: return "min"; + case Id_pow: return "pow"; + case Id_random: return "random"; + case Id_round: return "round"; + case Id_sin: return "sin"; + case Id_sqrt: return "sqrt"; + case Id_tan: return "tan"; + + case Id_E: return "E"; + case Id_PI: return "PI"; + case Id_LN10: return "LN10"; + case Id_LN2: return "LN2"; + case Id_LOG2E: return "LOG2E"; + case Id_LOG10E: return "LOG10E"; + case Id_SQRT1_2: return "SQRT1_2"; + case Id_SQRT2: return "SQRT2"; + } + return null; + } + +// #string_id_map# + + protected int mapNameToId(String s) { + int id; +// #generated# Last update: 2001-03-23 13:50:14 GMT+01:00 + L0: { id = 0; String X = null; int c; + L: switch (s.length()) { + case 1: if (s.charAt(0)=='E') {id=Id_E; break L0;} break L; + case 2: if (s.charAt(0)=='P' && s.charAt(1)=='I') {id=Id_PI; break L0;} break L; + case 3: switch (s.charAt(0)) { + case 'L': if (s.charAt(2)=='2' && s.charAt(1)=='N') {id=Id_LN2; break L0;} break L; + case 'a': if (s.charAt(2)=='s' && s.charAt(1)=='b') {id=Id_abs; break L0;} break L; + case 'c': if (s.charAt(2)=='s' && s.charAt(1)=='o') {id=Id_cos; break L0;} break L; + case 'e': if (s.charAt(2)=='p' && s.charAt(1)=='x') {id=Id_exp; break L0;} break L; + case 'l': if (s.charAt(2)=='g' && s.charAt(1)=='o') {id=Id_log; break L0;} break L; + case 'm': c=s.charAt(2); + if (c=='n') { if (s.charAt(1)=='i') {id=Id_min; break L0;} } + else if (c=='x') { if (s.charAt(1)=='a') {id=Id_max; break L0;} } + break L; + case 'p': if (s.charAt(2)=='w' && s.charAt(1)=='o') {id=Id_pow; break L0;} break L; + case 's': if (s.charAt(2)=='n' && s.charAt(1)=='i') {id=Id_sin; break L0;} break L; + case 't': if (s.charAt(2)=='n' && s.charAt(1)=='a') {id=Id_tan; break L0;} break L; + } break L; + case 4: switch (s.charAt(1)) { + case 'N': X="LN10";id=Id_LN10; break L; + case 'c': X="acos";id=Id_acos; break L; + case 'e': X="ceil";id=Id_ceil; break L; + case 'q': X="sqrt";id=Id_sqrt; break L; + case 's': X="asin";id=Id_asin; break L; + case 't': X="atan";id=Id_atan; break L; + } break L; + case 5: switch (s.charAt(0)) { + case 'L': X="LOG2E";id=Id_LOG2E; break L; + case 'S': X="SQRT2";id=Id_SQRT2; break L; + case 'a': X="atan2";id=Id_atan2; break L; + case 'f': X="floor";id=Id_floor; break L; + case 'r': X="round";id=Id_round; break L; + } break L; + case 6: c=s.charAt(0); + if (c=='L') { X="LOG10E";id=Id_LOG10E; } + else if (c=='r') { X="random";id=Id_random; } + break L; + case 7: X="SQRT1_2";id=Id_SQRT1_2; break L; + } + if (X!=null && X!=s && !X.equals(s)) id = 0; + } +// #/generated# + return id; + } + + private static final int + Id_abs = 1, + Id_acos = 2, + Id_asin = 3, + Id_atan = 4, + Id_atan2 = 5, + Id_ceil = 6, + Id_cos = 7, + Id_exp = 8, + Id_floor = 9, + Id_log = 10, + Id_max = 11, + Id_min = 12, + Id_pow = 13, + Id_random = 14, + Id_round = 15, + Id_sin = 16, + Id_sqrt = 17, + Id_tan = 18, + + LAST_METHOD_ID = 18, + + Id_E = 19, + Id_PI = 20, + Id_LN10 = 21, + Id_LN2 = 22, + Id_LOG2E = 23, + Id_LOG10E = 24, + Id_SQRT1_2 = 25, + Id_SQRT2 = 26, + + MAX_INSTANCE_ID = 26; + +// #/string_id_map# + + private static final double + E = Math.E, + PI = Math.PI, + LN10 = 2.302585092994046, + LN2 = 0.6931471805599453, + LOG2E = 1.4426950408889634, + LOG10E = 0.4342944819032518, + SQRT1_2 = 0.7071067811865476, + SQRT2 = 1.4142135623730951; +} diff --git a/src/org/mozilla/javascript/NativeNumber.java b/src/org/mozilla/javascript/NativeNumber.java new file mode 100644 index 0000000..4ec2dcb --- /dev/null +++ b/src/org/mozilla/javascript/NativeNumber.java @@ -0,0 +1,280 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * The contents of this file are subject to the Netscape Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express oqr + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is Rhino code, released + * May 6, 1999. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1997-1999 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * Igor Bukanov + * Mike McCabe + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (the "GPL"), in which case the + * provisions of the GPL are applicable instead of those above. + * If you wish to allow use of your version of this file only + * under the terms of the GPL and not to allow others to use your + * version of this file under the NPL, indicate your decision by + * deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete + * the provisions above, a recipient may use your version of this + * file under either the NPL or the GPL. + */ + +package org.mozilla.javascript; + +/** + * This class implements the Number native object. + * + * See ECMA 15.7. + * + * @author Norris Boyd + */ +public class NativeNumber extends IdScriptable { + + private static final int MAX_PRECISION = 100; + + public static void init(Context cx, Scriptable scope, boolean sealed) { + NativeNumber obj = new NativeNumber(); + obj.prototypeFlag = true; + obj.addAsPrototype(MAX_PROTOTYPE_ID, cx, scope, sealed); + } + + /** + * Zero-parameter constructor: just used to create Number.prototype + */ + public NativeNumber() { + doubleValue = defaultValue; + } + + public NativeNumber(double number) { + doubleValue = number; + } + + public String getClassName() { + return "Number"; + } + + protected void fillConstructorProperties + (Context cx, IdFunction ctor, boolean sealed) + { + final int attr = ScriptableObject.DONTENUM | + ScriptableObject.PERMANENT | + ScriptableObject.READONLY; + + ctor.defineProperty("NaN", wrap_double(ScriptRuntime.NaN), attr); + ctor.defineProperty("POSITIVE_INFINITY", + wrap_double(Double.POSITIVE_INFINITY), attr); + ctor.defineProperty("NEGATIVE_INFINITY", + wrap_double(Double.NEGATIVE_INFINITY), attr); + ctor.defineProperty("MAX_VALUE", wrap_double(Double.MAX_VALUE), attr); + ctor.defineProperty("MIN_VALUE", wrap_double(Double.MIN_VALUE), attr); + + super.fillConstructorProperties(cx, ctor, sealed); + } + + public int methodArity(int methodId) { + if (prototypeFlag) { + switch (methodId) { + case Id_constructor: return 1; + case Id_toString: return 1; + case Id_valueOf: return 0; + case Id_toLocaleString: return 1; + case Id_toFixed: return 1; + case Id_toExponential: return 1; + case Id_toPrecision: return 1; + } + } + return super.methodArity(methodId); + } + + public Object execMethod + (int methodId, IdFunction f, + Context cx, Scriptable scope, Scriptable thisObj, Object[] args) + throws JavaScriptException + { + if (prototypeFlag) { + switch (methodId) { + case Id_constructor: + return jsConstructor(args, thisObj == null); + + case Id_toString: return realThis(thisObj, f). + jsFunction_toString(toBase(args, 0)); + + case Id_valueOf: return wrap_double(realThis(thisObj, f). + jsFunction_valueOf()); + + case Id_toLocaleString: return realThis(thisObj, f). + jsFunction_toLocaleString(toBase(args, 0)); + + case Id_toFixed: return realThis(thisObj, f). + jsFunction_toFixed(cx, args); + + case Id_toExponential: return realThis(thisObj, f). + jsFunction_toExponential(cx, args); + + case Id_toPrecision:return realThis(thisObj, f). + jsFunction_toPrecision(cx, args); + } + } + return super.execMethod(methodId, f, cx, scope, thisObj, args); + } + + private NativeNumber realThis(Scriptable thisObj, IdFunction f) { + while (!(thisObj instanceof NativeNumber)) { + thisObj = nextInstanceCheck(thisObj, f, true); + } + return (NativeNumber)thisObj; + } + + private static int toBase(Object[] args, int index) { + return (index < args.length) ? ScriptRuntime.toInt32(args[index]) : 10; + } + + private Object jsConstructor(Object[] args, boolean inNewExpr) { + double d = args.length >= 1 + ? ScriptRuntime.toNumber(args[0]) + : defaultValue; + if (inNewExpr) { + // new Number(val) creates a new Number object. + return new NativeNumber(d); + } + // Number(val) converts val to a number value. + return wrap_double(d); + } + + public String toString() { + return jsFunction_toString(10); + } + + private String jsFunction_toString(int base) { + return ScriptRuntime.numberToString(doubleValue, base); + } + + private double jsFunction_valueOf() { + return doubleValue; + } + + private String jsFunction_toLocaleString(int base) { + return jsFunction_toString(base); + } + + private String jsFunction_toFixed(Context cx, Object[] args) { + /* We allow a larger range of precision than + ECMA requires; this is permitted by ECMA. */ + return num_to(cx, args, DToA.DTOSTR_FIXED, DToA.DTOSTR_FIXED, + -20, MAX_PRECISION, 0); + } + + private String jsFunction_toExponential(Context cx, Object[] args) { + /* We allow a larger range of precision than + ECMA requires; this is permitted by ECMA. */ + return num_to(cx, args, + DToA.DTOSTR_STANDARD_EXPONENTIAL, + DToA.DTOSTR_EXPONENTIAL, + 0, MAX_PRECISION, 1); + } + + private String jsFunction_toPrecision(Context cx, Object[] args) { + /* We allow a larger range of precision than + ECMA requires; this is permitted by ECMA. */ + return num_to(cx, args, DToA.DTOSTR_STANDARD, DToA.DTOSTR_PRECISION, + 1, MAX_PRECISION, 0); + } + + private String num_to(Context cx, Object[] args, + int zeroArgMode, int oneArgMode, + int precisionMin, int precisionMax, + int precisionOffset) + { + int precision; + + if (args.length == 0) { + precision = 0; + oneArgMode = zeroArgMode; + } else { + precision = ScriptRuntime.toInt32(args[0]); + if (precision < precisionMin || precision > precisionMax) { + String msg = ScriptRuntime.getMessage1( + "msg.bad.precision", ScriptRuntime.toString(args[0])); + throw NativeGlobal.constructError(cx, "RangeError", msg, this); + } + } + StringBuffer result = new StringBuffer(); + DToA.JS_dtostr(result, oneArgMode, precision + precisionOffset, + doubleValue); + return result.toString(); + } + + protected String getIdName(int id) { + if (prototypeFlag) { + switch (id) { + case Id_constructor: return "constructor"; + case Id_toString: return "toString"; + case Id_valueOf: return "valueOf"; + case Id_toLocaleString: return "toLocaleString"; + case Id_toFixed: return "toFixed"; + case Id_toExponential: return "toExponential"; + case Id_toPrecision: return "toPrecision"; + } + } + return null; + } + +// #string_id_map# + + protected int mapNameToId(String s) { + if (!prototypeFlag) { return 0; } + int id; +// #generated# Last update: 2001-04-23 10:40:45 CEST + L0: { id = 0; String X = null; int c; + L: switch (s.length()) { + case 7: c=s.charAt(0); + if (c=='t') { X="toFixed";id=Id_toFixed; } + else if (c=='v') { X="valueOf";id=Id_valueOf; } + break L; + case 8: X="toString";id=Id_toString; break L; + case 11: c=s.charAt(0); + if (c=='c') { X="constructor";id=Id_constructor; } + else if (c=='t') { X="toPrecision";id=Id_toPrecision; } + break L; + case 13: X="toExponential";id=Id_toExponential; break L; + case 14: X="toLocaleString";id=Id_toLocaleString; break L; + } + if (X!=null && X!=s && !X.equals(s)) id = 0; + } +// #/generated# + return id; + } + + private static final int + Id_constructor = 1, + Id_toString = 2, + Id_valueOf = 3, + Id_toLocaleString = 4, + Id_toFixed = 5, + Id_toExponential = 6, + Id_toPrecision = 7, + MAX_PROTOTYPE_ID = 7; + +// #/string_id_map# + + private static final double defaultValue = +0.0; + private double doubleValue; + + private boolean prototypeFlag; +} diff --git a/src/org/mozilla/javascript/NativeObject.java b/src/org/mozilla/javascript/NativeObject.java new file mode 100644 index 0000000..a2b8dee --- /dev/null +++ b/src/org/mozilla/javascript/NativeObject.java @@ -0,0 +1,291 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * The contents of this file are subject to the Netscape Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express oqr + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is Rhino code, released + * May 6, 1999. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1997-1999 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * Mike McCabe + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (the "GPL"), in which case the + * provisions of the GPL are applicable instead of those above. + * If you wish to allow use of your version of this file only + * under the terms of the GPL and not to allow others to use your + * version of this file under the NPL, indicate your decision by + * deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete + * the provisions above, a recipient may use your version of this + * file under either the NPL or the GPL. + */ + +package org.mozilla.javascript; + +import java.util.Hashtable; + +/** + * This class implements the Object native object. + * See ECMA 15.2. + * @author Norris Boyd + */ +public class NativeObject extends IdScriptable { + + public static void init(Context cx, Scriptable scope, boolean sealed) { + NativeObject obj = new NativeObject(); + obj.prototypeFlag = true; + obj.addAsPrototype(MAX_PROTOTYPE_ID, cx, scope, sealed); + } + + public String getClassName() { + return "Object"; + } + + public int methodArity(int methodId) { + if (prototypeFlag) { + switch (methodId) { + case Id_constructor: return 1; + case Id_toString: return 0; + case Id_toLocaleString: return 0; + case Id_valueOf: return 0; + case Id_hasOwnProperty: return 1; + case Id_propertyIsEnumerable: return 1; + case Id_isPrototypeOf: return 1; + } + } + return super.methodArity(methodId); + } + + public Object execMethod + (int methodId, IdFunction f, + Context cx, Scriptable scope, Scriptable thisObj, Object[] args) + throws JavaScriptException + { + if (prototypeFlag) { + switch (methodId) { + case Id_constructor: + return jsConstructor(cx, args, f, thisObj == null); + + case Id_toString: + return jsFunction_toString(cx, thisObj); + + case Id_toLocaleString: + return jsFunction_toLocaleString(cx, thisObj); + + case Id_valueOf: + return jsFunction_valueOf(thisObj); + + case Id_hasOwnProperty: + return jsFunction_hasOwnProperty(thisObj, args); + + case Id_propertyIsEnumerable: + return jsFunction_propertyIsEnumerable(cx, thisObj, args); + + case Id_isPrototypeOf: + return jsFunction_isPrototypeOf(cx, thisObj, args); + } + } + return super.execMethod(methodId, f, cx, scope, thisObj, args); + } + + private static Object jsConstructor(Context cx, Object[] args, + Function ctorObj, boolean inNewExpr) + throws JavaScriptException + { + if (!inNewExpr) { + // FunctionObject.construct will set up parent, proto + return ctorObj.construct(cx, ctorObj.getParentScope(), args); + } + if (args.length == 0 || args[0] == null || + args[0] == Undefined.instance) + { + return new NativeObject(); + } + return ScriptRuntime.toObject(ctorObj.getParentScope(), args[0]); + } + + public String toString() { + Context cx = Context.getCurrentContext(); + if (cx != null) + return jsFunction_toString(cx, this); + else + return "[object " + getClassName() + "]"; + } + + private static String jsFunction_toString(Context cx, Scriptable thisObj) + { + if (cx.getLanguageVersion() != cx.VERSION_1_2) + return "[object " + thisObj.getClassName() + "]"; + + return toSource(cx, thisObj); + } + + private static String jsFunction_toLocaleString(Context cx, + Scriptable thisObj) + { + return jsFunction_toString(cx, thisObj); + } + + private static String toSource(Context cx, Scriptable thisObj) + { + Scriptable m = thisObj; + + if (cx.iterating == null) + cx.iterating = new Hashtable(31); + + if (cx.iterating.get(m) == Boolean.TRUE) { + return "{}"; // stop recursion + } else { + StringBuffer result = new StringBuffer("{"); + Object[] ids = m.getIds(); + + for(int i=0; i < ids.length; i++) { + if (i > 0) + result.append(", "); + + Object id = ids[i]; + String idString = ScriptRuntime.toString(id); + Object p = (id instanceof String) + ? m.get((String) id, m) + : m.get(((Number) id).intValue(), m); + if (p instanceof String) { + result.append(idString + ":\"" + + ScriptRuntime + .escapeString(ScriptRuntime.toString(p)) + + "\""); + } else { + /* wrap changes to cx.iterating in a try/finally + * so that the reference always gets removed, and + * we don't leak memory. Good place for weak + * references, if we had them. + */ + try { + cx.iterating.put(m, Boolean.TRUE); // stop recursion. + result.append(idString + ":" + ScriptRuntime.toString(p)); + } finally { + cx.iterating.remove(m); + } + } + } + result.append('}'); + return result.toString(); + } + } + + private static Object jsFunction_valueOf(Scriptable thisObj) { + return thisObj; + } + + private static Object jsFunction_hasOwnProperty(Scriptable thisObj, + Object[] args) + { + if (args.length != 0) { + if (thisObj.has(ScriptRuntime.toString(args[0]), thisObj)) + return Boolean.TRUE; + } + return Boolean.FALSE; + } + + private static Object jsFunction_propertyIsEnumerable(Context cx, + Scriptable thisObj, + Object[] args) + { + try { + if (args.length != 0) { + String name = ScriptRuntime.toString(args[0]); + if (thisObj.has(name, thisObj)) { + int a = ((ScriptableObject)thisObj).getAttributes(name, thisObj); + if ((a & ScriptableObject.DONTENUM) == 0) + return Boolean.TRUE; + } + } + } + catch (PropertyException x) { + } + catch (ClassCastException x) { + } + return Boolean.FALSE; + } + + private static Object jsFunction_isPrototypeOf(Context cx, + Scriptable thisObj, + Object[] args) + { + if (args.length != 0 && args[0] instanceof Scriptable) { + Scriptable v = (Scriptable) args[0]; + do { + v = v.getPrototype(); + if (v == thisObj) + return Boolean.TRUE; + } while (v != null); + } + return Boolean.FALSE; + } + + protected String getIdName(int id) { + if (prototypeFlag) { + switch (id) { + case Id_constructor: return "constructor"; + case Id_toString: return "toString"; + case Id_toLocaleString: return "toLocaleString"; + case Id_valueOf: return "valueOf"; + case Id_hasOwnProperty: return "hasOwnProperty"; + case Id_propertyIsEnumerable: return "propertyIsEnumerable"; + case Id_isPrototypeOf: return "isPrototypeOf"; + } + } + return null; + } + +// #string_id_map# + + protected int mapNameToId(String s) { + if (!prototypeFlag) { return 0; } + int id; +// #generated# Last update: 2001-04-24 12:37:03 GMT+02:00 + L0: { id = 0; String X = null; int c; + L: switch (s.length()) { + case 7: X="valueOf";id=Id_valueOf; break L; + case 8: X="toString";id=Id_toString; break L; + case 11: X="constructor";id=Id_constructor; break L; + case 13: X="isPrototypeOf";id=Id_isPrototypeOf; break L; + case 14: c=s.charAt(0); + if (c=='h') { X="hasOwnProperty";id=Id_hasOwnProperty; } + else if (c=='t') { X="toLocaleString";id=Id_toLocaleString; } + break L; + case 20: X="propertyIsEnumerable";id=Id_propertyIsEnumerable; break L; + } + if (X!=null && X!=s && !X.equals(s)) id = 0; + } +// #/generated# + return id; + } + + private static final int + Id_constructor = 1, + Id_toString = 2, + Id_toLocaleString = 3, + Id_valueOf = 4, + Id_hasOwnProperty = 5, + Id_propertyIsEnumerable = 6, + Id_isPrototypeOf = 7, + MAX_PROTOTYPE_ID = 7; + +// #/string_id_map# + + private boolean prototypeFlag; +} diff --git a/src/org/mozilla/javascript/NativeScript.java b/src/org/mozilla/javascript/NativeScript.java new file mode 100644 index 0000000..cb6d855 --- /dev/null +++ b/src/org/mozilla/javascript/NativeScript.java @@ -0,0 +1,286 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * The contents of this file are subject to the Netscape Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express oqr + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is Rhino code, released + * May 6, 1999. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1997-1999 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * Roger Lawrence + * Mike McCabe + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (the "GPL"), in which case the + * provisions of the GPL are applicable instead of those above. + * If you wish to allow use of your version of this file only + * under the terms of the GPL and not to allow others to use your + * version of this file under the NPL, indicate your decision by + * deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete + * the provisions above, a recipient may use your version of this + * file under either the NPL or the GPL. + */ + +package org.mozilla.javascript; + +import java.io.StringReader; +import java.io.IOException; + +/** + * The JavaScript Script object. + * + * Note that the C version of the engine uses XDR as the format used + * by freeze and thaw. Since this depends on the internal format of + * structures in the C runtime, we cannot duplicate it. + * + * Since we cannot replace 'this' as a result of the compile method, + * this class has a dual nature. Generated scripts will have a null + * 'script' field and will override 'exec' and 'call'. Scripts created + * using the JavaScript constructor will forward requests to the + * nonnull 'script' field. + * + * @since 1.3 + * @author Norris Boyd + */ + +public class NativeScript extends NativeFunction implements Script { + + public static void init(Context cx, Scriptable scope, boolean sealed) { + NativeScript obj = new NativeScript(); + obj.scopeInit(cx, scope, sealed); + } + + public NativeScript() { + } + + private void scopeInit(Context cx, Scriptable scope, boolean sealed) { + // prototypeIdShift != 0 serves as indicator of prototype instance + // and as id offset to take into account ids present in each instance + // of the base class NativeFunction. + // Not to depend on the assumption NativeFunction.maxInstanceId() != 0, + // 1 is added super.maxInstanceId() to make sure that + // prototypeIdShift != 0 in the NativeScript prototype. + // In a similar way the following methods use + // methodId - prototypeIdShift + 1, not methodId - prototypeIdShift + // to unshift prototype id to [1 .. MAX_PROTOTYPE_ID] interval + prototypeIdShift = super.maxInstanceId() + 1; + addAsPrototype(MAX_PROTOTYPE_ID + prototypeIdShift - 1, + cx, scope, sealed); + } + + /** + * Returns the name of this JavaScript class, "Script". + */ + public String getClassName() { + return "Script"; + } + + /** + * Initialize script. + * + * Does nothing here, but scripts will override with code + * to initialize contained functions, regexp literals, etc. + */ + public void initScript(Scriptable scope) { + } + + public int methodArity(int methodId) { + if (prototypeIdShift != 0) { + switch (methodId - prototypeIdShift + 1) { + case Id_constructor: return 1; + case Id_toString: return 0; + case Id_exec: return 0; + case Id_compile: return 1; + } + } + return super.methodArity(methodId); + } + + public Object execMethod(int methodId, IdFunction f, Context cx, + Scriptable scope, Scriptable thisObj, + Object[] args) + throws JavaScriptException + { + if (prototypeIdShift != 0) { + switch (methodId - prototypeIdShift + 1) { + case Id_constructor: + return jsConstructor(cx, scope, args); + + case Id_toString: + return realThis(thisObj, f, true). + jsFunction_toString(cx, args); + + case Id_exec: + return realThis(thisObj, f, true).jsFunction_exec(); + + case Id_compile: + return realThis(thisObj, f, false). + jsFunction_compile(ScriptRuntime.toString(args, 0)); + } + } + + return super.execMethod(methodId, f, cx, scope, thisObj, args); + } + + private NativeScript realThis(Scriptable thisObj, IdFunction f, + boolean readOnly) + { + while (!(thisObj instanceof NativeScript)) { + thisObj = nextInstanceCheck(thisObj, f, readOnly); + } + return (NativeScript)thisObj; + } + + /** + * The Java method defining the JavaScript Script constructor. + * + */ + private static Object jsConstructor(Context cx, Scriptable scope, + Object[] args) + { + String source = args.length == 0 + ? "" + : ScriptRuntime.toString(args[0]); + return compile(scope, source); + } + + public static Script compile(Scriptable scope, String source) { + Context cx = Context.getContext(); + StringReader reader = new StringReader(source); + try { + int[] linep = { 0 }; + String filename = Context.getSourcePositionFromStack(linep); + if (filename == null) { + filename = "