From: megacz Date: Fri, 30 Jan 2004 07:41:49 +0000 (+0000) Subject: 2003/11/18 10:47:26 X-Git-Tag: RC3~331 X-Git-Url: http://git.megacz.com/?p=org.ibex.core.git;a=commitdiff_plain;h=c2a138c3882a4bd8dce0212ac59765af5cb126c6 2003/11/18 10:47:26 darcs-hash:20040130074149-2ba56-30f9053960eb42621df713a3adc0087b9b523f2c.gz --- diff --git a/src/org/xwt/Res.java b/src/org/xwt/Res.java index 0652e09..d35be09 100644 --- a/src/org/xwt/Res.java +++ b/src/org/xwt/Res.java @@ -100,7 +100,7 @@ public abstract class Res extends JS { ByteArray(byte[] bytes, String cacheKey) { this.bytes = bytes; this.cacheKey = cacheKey; } public String getCacheKey() throws NotCacheableException { return cacheKey; } public InputStream getInputStream(String path) throws IOException { - if (!"".equals(path)) throw new JS.Exn("can't get subresources of a byte[] resource"); + if (!"".equals(path)) throw new JSExn("can't get subresources of a byte[] resource"); return new ByteArrayInputStream(bytes); } } @@ -128,7 +128,7 @@ public abstract class Res extends JS { ZipInputStream zis = new ZipInputStream(pis); ZipEntry ze = zis.getNextEntry(); while(ze != null && !ze.getName().equals(path)) ze = zis.getNextEntry(); - if (ze == null) throw new JS.Exn("requested file (" + path + ") not found in archive"); + if (ze == null) throw new JSExn("requested file (" + path + ") not found in archive"); return new KnownLength.KnownLengthInputStream(zis, (int)ze.getSize()); } } diff --git a/src/org/xwt/SOAP.java b/src/org/xwt/SOAP.java index 113e01b..7ad91ef 100644 --- a/src/org/xwt/SOAP.java +++ b/src/org/xwt/SOAP.java @@ -160,7 +160,7 @@ class SOAP extends XMLRPC { } /** Appends the SOAP representation of o to sb */ - void appendObject(String name, Object o, StringBuffer sb) throws JS.Exn { + void appendObject(String name, Object o, StringBuffer sb) throws JSExn { if (o instanceof Number) { if ((double)((Number)o).intValue() == ((Number)o).doubleValue()) { sb.append(" <" + name + " xsi:type=\"xsd:int\">"); @@ -199,7 +199,7 @@ class SOAP extends XMLRPC { } catch (IOException e) { if (Log.on) Log.log(this, "caught IOException while attempting to send a ByteStream via SOAP"); if (Log.on) Log.log(this, e); - throw new JS.Exn("caught IOException while attempting to send a ByteStream via SOAP"); + throw new JSExn("caught IOException while attempting to send a ByteStream via SOAP"); } } else if (o instanceof String) { @@ -240,7 +240,7 @@ class SOAP extends XMLRPC { } } - protected String send(JSArray args, HTTP http) throws JS.Exn, IOException { + protected String send(JSArray args, HTTP http) throws JSExn, IOException { // build up the request StringBuffer content = new StringBuffer(); content.append("SOAPAction: " + action + "\r\n\r\n"); diff --git a/src/org/xwt/Surface.java b/src/org/xwt/Surface.java index 3a083cc..fb9585a 100644 --- a/src/org/xwt/Surface.java +++ b/src/org/xwt/Surface.java @@ -108,7 +108,7 @@ public abstract class Surface extends PixelBuffer { final Box who = Box.whoIs(root, mousex, mousey); Scheduler.add(new Scheduler.Task() { public void perform() { Platform.clipboardReadEnabled = true; - root.putAndTriggerJSTraps("Press3", Boolean.TRUE); + root.putAndTriggerTraps("Press3", Boolean.TRUE); Platform.clipboardReadEnabled = false; }}); } @@ -175,7 +175,7 @@ public abstract class Surface extends PixelBuffer { Box b = (Box)keywatchers.elementAt(i); for(Box cur = b; cur != null; cur = cur.parent) if (!cur.test(cur.VISIBLE)) continue outer; - b.putAndTriggerJSTraps("KeyPressed", key); + b.putAndTriggerTraps("KeyPressed", key); } Platform.clipboardReadEnabled = false; } @@ -195,7 +195,7 @@ public abstract class Surface extends PixelBuffer { Box b = (Box)keywatchers.elementAt(i); for(Box cur = b; cur != null; cur = cur.parent) if (!cur.test(cur.VISIBLE)) continue outer; - b.putAndTriggerJSTraps("KeyReleased", key); + b.putAndTriggerTraps("KeyReleased", key); } }}); } @@ -223,7 +223,7 @@ public abstract class Surface extends PixelBuffer { // Root gets motion events outside itself (if trapped, of course) if (!root.inside(oldmousex, oldmousey) && !root.inside(mousex, mousey) && (button1 || button2 || button3)) - root.putAndTriggerJSTraps("Move", Boolean.TRUE); + root.putAndTriggerTraps("Move", Boolean.TRUE); root.Move(oldmousex, oldmousey, mousex, mousey); if (!cursor.equals(oldcursor)) syncCursor(); @@ -246,7 +246,7 @@ public abstract class Surface extends PixelBuffer { Scheduler.add(new Scheduler.Task() { public void perform() { root.x = x; root.y = y; - root.putAndTriggerJSTraps("PosChange", Boolean.TRUE); + root.putAndTriggerTraps("PosChange", Boolean.TRUE); }}); } @@ -371,7 +371,7 @@ public abstract class Surface extends PixelBuffer { Scheduler.add(this); } - public void perform() { boxContainingMouse.putAndTriggerJSTraps(name, value); } + public void perform() { boxContainingMouse.putAndTriggerTraps(name, value); } public String toString() { return "SimpleMessage [name=" + name + ", value=" + value + "]"; } } diff --git a/src/org/xwt/Template.java b/src/org/xwt/Template.java index d633f48..56ca4d9 100644 --- a/src/org/xwt/Template.java +++ b/src/org/xwt/Template.java @@ -98,7 +98,7 @@ public class Template { if (staticscript == null) return staticJSScope; JSFunction temp = staticscript; staticscript = null; - temp.cloneWithNewParentJSScope(staticJSScope).call(null, null, null, null, 0); + temp.cloneWithNewParentScope(staticJSScope).call(null, null, null, null, 0); return staticJSScope; } @@ -122,15 +122,15 @@ public class Template { for (int i=0; children != null && i 0) - thisscript = JS.parse(t.fileName + (isstatic ? "._" : ""), t.content_start, new StringReader(contentString)); + thisscript = JSFunction.fromReader(t.fileName + (isstatic ? "._" : ""), + t.content_start, + new StringReader(contentString)); } catch (IOException ioe) { if (Log.on) Log.log(this, " ERROR: " + ioe.getMessage()); thisscript = null; @@ -347,8 +341,8 @@ public class Template { declare("$" + key); put("$" + key, target); } - public PerInstantiationJSScope(JSScope parentJSScope, XWT xwt, PerInstantiationJSScope parentBoxPis, JSScope myStatic) { - super(parentJSScope); + public PerInstantiationJSScope(JSScope parentScope, XWT xwt, PerInstantiationJSScope parentBoxPis, JSScope myStatic) { + super(parentScope); this.parentBoxPis = parentBoxPis; this.xwt = xwt; this.myStatic = myStatic; diff --git a/src/org/xwt/XMLRPC.java b/src/org/xwt/XMLRPC.java index efcccf7..cd5b4df 100644 --- a/src/org/xwt/XMLRPC.java +++ b/src/org/xwt/XMLRPC.java @@ -30,7 +30,7 @@ import org.bouncycastle.util.encoders.Base64; * convert. * */ -class XMLRPC extends JSCallable { +class XMLRPC extends JS { /** the url to connect to */ protected String url = null; @@ -170,10 +170,10 @@ class XMLRPC extends JSCallable { // Methods to make outbound XML-RPC request /////////////////////////////////////////////////// /** Appends the XML-RPC representation of o to sb */ - void appendObject(Object o, StringBuffer sb) throws JS.Exn { + void appendObject(Object o, StringBuffer sb) throws JSExn { if (o == null) { - throw new JS.Exn("attempted to send a null value via XML-RPC"); + throw new JSExn("attempted to send a null value via XML-RPC"); } else if (o instanceof Number) { if ((double)((Number)o).intValue() == ((Number)o).doubleValue()) { @@ -212,7 +212,7 @@ class XMLRPC extends JSCallable { } catch (IOException e) { if (Log.on) Log.log(this, "caught IOException while attempting to send a ByteStream via XML-RPC"); if (Log.on) Log.log(this, e); - throw new JS.Exn("caught IOException while attempting to send a ByteStream via XML-RPC"); + throw new JSExn("caught IOException while attempting to send a ByteStream via XML-RPC"); } } else if (o instanceof String) { @@ -255,7 +255,7 @@ class XMLRPC extends JSCallable { sb.append("\n"); } else if (o instanceof JSArray) { - if (tracker.get(o) != null) throw new JS.Exn("attempted to send multi-ref data structure via XML-RPC"); + if (tracker.get(o) != null) throw new JSExn("attempted to send multi-ref data structure via XML-RPC"); tracker.put(o, Boolean.TRUE); sb.append(" \n"); JSArray a = (JSArray)o; @@ -263,7 +263,7 @@ class XMLRPC extends JSCallable { sb.append(" \n"); } else if (o instanceof JS) { - if (tracker.get(o) != null) throw new JS.Exn("attempted to send multi-ref data structure via XML-RPC"); + if (tracker.get(o) != null) throw new JSExn("attempted to send multi-ref data structure via XML-RPC"); tracker.put(o, Boolean.TRUE); JS j = (JS)o; sb.append(" \n"); @@ -277,12 +277,12 @@ class XMLRPC extends JSCallable { sb.append(" \n"); } else { - throw new JS.Exn("attempt to send object of type " + o.getClass().getName() + " via XML-RPC"); + throw new JSExn("attempt to send object of type " + o.getClass().getName() + " via XML-RPC"); } } - public Object call_(JSArray args) throws JS.Exn, IOException { + public Object call_(JSArray args) throws JSExn, IOException { if (Log.verbose) Log.log(this, "call to " + url + " : " + methodname); if (tracker == null) tracker = new Hash(); @@ -323,7 +323,7 @@ class XMLRPC extends JSCallable { } } - protected String send(JSArray args, HTTP http) throws JS.Exn, IOException { + protected String send(JSArray args, HTTP http) throws JSExn, IOException { StringBuffer content = new StringBuffer(); content.append("\r\n"); content.append("\n"); @@ -342,38 +342,50 @@ class XMLRPC extends JSCallable { return content.toString(); } - protected Object recieve(BufferedReader br) throws JS.Exn, IOException { + protected Object recieve(BufferedReader br) throws JSExn, IOException { // parse XML reply try { new Helper().parse(br); } catch (XML.XMLException e) { if (Log.on) Log.log(this, "reply from server was not well-formed XML: " + e); - throw new JS.Exn("reply from server was not well-formed XML: " + e); + throw new JSExn("reply from server was not well-formed XML: " + e); } - if (fault) throw new JS.Exn(objects.elementAt(0)); + if (fault) throw new JSExn(objects.elementAt(0)); if (objects.size() == 0) return null; return objects.elementAt(0); } - public final Object call(Object a0, Object a1, Object a2, Object[] rest, int nargs) throws JS.Exn { + public final Object call(Object a0, Object a1, Object a2, Object[] rest, int nargs) throws JSExn { JSArray args = new JSArray(); for(int i=0; i= 1 ? (JS)obStack.elementAt(0) : null; } diff --git a/src/org/xwt/js/ByteCodes.java b/src/org/xwt/js/ByteCodes.java index d364d8c..e270885 100644 --- a/src/org/xwt/js/ByteCodes.java +++ b/src/org/xwt/js/ByteCodes.java @@ -57,13 +57,13 @@ interface ByteCodes { /** pop an element; push a JS.JSArray containing the keys of the popped element */ public static final byte PUSHKEYS = -16; - /** pops [arg+2] elements, pushes the former top element, then pushes back the rest (retaining order) */ + /** push the top element down so that (arg) elements are on top of it; all other elements retain ordering */ public static final byte SWAP = -17; - /** execute the ForthBlock pointed to by the literal in a fresh scope with parentJSScope==THIS */ + /** execute the bytecode block pointed to by the literal in a fresh scope with parentScope==THIS */ public static final byte NEWSCOPE = -18; - /** execute the ForthBlock pointed to by the literal in a fresh scope with parentJSScope==THIS */ + /** execute the bytecode block pointed to by the literal in a fresh scope with parentScope==THIS */ public static final byte OLDSCOPE = -19; /** push a copy of the top stack element */ diff --git a/src/org/xwt/js/Internal.java b/src/org/xwt/js/Internal.java deleted file mode 100644 index d1ac346..0000000 --- a/src/org/xwt/js/Internal.java +++ /dev/null @@ -1,151 +0,0 @@ -// Copyright 2003 Adam Megacz, see the COPYING file for licensing [GPL] - -package org.xwt.js; -import org.xwt.util.*; - -/** - * Misc static methods used internally by the JS engine - */ -class Internal { - // Package Helper Methods ////////////////////////////////////////////////////////////// - - private static Object wrapInt(int i) { return new Integer(i); } - static Object callMethodOnPrimitive(Object o, Object method, JSArray args) { - int alength = args.length(); - String s; - if(o instanceof Number) { - if(method.equals("toFixed")) throw new JS.Exn("toFixed() not implemented"); - if(method.equals("toExponential")) throw new JS.Exn("toExponential() not implemented"); - if(method.equals("toPrecision")) throw new JS.Exn("toPrecision() not implemented"); - if(method.equals("toString")) { - int radix = alength >= 1 ? JS.toInt(args.elementAt(0)) : 10; - return Long.toString(((Number)o).longValue(),radix); - } - } else if(o instanceof Boolean) { - // No methods for Booleans - } - s = JS.toString(o); - int slength = s.length(); - if(method.equals("substring")) { - int a = alength >= 1 ? JS.toInt(args.elementAt(0)) : 0; - int b = alength >= 2 ? JS.toInt(args.elementAt(1)) : slength; - if(a > slength) a = slength; - if(b > slength) b = slength; - if(a < 0) a = 0; - if(b < 0) b = 0; - if(a > b) { int tmp = a; a = b; b = tmp; } - return s.substring(a,b); - } - if(method.equals("substr")) { - int start = alength >= 1 ? JS.toInt(args.elementAt(0)) : 0; - int len = alength >= 2 ? JS.toInt(args.elementAt(1)) : Integer.MAX_VALUE; - if(start < 0) start = slength + start; - if(start < 0) start = 0; - if(len < 0) len = 0; - if(len > slength - start) len = slength - start; - if(len <= 0) return ""; - return s.substring(start,start+len); - } - if(method.equals("charAt")) { - int p = alength >= 1 ? JS.toInt(args.elementAt(0)) : 0; - if(p < 0 || p >= slength) return ""; - return s.substring(p,p+1); - } - if(method.equals("charCodeAt")) { - int p = alength >= 1 ? JS.toInt(args.elementAt(0)) : 0; - if(p < 0 || p >= slength) return new Double(Double.NaN); - return wrapInt(s.charAt(p)); - } - if(method.equals("concat")) { - StringBuffer sb = new StringBuffer(slength*2).append(s); - for(int i=0;i= 1 ? args.elementAt(0).toString() : "null"; - int start = alength >= 2 ? JS.toInt(args.elementAt(1)) : 0; - // Java's indexOf handles an out of bounds start index, it'll return -1 - return wrapInt(s.indexOf(search,start)); - } - if(method.equals("lastIndexOf")) { - String search = alength >= 1 ? args.elementAt(0).toString() : "null"; - int start = alength >= 2 ? JS.toInt(args.elementAt(1)) : 0; - // Java's indexOf handles an out of bounds start index, it'll return -1 - return wrapInt(s.lastIndexOf(search,start)); - } - if(method.equals("match")) { - return JSRegexp.stringMatch(s,args); - } - if(method.equals("replace")) { - return JSRegexp.stringReplace(s,args); - } - if(method.equals("search")) { - return JSRegexp.stringSearch(s,args); - } - if(method.equals("slice")) { - int a = alength >= 1 ? JS.toInt(args.elementAt(0)) : 0; - int b = alength >= 2 ? JS.toInt(args.elementAt(1)) : slength; - if(a < 0) a = slength + a; - if(b < 0) b = slength + b; - if(a < 0) a = 0; - if(b < 0) b = 0; - if(a > slength) a = slength; - if(b > slength) b = slength; - if(a > b) return ""; - return s.substring(a,b); - } - if(method.equals("split")){ - return JSRegexp.stringSplit(s,args); - } - if(method.equals("toLowerCase")) return s.toLowerCase(); - if(method.equals("toUpperCase")) return s.toUpperCase(); - if(method.equals("toString")) return s; - throw new JS.Exn("Attempted to call non-existent method: " + method); - } - - static Object getFromPrimitive(Object o, Object key) { - boolean returnJSCallable = false; - if(o instanceof Boolean) { - // no properties for Booleans - } else if(o instanceof Number) { - if(key.equals("toPrecision") || key.equals("toExponential") || key.equals("toFixed")) - returnJSCallable = true; - } - if(!returnJSCallable) { - // the string stuff applies to everything - String s = o.toString(); - - if(key.equals("length")) return wrapInt(s.length()); - - // this is sort of ugly, but this list should never change - // These should provide a complete (enough) implementation of the ECMA-262 String object - if(key.equals("substring") || key.equals("charAt") || key.equals("charCodeAt") || key.equals("concat") || - key.equals("indexOf") || key.equals("lastIndexOf") || key.equals("match") || key.equals("replace") || - key.equals("seatch") || key.equals("slice") || key.equals("split") || key.equals("toLowerCase") || - key.equals("toUpperCase") || key.equals("toString") || key.equals("substr") - ) - returnJSCallable = true; - } - if(returnJSCallable) { - final Object target = o; - final String method = key.toString(); - return new JSCallable() { - public Object call(Object a0, Object a1, Object a2, Object[] rest, int nargs) { - JSArray args = new JSArray(); - for(int i=0; i= cx.f.size) return cx.stack.pop(); - int op = cx.f.op[cx.pc]; - Object arg = cx.f.arg[cx.pc]; + if (f == null) return stack.pop(); + int op = f.op[pc]; + Object arg = f.arg[pc]; if(op == FINALLY_DONE) { - FinallyData fd = (FinallyData) cx.stack.pop(); + FinallyData fd = (FinallyData) stack.pop(); if(fd == null) continue OUTER; // NOP op = fd.op; arg = fd.arg; } switch(op) { - case LITERAL: cx.stack.push(arg); break; - case OBJECT: cx.stack.push(new JS()); break; - case ARRAY: cx.stack.push(new JSArray(JS.toNumber(arg).intValue())); break; - case DECLARE: cx.scope.declare((String)(arg==null ? cx.stack.peek() : arg)); if(arg != null) cx.stack.push(arg); break; - case TOPSCOPE: cx.stack.push(cx.scope); break; - case JT: if (JS.toBoolean(cx.stack.pop())) cx.pc += JS.toNumber(arg).intValue() - 1; break; - case JF: if (!JS.toBoolean(cx.stack.pop())) cx.pc += JS.toNumber(arg).intValue() - 1; break; - case JMP: cx.pc += JS.toNumber(arg).intValue() - 1; break; - case POP: cx.stack.pop(); break; + case LITERAL: stack.push(arg); break; + case OBJECT: stack.push(new JS()); break; + case ARRAY: stack.push(new JSArray(JS.toNumber(arg).intValue())); break; + case DECLARE: scope.declare((String)(arg==null ? stack.peek() : arg)); if(arg != null) stack.push(arg); break; + case TOPSCOPE: stack.push(scope); break; + case JT: if (JS.toBoolean(stack.pop())) pc += JS.toNumber(arg).intValue() - 1; break; + case JF: if (!JS.toBoolean(stack.pop())) pc += JS.toNumber(arg).intValue() - 1; break; + case JMP: pc += JS.toNumber(arg).intValue() - 1; break; + case POP: stack.pop(); break; case SWAP: { int depth = (arg == null ? 1 : JS.toInt(arg)); - Object save = cx.stack.elementAt(cx.stack.size() - 1); - for(int i=cx.stack.size() - 1; i > cx.stack.size() - 1 - depth; i--) - cx.stack.setElementAt(cx.stack.elementAt(i-1), i); - cx.stack.setElementAt(save, cx.stack.size() - depth - 1); + Object save = stack.elementAt(stack.size() - 1); + for(int i=stack.size() - 1; i > stack.size() - 1 - depth; i--) + stack.setElementAt(stack.elementAt(i-1), i); + stack.setElementAt(save, stack.size() - depth - 1); break; } - case DUP: cx.stack.push(cx.stack.peek()); break; - case NEWSCOPE: cx.scope = new JSScope(cx.scope); break; - case OLDSCOPE: cx.scope = cx.scope.getParentJSScope(); break; - case ASSERT: if (!JS.toBoolean(cx.stack.pop())) throw je("assertion failed"); break; - case BITNOT: cx.stack.push(new Long(~JS.toLong(cx.stack.pop()))); break; - case BANG: cx.stack.push(new Boolean(!JS.toBoolean(cx.stack.pop()))); break; - case NEWFUNCTION: cx.stack.push(((JSFunction)arg).cloneWithNewParentJSScope(cx.scope)); break; + case DUP: stack.push(stack.peek()); break; + case NEWSCOPE: scope = new JSScope(scope); break; + case OLDSCOPE: scope = scope.getParentScope(); break; + case ASSERT: if (!JS.toBoolean(stack.pop())) throw je("assertion failed"); break; + case BITNOT: stack.push(JS.N(~JS.toLong(stack.pop()))); break; + case BANG: stack.push(JS.B(!JS.toBoolean(stack.pop()))); break; + case NEWFUNCTION: stack.push(((JSFunction)arg).cloneWithNewParentScope(scope)); break; case LABEL: break; case TYPEOF: { - Object o = cx.stack.pop(); - if (o == null) cx.stack.push(null); - else if (o instanceof JS) cx.stack.push(((JS)o).typeName()); - else if (o instanceof String) cx.stack.push("string"); - else if (o instanceof Number) cx.stack.push("number"); - else if (o instanceof Boolean) cx.stack.push("boolean"); - else cx.stack.push("unknown"); + Object o = stack.pop(); + if (o == null) stack.push(null); + else if (o instanceof JS) stack.push(((JS)o).typeName()); + else if (o instanceof String) stack.push("string"); + else if (o instanceof Number) stack.push("number"); + else if (o instanceof Boolean) stack.push("boolean"); + else stack.push("unknown"); break; } case PUSHKEYS: { - Object o = cx.stack.peek(); + Object o = stack.peek(); Enumeration e = ((JS)o).keys(); JSArray a = new JSArray(); while(e.hasMoreElements()) a.addElement(e.nextElement()); - cx.stack.push(a); + stack.push(a); break; } case LOOP: - cx.stack.push(new LoopMarker(cx.pc, cx.pc > 0 && cx.f.op[cx.pc - 1] == LABEL ? - (String)cx.f.arg[cx.pc - 1] : (String)null, cx.scope)); - cx.stack.push(Boolean.TRUE); + stack.push(new LoopMarker(pc, pc > 0 && f.op[pc - 1] == LABEL ? (String)f.arg[pc - 1] : (String)null, scope)); + stack.push(Boolean.TRUE); break; case BREAK: case CONTINUE: - while(cx.stack.size() > 0) { - Object o = cx.stack.pop(); - if (o instanceof CallMarker) ee("break or continue not within a loop"); + while(stack.size() > 0) { + Object o = stack.pop(); + if (o instanceof CallMarker) je("break or continue not within a loop"); if (o instanceof TryMarker) { if(((TryMarker)o).finallyLoc < 0) continue; // no finally block, keep going - cx.stack.push(new FinallyData(op, arg)); - cx.scope = ((TryMarker)o).scope; - cx.pc = ((TryMarker)o).finallyLoc - 1; + stack.push(new FinallyData(op, arg)); + scope = ((TryMarker)o).scope; + pc = ((TryMarker)o).finallyLoc - 1; continue OUTER; } if (o instanceof LoopMarker) { if (arg == null || arg.equals(((LoopMarker)o).label)) { int loopInstructionLocation = ((LoopMarker)o).location; - int endOfLoop = ((Integer)cx.f.arg[loopInstructionLocation]).intValue() + loopInstructionLocation; - cx.scope = ((LoopMarker)o).scope; - if (op == CONTINUE) { cx.stack.push(o); cx.stack.push(Boolean.FALSE); } - cx.pc = op == BREAK ? endOfLoop - 1 : loopInstructionLocation; + int endOfLoop = ((Integer)f.arg[loopInstructionLocation]).intValue() + loopInstructionLocation; + scope = ((LoopMarker)o).scope; + if (op == CONTINUE) { stack.push(o); stack.push(Boolean.FALSE); } + pc = op == BREAK ? endOfLoop - 1 : loopInstructionLocation; continue OUTER; } } } throw new Error("CONTINUE/BREAK invoked but couldn't find LoopMarker at " + - cx.getSourceName() + ":" + cx.getLine()); + JS.getSourceName() + ":" + JS.getLine()); case TRY: { int[] jmps = (int[]) arg; // jmps[0] is how far away the catch block is, jmps[1] is how far away the finally block is // each can be < 0 if the specified block does not exist - cx.stack.push(new TryMarker(jmps[0] < 0 ? -1 : cx.pc + jmps[0], jmps[1] < 0 ? -1 : cx.pc + jmps[1], cx.scope)); + stack.push(new TryMarker(jmps[0] < 0 ? -1 : pc + jmps[0], jmps[1] < 0 ? -1 : pc + jmps[1], scope)); break; } case RETURN: { - Object retval = cx.stack.pop(); - while(cx.stack.size() > 0) { - Object o = cx.stack.pop(); + Object retval = stack.pop(); + while(stack.size() > 0) { + Object o = stack.pop(); if (o instanceof TryMarker) { if(((TryMarker)o).finallyLoc < 0) continue; - cx.stack.push(retval); - cx.stack.push(new FinallyData(RETURN)); - cx.scope = ((TryMarker)o).scope; - cx.pc = ((TryMarker)o).finallyLoc - 1; + stack.push(retval); + stack.push(new FinallyData(RETURN)); + scope = ((TryMarker)o).scope; + pc = ((TryMarker)o).finallyLoc - 1; continue OUTER; } else if (o instanceof CallMarker) { - if (cx.scope instanceof JSTrap.JSTrapScope) { - JSTrap.JSTrapScope ts = (JSTrap.JSTrapScope)cx.scope; + if (scope instanceof Trap.TrapScope) { + Trap.TrapScope ts = (Trap.TrapScope)scope; if (!ts.cascadeHappened) { ts.cascadeHappened = true; - JSTrap t = ts.t.next; + Trap t = ts.t.next; while (t != null && t.f.numFormalArgs == 0) t = t.next; if (t == null) { ((JS)ts.t.trapee).put(t.name, ts.val); - if (cx.pausecount > initialPauseCount) return null; // we were paused + if (pausecount > initialPauseCount) { pc++; return null; } // we were paused } else { - cx.stack.push(o); + stack.push(o); JSArray args = new JSArray(); args.addElement(ts.val); - cx.stack.push(args); - cx.f = t.f; - cx.scope = new JSTrap.JSTrapScope(cx.f.parentJSScope, t, ts.val); - cx.pc = -1; + stack.push(args); + f = t.f; + scope = new Trap.TrapScope(f.parentScope, t, ts.val); + pc = -1; break; } } } - cx.scope = ((CallMarker)o).scope; - cx.pc = ((CallMarker)o).pc; - cx.f = (JSFunction)((CallMarker)o).f; - cx.stack.push(retval); + scope = ((CallMarker)o).scope; + pc = ((CallMarker)o).pc; + f = (JSFunction)((CallMarker)o).f; + stack.push(retval); continue OUTER; } } @@ -150,39 +192,39 @@ class Interpreter implements ByteCodes, Tokens { } case PUT: { - Object val = cx.stack.pop(); - Object key = cx.stack.pop(); - Object target = cx.stack.peek(); + Object val = stack.pop(); + Object key = stack.pop(); + Object target = stack.peek(); if (target == null) throw je("tried to put a value to the " + key + " property on the null value"); if (!(target instanceof JS)) throw je("tried to put a value to the " + key + " property on a " + target.getClass().getName()); if (key == null) throw je("tried to assign \"" + (val==null?"(null)":val.toString()) + "\" to the null key"); - JSTrap t = null; - if (target instanceof JSTrap.JSTrappable) { - t = ((JSTrap.JSTrappable)target).getTrap(val); + Trap t = null; + if (target instanceof JS) { + t = ((JS)target).getTrap(val); while (t != null && t.f.numFormalArgs == 0) t = t.next; - } else if (target instanceof JSTrap.JSTrapScope && key.equals("cascade")) { - JSTrap.JSTrapScope ts = (JSTrap.JSTrapScope)target; + } else if (target instanceof Trap.TrapScope && key.equals("cascade")) { + Trap.TrapScope ts = (Trap.TrapScope)target; t = ts.t.next; ts.cascadeHappened = true; while (t != null && t.f.numFormalArgs == 0) t = t.next; if (t == null) target = ts.t.trapee; } if (t != null) { - cx.stack.push(new CallMarker(cx)); + stack.push(new CallMarker(this)); JSArray args = new JSArray(); args.addElement(val); - cx.stack.push(args); - cx.f = t.f; - cx.scope = new JSTrap.JSTrapScope(cx.f.parentJSScope, t, val); - cx.pc = -1; + stack.push(args); + f = t.f; + scope = new Trap.TrapScope(f.parentScope, t, val); + pc = -1; break; } ((JS)target).put(key, val); - if (cx.pausecount > initialPauseCount) return null; // we were paused - cx.stack.push(val); + if (pausecount > initialPauseCount) { pc++; return null; } // we were paused + stack.push(val); break; } @@ -190,44 +232,44 @@ class Interpreter implements ByteCodes, Tokens { case GET_PRESERVE: { Object o, v; if (op == GET) { - v = arg == null ? cx.stack.pop() : arg; - o = cx.stack.pop(); + v = arg == null ? stack.pop() : arg; + o = stack.pop(); } else { - v = cx.stack.pop(); - o = cx.stack.peek(); - cx.stack.push(v); + v = stack.pop(); + o = stack.peek(); + stack.push(v); } Object ret = null; if (v == null) throw je("tried to get the null key from " + o); - if (o == null) throw je("tried to get property \"" + v + "\" from the null value @" + cx.pc + "\n" + cx.f.dump()); + if (o == null) throw je("tried to get property \"" + v + "\" from the null value @" + pc + "\n" + f.dump()); if (o instanceof String || o instanceof Number || o instanceof Boolean) { - ret = Internal.getFromPrimitive(o,v); - cx.stack.push(ret); + ret = getFromPrimitive(o,v); + stack.push(ret); break; } else if (o instanceof JS) { - JSTrap t = null; - if (o instanceof JSTrap.JSTrappable) { - t = ((JSTrap.JSTrappable)o).getTrap(v); + Trap t = null; + if (o instanceof JS) { + t = ((JS)o).getTrap(v); while (t != null && t.f.numFormalArgs != 0) t = t.next; - } else if (o instanceof JSTrap.JSTrapScope && v.equals("cascade")) { - t = ((JSTrap.JSTrapScope)o).t.next; + } else if (o instanceof Trap.TrapScope && v.equals("cascade")) { + t = ((Trap.TrapScope)o).t.next; while (t != null && t.f.numFormalArgs != 0) t = t.next; - if (t == null) o = ((JSTrap.JSTrapScope)o).t.trapee; + if (t == null) o = ((Trap.TrapScope)o).t.trapee; } if (t != null) { - cx.stack.push(new CallMarker(cx)); + stack.push(new CallMarker(this)); JSArray args = new JSArray(); - cx.stack.push(args); - cx.f = t.f; - cx.scope = new JSTrap.JSTrapScope(cx.f.parentJSScope, t, null); - ((JSTrap.JSTrapScope)cx.scope).cascadeHappened = true; - cx.pc = -1; + stack.push(args); + f = t.f; + scope = new Trap.TrapScope(f.parentScope, t, null); + ((Trap.TrapScope)scope).cascadeHappened = true; + pc = -1; break; } ret = ((JS)o).get(v); - if (ret == JSCallable.METHOD) ret = new Internal.JSCallableStub((JS)o, v); - if (cx.pausecount > initialPauseCount) return null; // we were paused - cx.stack.push(ret); + if (ret == JS.METHOD) ret = new Stub((JS)o, v); + if (pausecount > initialPauseCount) { pc++; return null; } // we were paused + stack.push(ret); break; } throw je("tried to get property " + v + " from a " + o.getClass().getName()); @@ -237,81 +279,80 @@ class Interpreter implements ByteCodes, Tokens { int numArgs = JS.toInt(arg); Object method = null; Object ret = null; - Object object = cx.stack.pop(); + Object object = stack.pop(); - if (op == CALL) { - object = cx.stack.pop(); - } else { - if (object == JSCallable.METHOD) { - method = cx.stack.pop(); - object = cx.stack.pop(); + if (op == CALLMETHOD) { + if (object == JS.METHOD) { + method = stack.pop(); + object = stack.pop(); } else { - cx.stack.pop(); - cx.stack.pop(); + stack.pop(); + stack.pop(); } } if (object instanceof String || object instanceof Number || object instanceof Boolean) { - JSArray arguments = new JSArray(); - for(int j=numArgs - 1; j >= 0; j--) arguments.setElementAt(cx.stack.pop(), j); - ret = Internal.callMethodOnPrimitive(object, method, arguments); + if (numArgs > 2) throw new JSExn("too many arguments to primitive method"); + Object arg1 = numArgs >= 2 ? stack.pop() : null; + Object arg0 = numArgs >= 1 ? stack.pop() : null; + ret = callMethodOnPrimitive(object, method, arg0, arg1, numArgs); } else if (object instanceof JSFunction) { // FEATURE: use something similar to call0/call1/call2 here JSArray arguments = new JSArray(); - for(int j=numArgs - 1; j >= 0; j--) arguments.setElementAt(cx.stack.pop(), j); - cx.stack.push(new CallMarker(cx)); - cx.stack.push(arguments); - cx.f = (JSFunction)object; - cx.scope = new JSScope(cx.f.parentJSScope); - cx.pc = -1; + for(int j=numArgs - 1; j >= 0; j--) arguments.setElementAt(stack.pop(), j); + stack.push(new CallMarker(this)); + stack.push(arguments); + f = (JSFunction)object; + scope = new JSScope(f.parentScope); + pc = -1; break; - } else if (object instanceof JSCallable) { - JSCallable c = (JSCallable)object; + } else if (object instanceof JS) { + JS c = (JS)object; Object[] rest = numArgs > 3 ? new Object[numArgs - 3] : null; - for(int i=numArgs - 1; i>2; i--) rest[i-3] = cx.stack.pop(); - Object a2 = numArgs <= 2 ? null : cx.stack.pop(); - Object a1 = numArgs <= 1 ? null : cx.stack.pop(); - Object a0 = numArgs <= 0 ? null : cx.stack.pop(); + for(int i=numArgs - 1; i>2; i--) rest[i-3] = stack.pop(); + Object a2 = numArgs <= 2 ? null : stack.pop(); + Object a1 = numArgs <= 1 ? null : stack.pop(); + Object a0 = numArgs <= 0 ? null : stack.pop(); ret = method == null ? c.call(a0, a1, a2, rest, numArgs) : c.callMethod(method, a0, a1, a2, rest, numArgs); } else { - throw new JS.Exn("can't call a " + object.getClass().getName() + " @" + cx.pc + "\n" + cx.f.dump()); + throw new JSExn("can't call a " + object + " @" + pc + "\n" + f.dump()); } - if (cx.pausecount > initialPauseCount) return null; - cx.stack.push(ret); + if (pausecount > initialPauseCount) { pc++; return null; } + stack.push(ret); break; } case THROW: { - Object o = cx.stack.pop(); - if(o instanceof JS.Exn) throw (JS.Exn)o; - throw new JS.Exn(o); + Object o = stack.pop(); + if(o instanceof JSExn) throw (JSExn)o; + throw new JSExn(o); } case ASSIGN_SUB: case ASSIGN_ADD: { - Object val = cx.stack.pop(); - Object old = cx.stack.pop(); - Object key = cx.stack.pop(); - Object obj = cx.stack.peek(); + Object val = stack.pop(); + Object old = stack.pop(); + Object key = stack.pop(); + Object obj = stack.peek(); if (val instanceof JSFunction && obj instanceof JSScope) { JSScope parent = (JSScope)obj; - while(parent.getParentJSScope() != null) parent = parent.getParentJSScope(); - if (parent instanceof JSTrap.JSTrappable) { - JSTrap.JSTrappable b = (JSTrap.JSTrappable)parent; - if (op == ASSIGN_ADD) JSTrap.addTrap(b, key, (JSFunction)val); - else JSTrap.delTrap(b, key, (JSFunction)val); + while(parent.getParentScope() != null) parent = parent.getParentScope(); + if (parent instanceof JS) { + JS b = (JS)parent; + if (op == ASSIGN_ADD) b.addTrap(key, (JSFunction)val); + else b.delTrap(key, (JSFunction)val); // skip over the "normal" implementation of +=/-= - cx.pc += ((Integer)arg).intValue() - 1; + pc += ((Integer)arg).intValue() - 1; break; } } // use the "normal" implementation - cx.stack.push(key); - cx.stack.push(old); - cx.stack.push(val); + stack.push(key); + stack.push(old); + stack.push(val); break; } @@ -320,25 +361,25 @@ class Interpreter implements ByteCodes, Tokens { if(count < 2) throw new Error("this should never happen"); if(count == 2) { // common case - Object right = cx.stack.pop(); - Object left = cx.stack.pop(); + Object right = stack.pop(); + Object left = stack.pop(); if(left instanceof String || right instanceof String) - cx.stack.push(JS.toString(left).concat(JS.toString(right))); - else cx.stack.push(new Double(JS.toDouble(left) + JS.toDouble(right))); + stack.push(JS.toString(left).concat(JS.toString(right))); + else stack.push(JS.N(JS.toDouble(left) + JS.toDouble(right))); } else { Object[] args = new Object[count]; - while(--count >= 0) args[count] = cx.stack.pop(); + while(--count >= 0) args[count] = stack.pop(); if(args[0] instanceof String) { StringBuffer sb = new StringBuffer(64); for(int i=0;i> JS.toLong(right))); break; - case URSH: cx.stack.push(new Long(JS.toLong(left) >>> JS.toLong(right))); break; + case LSH: stack.push(JS.N(JS.toLong(left) << JS.toLong(right))); break; + case RSH: stack.push(JS.N(JS.toLong(left) >> JS.toLong(right))); break; + case URSH: stack.push(JS.N(JS.toLong(left) >>> JS.toLong(right))); break; case LT: case LE: case GT: case GE: { - if (left == null) left = new Integer(0); - if (right == null) right = new Integer(0); + if (left == null) left = JS.N(0); + if (right == null) right = JS.N(0); int result = 0; if (left instanceof String || right instanceof String) { result = left.toString().compareTo(right.toString()); } else { result = (int)java.lang.Math.ceil(JS.toDouble(left) - JS.toDouble(right)); } - cx.stack.push(new Boolean((op == LT && result < 0) || (op == LE && result <= 0) || - (op == GT && result > 0) || (op == GE && result >= 0))); + stack.push(JS.B((op == LT && result < 0) || (op == LE && result <= 0) || + (op == GT && result > 0) || (op == GE && result >= 0))); break; } @@ -397,39 +438,39 @@ class Interpreter implements ByteCodes, Tokens { if (l == null) { Object tmp = r; r = l; l = tmp; } if (l == null && r == null) ret = true; else if (r == null) ret = false; // l != null, so its false - else if (l instanceof Boolean) ret = new Boolean(JS.toBoolean(r)).equals(l); + else if (l instanceof Boolean) ret = JS.B(JS.toBoolean(r)).equals(l); else if (l instanceof Number) ret = JS.toNumber(r).doubleValue() == JS.toNumber(l).doubleValue(); else if (l instanceof String) ret = r != null && l.equals(r.toString()); else ret = l.equals(r); - cx.stack.push(new Boolean(op == EQ ? ret : !ret)); break; + stack.push(JS.B(op == EQ ? ret : !ret)); break; } default: throw new Error("unknown opcode " + op); } } } - } catch(JS.Exn e) { - while(cx.stack.size() > 0) { - Object o = cx.stack.pop(); + } catch(JSExn e) { + while(stack.size() > 0) { + Object o = stack.pop(); if (o instanceof CatchMarker || o instanceof TryMarker) { boolean inCatch = o instanceof CatchMarker; if(inCatch) { - o = cx.stack.pop(); + o = stack.pop(); if(((TryMarker)o).finallyLoc < 0) continue; // no finally block, keep going } if(!inCatch && ((TryMarker)o).catchLoc >= 0) { // run the catch block, this will implicitly run the finally block, if it exists - cx.stack.push(o); - cx.stack.push(catchMarker); - cx.stack.push(e.getObject()); - cx.scope = ((TryMarker)o).scope; - cx.pc = ((TryMarker)o).catchLoc - 1; + stack.push(o); + stack.push(catchMarker); + stack.push(e.getObject()); + scope = ((TryMarker)o).scope; + pc = ((TryMarker)o).catchLoc - 1; continue OUTER; } else { - cx.stack.push(e); - cx.stack.push(new FinallyData(THROW)); - cx.scope = ((TryMarker)o).scope; - cx.pc = ((TryMarker)o).finallyLoc - 1; + stack.push(e); + stack.push(new FinallyData(THROW)); + scope = ((TryMarker)o).scope; + pc = ((TryMarker)o).finallyLoc - 1; continue OUTER; } } @@ -441,15 +482,6 @@ class Interpreter implements ByteCodes, Tokens { } // end for } - // Exception Stuff //////////////////////////////////////////////////////////////// - - static class EvaluatorException extends RuntimeException { public EvaluatorException(String s) { super(s); } } - static EvaluatorException ee(String s) { - throw new EvaluatorException(JSContext.getSourceName() + ":" + JSContext.getLine() + " " + s); - } - static JS.Exn je(String s) { - throw new JS.Exn(JSContext.getSourceName() + ":" + JSContext.getLine() + " " + s); - } // Markers ////////////////////////////////////////////////////////////////////// @@ -458,7 +490,7 @@ class Interpreter implements ByteCodes, Tokens { int pc; JSScope scope; JSFunction f; - public CallMarker(JSContext cx) { pc = cx.pc + 1; scope = cx.scope; f = cx.f; } + public CallMarker(Interpreter cx) { pc = cx.pc + 1; scope = cx.scope; f = cx.f; } } public static class CatchMarker { public CatchMarker() { } } @@ -490,4 +522,155 @@ class Interpreter implements ByteCodes, Tokens { public FinallyData(int op, Object arg) { this.op = op; this.arg = arg; } public FinallyData(int op) { this(op,null); } } + + + // Operations on Primitives ////////////////////////////////////////////////////////////////////// + + static Object callMethodOnPrimitive(Object o, Object method, Object arg0, Object arg1, int alength) { + if (o instanceof Number) { + //#switch(method) + case "toFixed": throw new JSExn("toFixed() not implemented"); + case "toExponential": throw new JSExn("toExponential() not implemented"); + case "toPrecision": throw new JSExn("toPrecision() not implemented"); + case "toString": { + int radix = alength >= 1 ? JS.toInt(arg0) : 10; + return Long.toString(((Number)o).longValue(),radix); + } + //#end + } else if (o instanceof Boolean) { + // No methods for Booleans + throw new JSExn("attempt to call a method on a Boolean"); + } + + String s = JS.toString(o); + int slength = s.length(); + //#switch(method) + case "substring": { + int a = alength >= 1 ? JS.toInt(arg0) : 0; + int b = alength >= 2 ? JS.toInt(arg1) : slength; + if (a > slength) a = slength; + if (b > slength) b = slength; + if (a < 0) a = 0; + if (b < 0) b = 0; + if (a > b) { int tmp = a; a = b; b = tmp; } + return s.substring(a,b); + } + case "substr": { + int start = alength >= 1 ? JS.toInt(arg0) : 0; + int len = alength >= 2 ? JS.toInt(arg1) : Integer.MAX_VALUE; + if (start < 0) start = slength + start; + if (start < 0) start = 0; + if (len < 0) len = 0; + if (len > slength - start) len = slength - start; + if (len <= 0) return ""; + return s.substring(start,start+len); + } + case "charAt": { + int p = alength >= 1 ? JS.toInt(arg0) : 0; + if (p < 0 || p >= slength) return ""; + return s.substring(p,p+1); + } + case "charCodeAt": { + int p = alength >= 1 ? JS.toInt(arg0) : 0; + if (p < 0 || p >= slength) return JS.N(Double.NaN); + return JS.N(s.charAt(p)); + } + case "concat": { + // FIXME takes variable number of arguments... + /* + StringBuffer sb = new StringBuffer(slength*2).append(s); + for(int i=0;i= 1 ? arg0.toString() : "null"; + int start = alength >= 2 ? JS.toInt(arg1) : 0; + // Java's indexOf handles an out of bounds start index, it'll return -1 + return JS.N(s.indexOf(search,start)); + } + case "lastIndexOf": { + String search = alength >= 1 ? arg0.toString() : "null"; + int start = alength >= 2 ? JS.toInt(arg1) : 0; + // Java's indexOf handles an out of bounds start index, it'll return -1 + return JS.N(s.lastIndexOf(search,start)); + } + case "match": return JSRegexp.stringMatch(s,arg0); + case "replace": return JSRegexp.stringReplace(s,(String)arg0,arg1); + case "search": return JSRegexp.stringSearch(s,arg0); + case "split": return JSRegexp.stringSplit(s,(String)arg0,arg1); + case "toLowerCase": return s.toLowerCase(); + case "toUpperCase": return s.toUpperCase(); + case "toString": return s; + case "slice": { + int a = alength >= 1 ? JS.toInt(arg0) : 0; + int b = alength >= 2 ? JS.toInt(arg1) : slength; + if (a < 0) a = slength + a; + if (b < 0) b = slength + b; + if (a < 0) a = 0; + if (b < 0) b = 0; + if (a > slength) a = slength; + if (b > slength) b = slength; + if (a > b) return ""; + return s.substring(a,b); + } + //#end + throw new JSExn("Attempted to call non-existent method: " + method); + } + + static Object getFromPrimitive(Object o, Object key) { + boolean returnJS = false; + if (o instanceof Boolean) { + throw new JSExn("cannot call methods on Booleans"); + } else if (o instanceof Number) { + if (key.equals("toPrecision") || key.equals("toExponential") || key.equals("toFixed")) + returnJS = true; + } + if (!returnJS) { + // the string stuff applies to everything + String s = o.toString(); + + // this is sort of ugly, but this list should never change + // These should provide a complete (enough) implementation of the ECMA-262 String object + + //#switch(key) + case "length": return JS.N(s.length()); + case "substring": returnJS = true; break; + case "charAt": returnJS = true; break; + case "charCodeAt": returnJS = true; break; + case "concat": returnJS = true; break; + case "indexOf": returnJS = true; break; + case "lastIndexOf": returnJS = true; break; + case "match": returnJS = true; break; + case "replace": returnJS = true; break; + case "seatch": returnJS = true; break; + case "slice": returnJS = true; break; + case "split": returnJS = true; break; + case "toLowerCase": returnJS = true; break; + case "toUpperCase": returnJS = true; break; + case "toString": returnJS = true; break; + case "substr": returnJS = true; break; + //#end + } + if (returnJS) { + final Object target = o; + final String method = key.toString(); + return new JS() { + public Object call(Object a0, Object a1, Object a2, Object[] rest, int nargs) { + if (nargs > 2) throw new JSExn("cannot call that method with that many arguments"); + return callMethodOnPrimitive(target, method, a0, a1, nargs); + } + }; + } + return null; + } + + private static class Stub extends JS { + private Object method; + JS obj; + public Stub(JS obj, Object method) { this.obj = obj; this.method = method; } + public Object call(Object a0, Object a1, Object a2, Object[] rest, int nargs) { + return ((JS)obj).callMethod(method, a0, a1, a2, rest, nargs); + } + } } diff --git a/src/org/xwt/js/JS.java b/src/org/xwt/js/JS.java index 327d35c..f227f53 100644 --- a/src/org/xwt/js/JS.java +++ b/src/org/xwt/js/JS.java @@ -1,25 +1,59 @@ // Copyright 2003 Adam Megacz, see the COPYING file for licensing [GPL] - package org.xwt.js; + import org.xwt.util.*; import org.xwt.*; import java.io.*; import java.util.*; -/** - * The public API for the JS engine. JS itself is actually a class - * implementing the absolute minimal amount of functionality for an - * Object which can be manipulated by JavaScript code. - */ +// FIXME: grafts +/** The minimum set of functionality required for objects which are manipulated by JavaScript */ public class JS { - // Public Helper Methods ////////////////////////////////////////////////////////////////////// + // Static Interpreter Control Methods /////////////////////////////////////////////////////////////// + + public static int getLine() { + Interpreter c = Interpreter.current(); + return c.f == null || c.pc < 0 || c.pc >= c.f.size ? -1 : c.f.line[c.pc]; + } + + public static String getSourceName() { + Interpreter c = Interpreter.current(); + return c.f == null ? null : c.f.sourceName; + } + + public static class PausedException extends Exception { PausedException() { } } + + public static void invokePauseable(JSFunction function) throws JS.PausedException { + Interpreter i = new Interpreter(function, true, new JSArray()); + int oldpausecount = i.pausecount; + i.resume(); + if (i.pausecount > oldpausecount) throw new PausedException(); + } + + public static class NotPauseableException extends Exception { NotPauseableException() { } } - /** parse and compile a function */ - public static JSFunction parse(String sourceName, int firstLine, Reader sourceCode) throws IOException { - return new JSFunction(sourceName, firstLine, sourceCode, null); + /** returns a callback which will restart the context; expects a value to be pushed onto the stack when unpaused */ + public static UnpauseCallback pause() throws NotPauseableException { + Interpreter i = Interpreter.current(); + if (i.pausecount == -1) throw new NotPauseableException(); + i.pausecount++; + return new JS.UnpauseCallback(i); } + public static class UnpauseCallback { + Interpreter i; + UnpauseCallback(Interpreter i) { this.i = i; } + public void unpause(Object o) throws PausedException { + i.stack.push(o); + i.resume(); + } + } + + + + // Static Helper Methods /////////////////////////////////////////////////////////////////////////////////// + /** coerce an object to a Boolean */ public static boolean toBoolean(Object o) { if (o == null) return false; @@ -46,14 +80,14 @@ public class JS { /** coerce an object to a Number */ public static Number toNumber(Object o) { - if (o == null) return new Long(0); + if (o == null) return ZERO; if (o instanceof Number) return ((Number)o); // NOTE: There are about 3 pages of rules in ecma262 about string to number conversions // We aren't even close to following all those rules. We probably never will be. if (o instanceof String) - try { return new Double((String)o); } catch (NumberFormatException e) { return new Double(Double.NaN); } - if (o instanceof Boolean) return ((Boolean)o).booleanValue() ? new Long(1) : new Long(0); + try { return N((String)o); } catch (NumberFormatException e) { return N(Double.NaN); } + if (o instanceof Boolean) return ((Boolean)o).booleanValue() ? N(1) : ZERO; if (o instanceof JS) return ((JS)o).coerceToNumber(); throw new Error("toNumber() got object of type " + o.getClass().getName() + " which we don't know how to handle"); } @@ -71,18 +105,23 @@ public class JS { } return o.toString(); } + // Instance Methods //////////////////////////////////////////////////////////////////// + + public static final Integer ZERO = new Integer(0); // this gets around a wierd fluke in the Java type checking rules for ?..: public static final Object T = Boolean.TRUE; public static final Object F = Boolean.FALSE; - // FEATURE: be smart here; perhaps intern + // FIXME: be much smarter here public static final Number N(int i) { return new Integer(i); } public static final Number N(long l) { return new Long(l); } public static final Number N(double d) { return new Double(d); } + public static final Number N(String s) { return new Double(s); } public static final Boolean B(boolean b) { return b ? Boolean.TRUE : Boolean.FALSE; } + public static final Boolean B(int i) { return i==0 ? Boolean.FALSE : Boolean.TRUE; } private static Enumeration emptyEnumeration = new Enumeration() { public boolean hasMoreElements() { return false; } @@ -94,81 +133,71 @@ public class JS { public Object get(Object key) { return entries == null ? null : entries.get(key, null); } public void put(Object key, Object val) { if (entries == null) entries = new Hash(); entries.put(key, null, val); } - // note that we don't actually implement trappable... we just provide these for subclasses which choose to - public JSTrap getTrap(Object key) { return entries == null ? null : (JSTrap)entries.get(key, JSTrap.class); } - public void putTrap(Object key, JSTrap t) { if (entries == null) entries = new Hash(); entries.put(key, JSTrap.class, t); } - public Number coerceToNumber() { throw new JS.Exn("tried to coerce a JavaScript object to a Number"); } - public String coerceToString() { throw new JS.Exn("tried to coerce a JavaScript object to a String"); } - public boolean coerceToBoolean() { throw new JS.Exn("tried to coerce a JavaScript object to a Boolean"); } + // Trap support ////////////////////////////////////////////////////////////////////////////// - public String typeName() { return "object"; } + /** override and return true to allow placing traps on this object */ + protected boolean isTrappable() { return false; } + /** performs a put, triggering traps if present; traps are run in an unpauseable interpreter */ + public final void putAndTriggerTraps(Object key, Object value) { + Trap t = getTrap(key); + if (t != null) t.invoke(key, value); + else put(key, value); + } - // Inner Classes ///////////////////////////////////////////////////////////////////////// + /** performs a get, triggering traps if present; traps are run in an unpauseable interpreter */ + public final Object getAndTriggerTraps(Object key) { + Trap t = getTrap(key); + if (t != null) return t.invoke(key); + else return get(key); + } - /** An exception which can be thrown and caught by JavaScript code */ - public static class Exn extends RuntimeException { - private Object js = null; - public Exn(Object js) { this.js = js; } - public String toString() { return "JS.Exn: " + js; } - public String getMessage() { return toString(); } - public Object getObject() { return js; } - } + /** retrieve a trap from the entries hash */ + protected final Trap getTrap(Object key) { + return !isTrappable() || entries == null ? null : (Trap)entries.get(key, Trap.class); + } - /** the result of a graft */ - // FIXME: uber-broken - public static class Graft extends JSCallable { - private JS graftee; - private Object replaced_key; - private Object replaced_val; - public Graft(Object graftee, Object key, Object val) { - if (graftee instanceof JSArray) throw new JS.Exn("can't graft onto JSArrays (yet)"); - if (graftee instanceof JSCallable) throw new JS.Exn("can't graft onto JSCallables (yet)"); - if (graftee instanceof JSScope) throw new JS.Exn("can't graft onto JSScopes (yet)"); - this.graftee = (JS)graftee; - replaced_key = key; - replaced_val = val; - } - public boolean equals(Object o) { return (this == o || graftee.equals(o)); } - public int hashCode() { return graftee.hashCode(); } - public Object get(Object key) { return replaced_key.equals(key) ? replaced_val : graftee.get(key); } - public void put(Object key, Object val) { graftee.put(key, val); } - /* - public Object call(Object method, JSArray args) { - if (replaced_key.equals(method)) return ((JSCallable)replaced_val).call(null, args); - else if (graftee instanceof JSCallable) return ((JSCallable)graftee).call(method, args); - else throw new JS.Exn("cannot call this value"); - } - */ - public Number coerceToNumber() { return graftee.coerceToNumber(); } - public String coerceToString() { return graftee.coerceToString(); } - public boolean coerceToBoolean() { return graftee.coerceToBoolean(); } - public String typeName() { return graftee.typeName(); } - public Enumeration keys() { - return new Enumeration() { - Enumeration graftee_enumeration = graftee.keys(); - boolean returned_replaced = false; - public boolean hasMoreElements() { - if (graftee_enumeration.hasMoreElements()) return true; - return !returned_replaced; - } - public Object nextElement() { - if (!graftee_enumeration.hasMoreElements()) { - if (returned_replaced) throw new NoSuchElementException(); - returned_replaced = true; - return replaced_key; - } else { - Object ret = graftee_enumeration.nextElement(); - if (!returned_replaced && ret.equals(replaced_key)) returned_replaced = true; - return ret; - } - } - }; - } + /** retrieve a trap from the entries hash */ + protected final void putTrap(Object key, Trap value) { + if (!isTrappable()) return; + if (entries == null) entries = new Hash(); + entries.put(key, Trap.class, value); } -} + /** adds a trap, avoiding duplicates */ + protected final void addTrap(Object name, JSFunction f) { + for(Trap t = getTrap(name); t != null; t = t.next) if (t.f == f) return; + putTrap(name, new Trap(this, name.toString(), f, (Trap)getTrap(name))); + } + + /** deletes a trap, if present */ + protected final void delTrap(Object name, JSFunction f) { + Trap t = (Trap)getTrap(name); + if (t == null) return; + if (t.f == f) { putTrap(t.name, t.next); return; } + for(; t.next != null; t = t.next) if (t.next.f == f) { t.next = t.next.next; return; } + } + // Call Support ////////////////////////////////////////////////////////////////////////////// + // return this from get() if the key was actually a method. + public static final Object METHOD = new Object(); + public Object callMethod(Object method, Object a0, Object a1, Object a2, Object[] rest, int nargs) { + throw new JSExn("attempted to call the null value (method "+method+")"); + } + public Object call(Object a0, Object a1, Object a2, Object[] rest, int nargs) { + throw new JSExn("you cannot call this object)"); + } + + + // Typing Support ////////////////////////////////////////////////////////////////////////////// + + public Number coerceToNumber() { throw new JSExn("tried to coerce a JavaScript object to a Number"); } + public String coerceToString() { throw new JSExn("tried to coerce a JavaScript object to a String"); } + public boolean coerceToBoolean() { throw new JSExn("tried to coerce a JavaScript object to a Boolean"); } + + public String typeName() { return "object"; } + +} diff --git a/src/org/xwt/js/JSArray.java b/src/org/xwt/js/JSArray.java index c918bda..069edc0 100644 --- a/src/org/xwt/js/JSArray.java +++ b/src/org/xwt/js/JSArray.java @@ -5,8 +5,9 @@ import org.xwt.util.*; import java.io.*; import java.util.*; +// FIXME: review, use redblacktrees /** A JavaScript JSArray */ -public class JSArray extends JSCallable { +public class JSArray extends JS { private Vec vec = new Vec(); public JSArray() { } public JSArray(int size) { vec.setSize(size); } @@ -54,7 +55,7 @@ public class JSArray extends JSCallable { return super.callMethod(method, a0, a1, a2, rest, nargs); } - public Object get(Object key) throws JS.Exn { + public Object get(Object key) throws JSExn { if (key instanceof Number) { int i = intVal(key); if (i == Integer.MIN_VALUE) return super.get(key); @@ -152,9 +153,9 @@ public class JSArray extends JSCallable { } }; private Object sort(Object tmp) { - if(tmp instanceof JSCallable) { + if(tmp instanceof JS) { final JSArray funcArgs = new JSArray(2); - final JSCallable jsFunc = (JSCallable) tmp; + final JS jsFunc = (JS) tmp; vec.sort(new Vec.CompareFunc() { public int compare(Object a, Object b) { funcArgs.setElementAt(a,0); diff --git a/src/org/xwt/js/JSCallable.java b/src/org/xwt/js/JSCallable.java deleted file mode 100644 index ac44c5c..0000000 --- a/src/org/xwt/js/JSCallable.java +++ /dev/null @@ -1,29 +0,0 @@ -package org.xwt.js; - -/** anything that is callable with the () operator and wasn't compiled from JS code */ -public abstract class JSCallable extends JS { - - // return this from get() if the key was actually a method. - public static final Object METHOD = new Object(); - - public Object callMethod(Object method, Object a0, Object a1, Object a2, Object[] rest, int nargs) { - if (method == null) return call(a0, a1, a2, rest, nargs); - Class c = this.getClass(); - String descrip = c.getName(); - if (c == Internal.JSCallableStub.class) { - descrip = ((Internal.JSCallableStub)this).obj.getClass().getName(); - } - throw new JS.Exn("attempted to call an undefined method ("+method+") on a " + descrip); - } - - public Object call(Object a0, Object a1, Object a2, Object[] rest, int nargs) { - Class c = this.getClass(); - String descrip = c.getName(); - if (c == Internal.JSCallableStub.class) { - descrip = ((Internal.JSCallableStub)this).obj.getClass().getName(); - } - throw new JS.Exn("you cannot call this object (of type " + descrip + ")"); - } - -} - diff --git a/src/org/xwt/js/JSContext.java b/src/org/xwt/js/JSContext.java deleted file mode 100644 index ec73e94..0000000 --- a/src/org/xwt/js/JSContext.java +++ /dev/null @@ -1,88 +0,0 @@ -package org.xwt.js; -import java.util.*; -import org.xwt.util.*; - -/** encapsulates the state of a JavaScript "thread" (no relation to Java threads) */ -public class JSContext { - - // Statics ////////////////////////////////////////////////////////////////////// - private int getLine_() { return current().f == null || (pc < 0 || pc >= f.size) ? -1 : f.line[pc]; } - public static int getLine() { return current().getLine_(); } - public static String getSourceName() { return current().f == null ? null : current().f.sourceName; } - - /** fetches the currently-executing javascript function */ - private static JSContext current() { return (JSContext)threadToJSContext.get(Thread.currentThread()); } - private static Hashtable threadToJSContext = new Hashtable(); - - // Instance members and methods ////////////////////////////////////////////////////////////////////// - - /** - * the number of times this context has been paused; used by - * Function.eval() to make sure it behaves properly even if the - * pause Callback is invoked *before* control is returned to - * eval() - */ - int pausecount = 0; - boolean pauseable; - - JSFunction f; ///< the currently-executing JSFunction - JSScope scope; - Vec stack = new Vec(); ///< the object stack - int pc = 0; ///< the program counter - - /** can be paused */ - public static void invokePauseable(JSFunction function) { - new JSContext(function, true).invoke(new JSArray()); - } - - /** cannot be paused */ - public static void invokeTrap(JSTrap.JSTrappable t, Object key, Object value) { - JSFunction invoker = new JSFunction("trap invoker", 0, null); - invoker.add(-1, ByteCodes.PUT, null); - JSContext cx = new JSContext(invoker, false); - cx.stack.push(t); - cx.stack.push(key); - cx.stack.push(value); - cx.resume(); - } - - JSContext(JSFunction f, boolean pauseable) { - this.pauseable = pauseable; - this.f = f; - scope = new JSScope(f.parentJSScope); - } - - void invoke(JSArray args) { - JSFunction tf = f; - f = null; - stack.push(new Interpreter.CallMarker(this)); - f = tf; - stack.push(args); - resume(); - } - - /** returns a callback which will restart the context, or null if this context is not pauseable */ - public static Callback pause() { return current().pause_(); } - private Callback pause_() { - if (!pauseable) return null; - pausecount++; - return new Callback() { public Object call(Object o) { - stack.push(o); - pc++; - resume(); - return null; - } }; - } - - private void resume() { - Thread t = Thread.currentThread(); - JSContext old = (JSContext)threadToJSContext.get(t); - threadToJSContext.put(t, this); - try { - Interpreter.eval(this); - } finally { - if (old == null) threadToJSContext.remove(t); - else threadToJSContext.put(t, old); - } - } -} diff --git a/src/org/xwt/js/JSDate.java b/src/org/xwt/js/JSDate.java index 901ee9d..345e3d4 100644 --- a/src/org/xwt/js/JSDate.java +++ b/src/org/xwt/js/JSDate.java @@ -48,7 +48,7 @@ import java.text.SimpleDateFormat; * @author Mike McCabe * @author Adam Megacz (many modifications */ -public class JSDate extends JSCallable { +public class JSDate extends JS { public JSDate() { if (thisTimeZone == null) { @@ -896,6 +896,7 @@ public class JSDate extends JSCallable { private static double _toNumber(Object[] o, int index) { return JS.toDouble(o[index]); } private static double toDouble(double d) { return d; } + // FIXME: switch to new calling convention here public JSDate(JSArray args_) { Object[] args = new Object[args_.length()]; for(int i=0; iunpauseable context. */ public Object call(Object a0, Object a1, Object a2, Object[] rest, int nargs) { - JSContext cx = new JSContext(this, false); JSArray args = new JSArray(); if (nargs > 0) args.addElement(a0); if (nargs > 1) args.addElement(a1); if (nargs > 2) args.addElement(a2); for(int i=3; i= s.length()) { lastIndex = 0; return null; } + REMatch match = re.getMatch(s,start); + if(global) lastIndex = match == null ? s.length() : match.getEndIndex(); + return match == null ? null : matchToExecResult(match,re,s); + } + case "test": { + String s = (String)a0; + if (!global) return B(re.getMatch(s) != null); + int start = global ? lastIndex : 0; + if(start < 0 || start >= s.length()) { lastIndex = 0; return null; } + REMatch match = re.getMatch(s,start); + lastIndex = match != null ? s.length() : match.getEndIndex(); + return B(match != null); + } case "toString": return toString(a0); + case "stringMatch": return stringMatch(a0,a1); + case "stringSearch": return stringSearch(a0,a1); + //#end + break; + } + case 2: { + //#switch(method) + case "stringReplace": return stringReplace(a0, a1,a2); //#end break; } @@ -54,7 +79,7 @@ public class JSRegexp extends JSCallable { case "exec": return METHOD; case "test": return METHOD; case "toString": return METHOD; - case "lastIndex": return new Integer(lastIndex); + case "lastIndex": return N(lastIndex); //#end return super.get(key); } @@ -63,58 +88,18 @@ public class JSRegexp extends JSCallable { if(key.equals("lastIndex")) lastIndex = JS.toNumber(value).intValue(); super.put(key,value); } - - private Object exec(String s) throws JS.Exn { - int start = global ? lastIndex : 0; - if(start < 0 || start >= s.length()) { - lastIndex = 0; - return null; - } - - REMatch match = re.getMatch(s,start); - if(global) - lastIndex = match == null ? s.length() : match.getEndIndex(); - if(match == null) - return null; - else - return matchToExecResult(match,re,s); - } - + private static Object matchToExecResult(REMatch match, RE re, String s) { JS ret = new JS(); - ret.put("index",new Integer(match.getStartIndex())); + ret.put("index", N(match.getStartIndex())); ret.put("input",s); int n = re.getNumSubs(); - ret.put("length",new Integer(n+1)); + ret.put("length", N(n+1)); ret.put("0",match.toString()); - for(int i=1;i<=n;i++) - ret.put(Integer.toString(i),match.toString(i)); + for(int i=1;i<=n;i++) ret.put(Integer.toString(i),match.toString(i)); return ret; } - - private Object exec(JSArray args) throws JS.Exn { - if(args.length() < 1) throw new JS.Exn("Not enough args to exec"); - String s = args.elementAt(0).toString(); - return exec(s); - } - - private Object test(String s) throws JS.Exn { - if(global) { - int start = global ? lastIndex : 0; - if(start < 0 || start >= s.length()) { - lastIndex = 0; - return null; - } - - REMatch match = re.getMatch(s,start); - lastIndex = match != null ? s.length() : match.getEndIndex(); - return wrapBool(match != null); - } else { - return wrapBool(re.getMatch(s) != null); - } - } - public String toString() { StringBuffer sb = new StringBuffer(); sb.append('/'); @@ -126,9 +111,7 @@ public class JSRegexp extends JSCallable { return sb.toString(); } - public static Object stringMatch(Object o, JSArray args) throws JS.Exn { - if(args.length() < 1) throw new JS.Exn("not enough args to match"); - Object arg0 = args.elementAt(0); + public static Object stringMatch(Object o, Object arg0) throws JSExn { String s = o.toString(); RE re; JSRegexp regexp = null; @@ -143,41 +126,26 @@ public class JSRegexp extends JSCallable { REMatch match = re.getMatch(s); return matchToExecResult(match,re,s); } - if(!regexp.global) - return regexp.exec(s); + if(!regexp.global) return regexp.callMethod("exec", s, null, null, null, 1); JSArray ret = new JSArray(); REMatch[] matches = re.getAllMatches(s); - for(int i=0;i 0) - regexp.lastIndex = matches[matches.length-1].getEndIndex(); - else - regexp.lastIndex = s.length(); + for(int i=0;i 0 ? matches[matches.length-1].getEndIndex() : s.length(); return ret; } - public static Object stringSearch(Object o, JSArray args) throws JS.Exn { - if(args.length() < 1) throw new JS.Exn("not enough args to match"); - Object arg0 = args.elementAt(0); + public static Object stringSearch(Object o, Object arg0) throws JSExn { String s = o.toString(); - RE re; - if(arg0 instanceof JSRegexp) - re = ((JSRegexp)arg0).re; - else - re = newRE(arg0.toString(),0); + RE re = arg0 instanceof JSRegexp ? ((JSRegexp)arg0).re : newRE(arg0.toString(),0); REMatch match = re.getMatch(s); - if(match == null) return new Integer(-1); - return new Integer(match.getStartIndex()); + return match == null ? N(-1) : N(match.getStartIndex()); } - public static Object stringReplace(Object o, JSArray args) throws JS.Exn { - if(args.length() < 2) throw new JS.Exn("not enough args to replace"); - Object arg0 = args.elementAt(0); - Object arg1 = args.elementAt(1); + public static Object stringReplace(Object o, Object arg0, Object arg1) throws JSExn { String s = o.toString(); RE re; - JSCallable replaceFunc = null; + JS replaceFunc = null; String replaceString = null; JSRegexp regexp = null; if(arg0 instanceof JSRegexp) { @@ -186,8 +154,8 @@ public class JSRegexp extends JSCallable { } else { re = newRE(arg0.toString(),0); } - if(arg1 instanceof JSCallable) - replaceFunc = (JSCallable) arg1; + if(arg1 instanceof JS) + replaceFunc = (JS) arg1; else replaceString = arg1.toString(); REMatch[] matches; @@ -215,21 +183,35 @@ public class JSRegexp extends JSCallable { sb.append(sa,pos,match.getStartIndex()-pos); pos = match.getEndIndex(); if(replaceFunc != null) { - // FEATURE: reintroduce - throw new JS.Exn("stringReplace() with a replacement function is temporarily disabled"); - /* - JSArray a = new JSArray(); - a.addElement(match.toString()); - if(regexp != null) { - int n = re.getNumSubs(); - for(int j=1;j<=n;j++) - a.addElement(match.toString(j)); + int n = (regexp == null ? 0 : re.getNumSubs()); + int numArgs = 3 + n; + Object[] rest = new Object[numArgs - 3]; + Object a0 = match.toString(); + Object a1 = null; + Object a2 = null; + for(int j=1;j<=n;j++) + switch(j) { + case 1: a1 = match.toString(j); break; + case 2: a2 = match.toString(j); break; + default: rest[j - 3] = match.toString(j); break; + } + switch(numArgs) { + case 3: + a1 = N(match.getStartIndex()); + a2 = s; + break; + case 4: + a2 = N(match.getStartIndex()); + rest[0] = s; + break; + default: + rest[rest.length - 2] = N(match.getStartIndex()); + rest[rest.length - 1] = s; } - a.addElement(new Integer(match.getStartIndex())); - a.addElement(s); - Object ret = replaceFunc.call(a, null); - sb.append(ret.toString()); - */ + + // note: can't perform pausing operations in here + sb.append((String)replaceFunc.call(a0, a1, a2, rest, numArgs)); + } else { sb.append(mySubstitute(match,replaceString,s)); } @@ -281,16 +263,9 @@ public class JSRegexp extends JSCallable { } - public static Object stringSplit(Object o,JSArray args) { - String s = o.toString(); - if(args.length() < 1 || args.elementAt(0) == null || s.length() == 0) { - JSArray ret = new JSArray(); - ret.addElement(s); - return ret; - } - Object arg0 = args.elementAt(0); - - int limit = args.length() < 2 ? Integer.MAX_VALUE : JS.toInt(args.elementAt(1)); + public static Object stringSplit(Object o, String s, Object arg0) { + // FIXME: reintroduce args.length() < 2 ? Integer.MAX_VALUE : JS.toInt(args.elementAt(1)); + int limit = JS.toInt(arg0); if(limit < 0) limit = Integer.MAX_VALUE; if(limit == 0) return new JSArray(); @@ -342,21 +317,13 @@ public class JSRegexp extends JSCallable { return ret; } - public static RE newRE(String pattern, int flags) throws JS.Exn { + public static RE newRE(String pattern, int flags) throws JSExn { try { return new RE(pattern,flags,RESyntax.RE_SYNTAX_PERL5); } catch(REException e) { - throw new JS.Exn(e.toString()); + throw new JSExn(e.toString()); } } - private static Boolean wrapBool(boolean b) { - return b ? Boolean.TRUE : Boolean.FALSE; - } - - private static Boolean wrapBool(int n) { - return wrapBool(n != 0); - } - public String typeName() { return "regexp"; } } diff --git a/src/org/xwt/js/JSScope.java b/src/org/xwt/js/JSScope.java index 44bfec1..971d550 100644 --- a/src/org/xwt/js/JSScope.java +++ b/src/org/xwt/js/JSScope.java @@ -5,37 +5,34 @@ import org.xwt.util.*; import java.io.*; import java.util.*; -/** Implementation of a JavaScript JSScope */ -public class JSScope extends JSCallable { - private JSScope parentJSScope; +/** Implementation of a JavaScript Scope */ +public class JSScope extends JS { + + private JSScope parentScope; + private static final Object NULL_PLACEHOLDER = new Object(); - public JSScope(JSScope parentJSScope) { - if (parentJSScope == this) throw new Error("can't make a scope its own parent!"); - this.parentJSScope = parentJSScope; - } - public boolean isTransparent() { return false; } + public JSScope(JSScope parentScope) { this.parentScope = parentScope; } public void declare(String s) { super.put(s, NULL_PLACEHOLDER); } - public JSScope getParentJSScope() { return parentJSScope; } - public boolean has(Object key) { return super.get(key) != null; } + public JSScope getParentScope() { return parentScope; } public Object get(Object key) { Object o = super.get(key); if (o != null) return o == NULL_PLACEHOLDER ? null : o; - else return parentJSScope == null ? null : parentJSScope.get(key); + else return parentScope == null ? null : parentScope.get(key); } + public boolean has(Object key) { return super.get(key) != null; } public void put(Object key, Object val) { - if (parentJSScope != null && !has(key)) parentJSScope.put(key, val); + if (parentScope != null && !has(key)) parentScope.put(key, val); else super.put(key, val == null ? NULL_PLACEHOLDER : val); } public static class Global extends JSScope { private final static Double NaN = new Double(Double.NaN); private final static Double POSITIVE_INFINITY = new Double(Double.POSITIVE_INFINITY); - - public Global(JSScope parent) { super(parent); } + public Global() { super(null); } public Object get(Object key) { //#switch(key) case "NaN": return NaN; @@ -61,42 +58,36 @@ public class JSScope extends JSCallable { case 0: { //#switch(method) case "stringFromCharCode": - JSArray args = new JSArray(); - for(int i=0; i