2003/11/18 10:47:26
authormegacz <megacz@xwt.org>
Fri, 30 Jan 2004 07:41:49 +0000 (07:41 +0000)
committermegacz <megacz@xwt.org>
Fri, 30 Jan 2004 07:41:49 +0000 (07:41 +0000)
darcs-hash:20040130074149-2ba56-30f9053960eb42621df713a3adc0087b9b523f2c.gz

20 files changed:
src/org/xwt/Res.java
src/org/xwt/SOAP.java
src/org/xwt/Surface.java
src/org/xwt/Template.java
src/org/xwt/XMLRPC.java
src/org/xwt/XWT.java
src/org/xwt/js/ByteCodes.java
src/org/xwt/js/Internal.java [deleted file]
src/org/xwt/js/Interpreter.java
src/org/xwt/js/JS.java
src/org/xwt/js/JSArray.java
src/org/xwt/js/JSCallable.java [deleted file]
src/org/xwt/js/JSContext.java [deleted file]
src/org/xwt/js/JSDate.java
src/org/xwt/js/JSFunction.java
src/org/xwt/js/JSMath.java
src/org/xwt/js/JSRegexp.java
src/org/xwt/js/JSScope.java
src/org/xwt/js/JSTrap.java [deleted file]
src/org/xwt/js/Lexer.java

index 0652e09..d35be09 100644 (file)
@@ -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());
         }
     }
index 113e01b..7ad91ef 100644 (file)
@@ -160,7 +160,7 @@ class SOAP extends XMLRPC {
     }
 
     /** Appends the SOAP representation of <code>o</code> to <code>sb</code> */
-    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");
index 3a083cc..fb9585a 100644 (file)
@@ -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 + "]"; }
 
     }
index d633f48..56ca4d9 100644 (file)
@@ -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<children.size(); i++) {
             Box kid = new Box();
             ((Template)children.elementAt(i)).apply(kid, xwt, pis);
-            b.putAndTriggerJSTraps(JS.N(b.numchildren), kid);
+            b.putAndTriggerTraps(JS.N(b.numchildren), kid);
         }
 
-        if (script != null) script.cloneWithNewParentJSScope(pis).call(null, null, null, null, 0);
+        if (script != null) script.cloneWithNewParentScope(pis).call(null, null, null, null, 0);
 
         for(int i=0; keys != null && i<keys.length; i++)
-            if (vals[i] instanceof String && ((String)vals[i]).charAt(0) == '$') b.putAndTriggerJSTraps(keys[i], pis.get(vals[i]));
-            else if ("image".equals(keys[i])) b.putAndTriggerJSTraps("image", resolveStringToResource((String)vals[i], xwt, true));
-            else if (keys[i] != null) b.putAndTriggerJSTraps(keys[i], vals[i]);
+            if (vals[i] instanceof String && ((String)vals[i]).charAt(0) == '$') b.putAndTriggerTraps(keys[i], pis.get(vals[i]));
+            else if ("image".equals(keys[i])) b.putAndTriggerTraps("image", resolveStringToResource((String)vals[i], xwt, true));
+            else if (keys[i] != null) b.putAndTriggerTraps(keys[i], vals[i]);
     }
 
 
@@ -211,17 +211,9 @@ public class Template {
             Hash h = new Hash(c.len * 2, 3);
             for(int i=0; i<c.len; i++) {
                 if (c.keys[i] == null) continue;
-                if (c.keys[i].endsWith(":image")) {
-                    String uri = (String)c.urimap.get(c.keys[i].substring(0, c.keys[i].indexOf(':')));
-                    c.keys[i] = c.keys[i].substring(c.keys[i].lastIndexOf(':') + 1);
-                    c.vals[i] = uri + "." + c.vals[i];
-                }                    
-                if ((c.keys[i].equals("preapply") || c.keys[i].endsWith(":preapply")) && c.localName.equals("template")) {
-                    String uri = "";
-                    if (c.keys[i].endsWith(":preapply")) {
-                        uri = "." + c.urimap.get(c.keys[i].substring(0, c.keys[i].indexOf(':')));
-                        c.keys[i] = c.keys[i].substring(c.keys[i].lastIndexOf(':') + 1);
-                    }
+                if (c.keys[i].equals("fill") || c.keys[i].equals("font")) c.vals[i] = c.uris[i] + "." + c.vals[i];
+                if (c.keys[i].equals("preapply")) {
+                    String uri = c.uris[i];
                     StringTokenizer tok = new StringTokenizer(c.vals[i].toString(), " ");
                     while(tok.hasMoreTokens()) t.preapply.addElement(uri + tok.nextToken());
                     c.keys[i] = c.keys[c.keys.length - 1];
@@ -283,7 +275,9 @@ public class Template {
             try {
                 String contentString = t.content.toString();
                 if (contentString.trim().length() > 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;
index efcccf7..cd5b4df 100644 (file)
@@ -30,7 +30,7 @@ import org.bouncycastle.util.encoders.Base64;
  *         convert.
  *  </ol>
  */
-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 <code>o</code> to <code>sb</code> */
-    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("</dateTime.iso8601></value>\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("                <value><array><data>\n");
             JSArray a = (JSArray)o;
@@ -263,7 +263,7 @@ class XMLRPC extends JSCallable {
             sb.append("                </data></array></value>\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("                <value><struct>\n");
@@ -277,12 +277,12 @@ class XMLRPC extends JSCallable {
             sb.append("                </struct></value>\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("<?xml version=\"1.0\"?>\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<nargs; i++) args.addElement(i==0?a0:i==1?a1:i==2?a2:rest[i-3]);
         return call(args);
     }
-    public final Object call(final JSArray args) throws JS.Exn {
-        final Callback callback = JSContext.pause();
-        new java.lang.Thread() {
-            public void run() {
-                try {
-                    final Object ret = call_(args);
-                    Scheduler.add(new Scheduler.Task() { public void perform() { callback.call(ret); } });
-                } catch (IOException se) {
-                    if (Log.on) Log.log(this, se);
-                    throw new JS.Exn("socket exception: " + se);
-                }
-        } }.start();
-        return null;
+    public final Object call(final JSArray args) throws JSExn {
+        try {
+            final JS.UnpauseCallback callback = JS.pause();
+            new java.lang.Thread() {
+                public void run() {
+                    try {
+                        final Object ret = call_(args);
+                        Scheduler.add(new Scheduler.Task() {
+                                public void perform() {
+                                    try {
+                                        callback.unpause(null);
+                                    } catch (JS.PausedException pe) {
+                                        // okay
+                                    }
+                                }
+                            });
+                    } catch (IOException se) {
+                        if (Log.on) Log.log(this, se);
+                        throw new JSExn("socket exception: " + se);
+                    }
+                } }.start();
+            return null;
+        } catch (NotPauseableException npe) {
+            throw new JSExn("cannot invoke an XML-RPC call in the foreground thread");
+        }
     }
 
     /** When you get a property from an XMLRPC, it just returns another XMLRPC with the property name tacked onto methodname. */
index fec4565..4e3b3e1 100644 (file)
@@ -11,13 +11,13 @@ import org.xwt.translators.*;
 import org.bouncycastle.util.encoders.Base64;
 
 /** Singleton class that provides all functionality in the xwt.* namespace */
-public final class XWT extends JSCallable {
+public final class XWT extends JS {
 
     public final Res rr;
     public XWT(Res rr) { this.rr = rr; }
 
     /** lets us put multi-level get/put/call keys all in the same method */
-    private class Sub extends JSCallable {
+    private class Sub extends JS {
         String key;
         Sub(String key) { this.key = key; }
         public String toString() { return "XWTSUB " + key; }
@@ -64,7 +64,7 @@ public final class XWT extends JSCallable {
             if (Surface.button1 && !Surface.button2 && !Surface.button3) return new Integer(1);
             else if (!Surface.button1 && Surface.button2 && !Surface.button3) return new Integer(2);
             else if (!Surface.button1 && !Surface.button2 && Surface.button3) return new Integer(3);
-            else return new Integer(0);
+            else return ZERO;
         case "undocumented": return new Sub("undocumented");
         case "undocumented.internal": return new Sub("undocumented.internal");
         case "thread.yield": return METHOD;
@@ -98,7 +98,15 @@ public final class XWT extends JSCallable {
     public void put(Object name, final Object value) {
         //#switch(name)
         case "thread":
-            Scheduler.add(new Scheduler.Task() { public void perform() { JSContext.invokePauseable((JSFunction)value); } });
+            Scheduler.add(new Scheduler.Task() {
+                    public void perform() {
+                        try {
+                            JS.invokePauseable((JSFunction)value);
+                        } catch (JS.PausedException pe) {
+                            // okay; wait for ourselves to be re-enqueued
+                        }
+                    }
+                });
         case "ui.clipboard": Platform.setClipBoard((String)value);
         case "ui.frame": Platform.createSurface((Box)value, true, true);
         case "ui.window": Platform.createSurface((Box)value, false, true);
@@ -108,7 +116,7 @@ public final class XWT extends JSCallable {
         //#end
     }
 
-    public Object callMethod(Object name, Object a, Object b, Object c, Object[] rest, int nargs) throws JS.Exn {
+    public Object callMethod(Object name, Object a, Object b, Object c, Object[] rest, int nargs) throws JSExn {
         if (name.equals("date")) {
             JSArray args = new JSArray();
             for(int i=0; i<nargs; i++) args.addElement(i==0?a:i==1?b:i==2?c:rest[i-3]);
@@ -126,12 +134,14 @@ public final class XWT extends JSCallable {
         } else if (nargs == 3 && name.equals("soap")) {
             if (name.equals("soap")) {
                 return new SOAP((String)a, "", (String)b, (String)c);
+                /* FIXME
             } else if (name.equals("graft")) {
-                if (a instanceof Box) throw new JS.Exn("can't graft onto Boxes (yet)");
-                if (a instanceof Number) throw new JS.Exn("can't graft onto Numbers (yet)");
-                if (a instanceof String) throw new JS.Exn("can't graft onto Strings (yet)");
+                if (a instanceof Box) throw new JSExn("can't graft onto Boxes (yet)");
+                if (a instanceof Number) throw new JSExn("can't graft onto Numbers (yet)");
+                if (a instanceof String) throw new JSExn("can't graft onto Strings (yet)");
                 if (a instanceof Res) return new Res.Graft((Res)a, b, c);
                 return new JS.Graft((JS)a, b, c);
+                */
             }
         } else if (nargs == 1) {
             //#switch(name)
@@ -140,14 +150,14 @@ public final class XWT extends JSCallable {
             case "res.unzip": return new Res.Zip((Res)a);
             case "res.uncab": return new Res.Cab((Res)a);
             case "res.cache": try { return new Res.CachedRes((Res)a, "resources", true); }
-                              catch (Res.NotCacheableException e) { throw new JS.Exn("this resource cannot be cached"); }
+                              catch (Res.NotCacheableException e) { throw new JSExn("this resource cannot be cached"); }
             case "res.url":
                 String url = (String)a;
                 if (url.startsWith("http://")) return new Res.HTTP(url);
                 else if (url.startsWith("https://")) return new Res.HTTP(url);
                 else if (url.startsWith("data:")) return new Res.ByteArray(Base64.decode(url.substring(5)), null);
                 else if (url.startsWith("utf8:")) return new Res.ByteArray(url.substring(5).getBytes(), null);
-                throw new JS.Exn("invalid resource specifier " + url);
+                throw new JSExn("invalid resource specifier " + url);
             case "thread.sleep": sleep(JS.toInt(a)); return null;
             case "log.println": Log.logJS(this, a== null ? "**null**" : a.toString()); return null;
             case "log.dump": Log.recursiveLog("","",a); return null;
@@ -167,18 +177,30 @@ public final class XWT extends JSCallable {
     }
 
     public static void sleep(final int i) {
-        final Callback callback = JSContext.pause();
-        final long currentTime = System.currentTimeMillis();
-        new Thread() {
-            public void run() {
-                try { Thread.sleep(i); } catch (InterruptedException e) { }
-                Scheduler.add(new Scheduler.Task() { public void perform() { callback.call(null); } });
-            }
-        }.start();
+        try {
+            final JS.UnpauseCallback callback = JS.pause();
+            final long currentTime = System.currentTimeMillis();
+            new Thread() {
+                public void run() {
+                    try { Thread.sleep(i); } catch (InterruptedException e) { }
+                    Scheduler.add(new Scheduler.Task() {
+                            public void perform() {
+                                try {
+                                    callback.unpause(null);
+                                } catch (JS.PausedException pe) {
+                                    // okay
+                                }
+                            }
+                        });
+                }
+            }.start();
+        } catch (JS.NotPauseableException npe) {
+            throw new JSExn("you cannot sleep or yield in the foreground thread");
+        }
     }
     
     public static final JSMath xwtMath = new JSMath() {
-            private JS gs = new JSScope.Global(null);
+            private JS gs = new JSScope.Global();
             public String toString() { return "XWTMATH"; }
             public Object get(Object key) {
                 //#switch(key)
@@ -192,7 +214,7 @@ public final class XWT extends JSCallable {
         };
 
     public static final JS xwtString = new JS() {
-            private JS gs = new JSScope.Global(null);
+            private JS gs = new JSScope.Global();
             public void put(Object key, Object val) { }
             public Object get(Object key) {
                 //#switch(key)
@@ -242,16 +264,16 @@ public final class XWT extends JSCallable {
             }
         }
         public void whitespace(char[] ch, int start, int length) {}
-        public JS doParse(InputStream is) throws JS.Exn {
+        public JS doParse(InputStream is) throws JSExn {
             try { 
                 BufferedReader r = new BufferedReader(new InputStreamReader(is));
                 parse(r);
             } catch (XML.XMLException e) {
-                throw new JS.Exn("error parsing XML: " + e.toString());
+                throw new JSExn("error parsing XML: " + e.toString());
             } catch (IOException e) {
                 if (Log.on) Log.log(this, "IO Exception while reading from file");
                 if (Log.on) Log.log(this, e);
-                throw new JS.Exn("error reading from Resource");
+                throw new JSExn("error reading from Resource");
             }
             return obStack.size() >= 1 ? (JS)obStack.elementAt(0) : null;
         }
index d364d8c..e270885 100644 (file)
@@ -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 (file)
index d1ac346..0000000
+++ /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<alength;i++) sb.append(args.elementAt(i));
-            return sb.toString();
-        }
-        if(method.equals("indexOf")) {
-            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.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<nargs; i++) args.addElement(i==0?a0:i==1?a1:i==2?a2:rest[i-3]);
-                        return callMethodOnPrimitive(target,method,args);
-                    }
-            };
-        }
-        return null;
-    }
-    
-    static class JSCallableStub extends JSCallable {
-        private Object method;
-        JS obj;
-        public JSCallableStub(JS obj, Object method) { this.obj = obj; this.method = method; }
-        public Object call(Object a0, Object a1, Object a2, Object[] rest, int nargs) {
-            return ((JSCallable)obj).callMethod(method, a0, a1, a2, rest, nargs);
-        }
-    }
-}
index c9bcd73..f65a585 100644 (file)
@@ -5,144 +5,186 @@ import org.xwt.util.*;
 import java.util.*;
 import java.io.*;
 
+/** Encapsulates a single JS interpreter (ie call stack) */
 class Interpreter implements ByteCodes, Tokens {
 
-    static Object eval(final JSContext cx) throws JS.Exn {
-        final int initialPauseCount = cx.pausecount;
-        OUTER: for(;; cx.pc++) {
+
+    // Thread-Interpreter Mapping /////////////////////////////////////////////////////////////////////////
+
+    static Interpreter current() { return (Interpreter)threadToInterpreter.get(Thread.currentThread()); }
+    private static Hashtable threadToInterpreter = new Hashtable();
+
+    
+    // Instance members and methods //////////////////////////////////////////////////////////////////////
+    
+    int pausecount;               ///< the number of times pause() has been invoked; -1 indicates unpauseable
+    JSFunction f = null;          ///< the currently-executing JSFunction
+    JSScope scope;                ///< the current top-level scope (LIFO stack via NEWSCOPE/OLDSCOPE)
+    Vec stack = new Vec();        ///< the object stack
+    int pc = 0;                   ///< the program counter
+
+    Interpreter(JSFunction f, boolean pauseable, JSArray args) {
+        stack.push(new Interpreter.CallMarker(this));    // the "root function returned" marker -- f==null
+        this.f = f;
+        this.pausecount = pauseable ? 0 : -1;
+        this.scope = new JSScope(f.parentScope);
+        stack.push(args);
+    }
+    
+    /** this is the only synchronization point we need in order to be threadsafe */
+    synchronized Object resume() {
+        Thread t = Thread.currentThread();
+        Interpreter old = (Interpreter)threadToInterpreter.get(t);
+        threadToInterpreter.put(t, this);
+        try {
+            return run();
+        } finally {
+            if (old == null) threadToInterpreter.remove(t);
+            else threadToInterpreter.put(t, old);
+        }
+    }
+
+    private static JSExn je(String s) { return new JSExn(JS.getSourceName() + ":" + JS.getLine() + " " + s); }
+
+    // FIXME: double check the trap logic
+    private Object run() throws JSExn {
+
+        // if pausecount changes after a get/put/call, we know we've been paused
+        final int initialPauseCount = pausecount;
+
+        OUTER: for(;; pc++) {
         try {
-            if (cx.f == null || cx.pc >= 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<args.length;i++) sb.append(JS.toString(args[i]));
-                        cx.stack.push(sb.toString());
+                        stack.push(sb.toString());
                     } else {
                         int numStrings = 0;
                         for(int i=0;i<args.length;i++) if(args[i] instanceof String) numStrings++;
                         if(numStrings == 0) {
                             double d = 0.0;
                             for(int i=0;i<args.length;i++) d += JS.toDouble(args[i]);
-                            cx.stack.push(new Double(d));
+                            stack.push(JS.N(d));
                         } else {
                             int i=0;
                             StringBuffer sb = new StringBuffer(64);
@@ -347,10 +388,10 @@ class Interpreter implements ByteCodes, Tokens {
                                 do {
                                     d += JS.toDouble(args[i++]);
                                 } while(!(args[i] instanceof String));
-                                sb.append(JS.toString(new Double(d)));
+                                sb.append(JS.toString(JS.N(d)));
                             }
                             while(i < args.length) sb.append(JS.toString(args[i++]));
-                            cx.stack.push(sb.toString());
+                            stack.push(sb.toString());
                         }
                     }
                 }
@@ -358,34 +399,34 @@ class Interpreter implements ByteCodes, Tokens {
             }
 
             default: {
-                Object right = cx.stack.pop();
-                Object left = cx.stack.pop();
+                Object right = stack.pop();
+                Object left = stack.pop();
                 switch(op) {
                         
-                case BITOR: cx.stack.push(new Long(JS.toLong(left) | JS.toLong(right))); break;
-                case BITXOR: cx.stack.push(new Long(JS.toLong(left) ^ JS.toLong(right))); break;
-                case BITAND: cx.stack.push(new Long(JS.toLong(left) & JS.toLong(right))); break;
-
-                case SUB: cx.stack.push(new Double(JS.toDouble(left) - JS.toDouble(right))); break;
-                case MUL: cx.stack.push(new Double(JS.toDouble(left) * JS.toDouble(right))); break;
-                case DIV: cx.stack.push(new Double(JS.toDouble(left) / JS.toDouble(right))); break;
-                case MOD: cx.stack.push(new Double(JS.toDouble(left) % JS.toDouble(right))); break;
+                case BITOR: stack.push(JS.N(JS.toLong(left) | JS.toLong(right))); break;
+                case BITXOR: stack.push(JS.N(JS.toLong(left) ^ JS.toLong(right))); break;
+                case BITAND: stack.push(JS.N(JS.toLong(left) & JS.toLong(right))); break;
+
+                case SUB: stack.push(JS.N(JS.toDouble(left) - JS.toDouble(right))); break;
+                case MUL: stack.push(JS.N(JS.toDouble(left) * JS.toDouble(right))); break;
+                case DIV: stack.push(JS.N(JS.toDouble(left) / JS.toDouble(right))); break;
+                case MOD: stack.push(JS.N(JS.toDouble(left) % JS.toDouble(right))); break;
                         
-                case LSH: cx.stack.push(new Long(JS.toLong(left) << JS.toLong(right))); break;
-                case RSH: cx.stack.push(new Long(JS.toLong(left) >> 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<alength;i++) sb.append(args.elementAt(i));
+            return sb.toString();
+            */
+        }
+        case "indexOf": {
+            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.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);
+        }
+    }
 }
index 327d35c..f227f53 100644 (file)
@@ -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"; }
+
+} 
index c918bda..069edc0 100644 (file)
@@ -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 (file)
index ac44c5c..0000000
+++ /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 (file)
index ec73e94..0000000
+++ /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);
-        }
-    }
-}
index 901ee9d..345e3d4 100644 (file)
@@ -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; i<args.length; i++) args[i] = args_.elementAt(i);
index 92e3eff..a9164b8 100644 (file)
@@ -6,24 +6,42 @@ import java.util.*;
 import java.io.*;
 
 /** A JavaScript function, compiled into bytecode */
-public class JSFunction extends JSCallable implements ByteCodes, Tokens {
+public class JSFunction extends JS implements ByteCodes, Tokens {
 
 
     // Fields and Accessors ///////////////////////////////////////////////
 
     int numFormalArgs = 0;         ///< the number of formal arguments
+
     String sourceName;             ///< the source code file that this block was drawn from
-    int[] line = new int[10];      ///< the line numbers
     private int firstLine = -1;    ///< the first line of this script
+
+    int[] line = new int[10];      ///< the line numbers
     int[] op = new int[10];        ///< the instructions
     Object[] arg = new Object[10]; ///< the arguments to the instructions
     int size = 0;                  ///< the number of instruction/argument pairs
-    JSScope parentJSScope;          ///< the default scope to use as a parent scope when executing this
 
+    JSScope parentScope;           ///< the default scope to use as a parent scope when executing this
+
+
+    // Public //////////////////////////////////////////////////////////////////////////////
 
-    // Constructors ////////////////////////////////////////////////////////
+    /** parse and compile a function */
+    public static JSFunction fromReader(String sourceName, int firstLine, Reader sourceCode) throws IOException {
+        JSFunction ret = new JSFunction(sourceName, firstLine, null);
+        if (sourceCode == null) return ret;
+        Parser p = new Parser(sourceCode, sourceName, firstLine);
+        while(true) {
+            int s = ret.size;
+            p.parseStatement(ret, null);
+            if (s == ret.size) break;
+        }
+        ret.add(-1, LITERAL, null); 
+        ret.add(-1, RETURN);
+        return ret;
+    }
 
-    public JSFunction cloneWithNewParentJSScope(JSScope s) {
+    public JSFunction cloneWithNewParentScope(JSScope s) {
         JSFunction ret = new JSFunction(sourceName, firstLine, s);
         // Reuse the same op, arg, line, and size variables for the new "instance" of the function
         // NOTE: Neither *this* function nor the new function should be modified after this call
@@ -35,40 +53,26 @@ public class JSFunction extends JSCallable implements ByteCodes, Tokens {
         return ret;
     }
 
-    JSFunction(String sourceName, int firstLine, JSScope parentJSScope) {
-        this.sourceName = sourceName;
-        this.firstLine = firstLine;
-        this.parentJSScope = parentJSScope;
-    }
-
-    protected JSFunction(String sourceName, int firstLine, Reader sourceCode, JSScope parentJSScope) throws IOException {
-        this(sourceName, firstLine, parentJSScope);
-        if (sourceCode == null) return;
-        Parser p = new Parser(sourceCode, sourceName, firstLine);
-        while(true) {
-            int s = size;
-            p.parseStatement(this, null);
-            if (s == size) break;
-        }
-        add(-1, LITERAL, null); 
-        add(-1, RETURN);
-    }
-
     /** Note: code gets run in an <i>unpauseable</i> 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<nargs; i++) args.addElement(rest[i-3]);
-        cx.invoke(args);
-        return cx.stack.pop();
+        Interpreter cx = new Interpreter(this, false, args);
+        return cx.resume();
     }
 
 
     // Adding and Altering Bytecodes ///////////////////////////////////////////////////
 
+    JSFunction(String sourceName, int firstLine, JSScope parentScope) {
+        this.sourceName = sourceName;
+        this.firstLine = firstLine;
+        this.parentScope = parentScope;
+    }
+
     int get(int pos) { return op[pos]; }
     Object getArg(int pos) { return arg[pos]; }
     void set(int pos, int op_, Object arg_) { op[pos] = op_; arg[pos] = arg_; }
@@ -99,10 +103,8 @@ public class JSFunction extends JSCallable implements ByteCodes, Tokens {
         for (int i=0; i < size; i++) {
             sb.append(i);
             sb.append(": ");
-            if (op[i] < 0)
-                sb.append(bytecodeToString[-op[i]]);
-            else
-                sb.append(codeToString[op[i]]);
+            if (op[i] < 0) sb.append(bytecodeToString[-op[i]]);
+            else sb.append(codeToString[op[i]]);
             sb.append(" ");
             sb.append(arg[i] == null ? "(no arg)" : arg[i]);
             if((op[i] == JF || op[i] == JT || op[i] == JMP) && arg[i] != null && arg[i] instanceof Number) {
index 7a27f28..bf34435 100644 (file)
@@ -6,7 +6,7 @@ import java.io.*;
 import java.util.*;
 
 /** The JavaScript Math object */
-public class JSMath extends JSCallable {
+public class JSMath extends JS {
 
     public static JSMath singleton = new JSMath();
 
index 819e1f3..ca22fce 100644 (file)
@@ -1,20 +1,22 @@
+// Copyright 2003 Adam Megacz, see the COPYING file for licensing [GPL]
 package org.xwt.js;
 
 import gnu.regexp.*;
 
-public class JSRegexp extends JSCallable {
+/** A JavaScript regular expression object */
+public class JSRegexp extends JS {
     private boolean global;
     private RE re;
     private int lastIndex;
     
-    public JSRegexp(Object arg0, Object arg1) throws JS.Exn {
+    public JSRegexp(Object arg0, Object arg1) throws JSExn {
         if(arg0 instanceof JSRegexp) {
             JSRegexp r = (JSRegexp) arg0;
             this.global = r.global;
             this.re = r.re;
             this.lastIndex = r.lastIndex;
         } else {
-            String pattern = arg0.toString();
+            String pattern = (String)arg0;
             String sFlags = null;
             int flags = 0;
             if(arg1 != null) sFlags = (String)arg1;
@@ -24,14 +26,14 @@ public class JSRegexp extends JSCallable {
                     case 'i': flags |= RE.REG_ICASE; break;
                     case 'm': flags |= RE.REG_MULTILINE; break;
                     case 'g': global = true; break;
-                    default: throw new JS.Exn("Invalid flag in regexp \"" + sFlags.charAt(i) + "\"");
+                    default: throw new JSExn("Invalid flag in regexp \"" + sFlags.charAt(i) + "\"");
                 }
             }
             re = newRE(pattern,flags);
-            put("source",pattern);
-            put("global",wrapBool(global));
-            put("ignoreCase",wrapBool(flags & RE.REG_ICASE));
-            put("multiline",wrapBool(flags & RE.REG_MULTILINE));
+            put("source", pattern);
+            put("global", B(global));
+            put("ignoreCase", B(flags & RE.REG_ICASE));
+            put("multiline", B(flags & RE.REG_MULTILINE));
         }
     }
 
@@ -39,9 +41,32 @@ public class JSRegexp extends JSCallable {
         switch(nargs) {
             case 1: {
                 //#switch(method)
-                case "exec": return exec((String)a0);
-                case "test": return test((String)a0);
+                case "exec": {
+                    String s = (String)a0;
+                    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();
+                    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<matches.length;i++)
-            ret.addElement(matches[i].toString());
-        if(matches.length > 0)
-            regexp.lastIndex = matches[matches.length-1].getEndIndex();
-        else
-            regexp.lastIndex = s.length();
+        for(int i=0;i<matches.length;i++) ret.addElement(matches[i].toString());
+        regexp.lastIndex = matches.length > 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"; }
 }
index 44bfec1..971d550 100644 (file)
@@ -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<nargs; i++) args.addElement(i==0?a0:i==1?a1:i==2?a2:rest[i-3]);
-                        return stringFromCharCode(args);
-                    default: break;
+                        char buf[] = new char[nargs];
+                        for(int i=0; i<nargs; i++) buf[i] = (char)(JS.toInt(i==0?a0:i==1?a1:i==2?a2:rest[i-3]) & 0xffff);
+                        return new String(buf);
                     //#end
+                    break;
                 }
                 case 1: {
                     //#switch(method)
                     case "parseInt": return parseInt(a0, N(0));
                     case "isNaN": { double d = toDouble(a0); return d == d ? F : T; }
                     case "isFinite": { double d = toDouble(a0); return (d == d && !Double.isInfinite(d)) ? T : F; }
-                    case "decodeURI": throw new JS.Exn("unimplemented");
-                    case "decodeURIComponent": throw new JS.Exn("unimplemented");
-                    case "encodeURI": throw new JS.Exn("unimplemented");
-                    case "encodeURIComponent": throw new JS.Exn("unimplemented");
-                    case "escape": throw new JS.Exn("unimplemented");
-                    case "unescape": throw new JS.Exn("unimplemented");
-                    default: break;
+                    case "decodeURI": throw new JSExn("unimplemented");
+                    case "decodeURIComponent": throw new JSExn("unimplemented");
+                    case "encodeURI": throw new JSExn("unimplemented");
+                    case "encodeURIComponent": throw new JSExn("unimplemented");
+                    case "escape": throw new JSExn("unimplemented");
+                    case "unescape": throw new JSExn("unimplemented");
                     //#end
+                    break;
                 }
                 case 2: {
                     //#switch(method)
                     case "parseInt": return parseInt(a0, a1);
-                    default: break;
                     //#end
+                    break;
                 }
             }
             return super.callMethod(method, a0, a1, a2, rest, nargs);
         }
 
-        private Object stringFromCharCode(JSArray args) {
-            char buf[] = new char[args.length()];
-            for(int i=0;i<args.length();i++) buf[i] = (char)(JS.toInt(args.elementAt(i)) & 0xffff);
-            return new String(buf);
-        }
-
         private Object parseInt(Object arg, Object r) {
             int radix = JS.toInt(r);
             String s = (String)arg;
@@ -117,7 +108,7 @@ public class JSScope extends JSCallable {
                     radix = 16;
                 } else {
                     radix = 8;
-                    if(length == start || Character.digit(s.charAt(start+1),8)==-1) return new Integer(0);
+                    if(length == start || Character.digit(s.charAt(start+1),8)==-1) return JS.ZERO;
                 }
             }
             if(radix == 0) radix = 10;
@@ -125,7 +116,7 @@ public class JSScope extends JSCallable {
             // try the fast way first
             try {
                 String s2 = start == 0 ? s : s.substring(start);
-                return new Integer(sign*Integer.parseInt(s2,radix));
+                return JS.N(sign*Integer.parseInt(s2,radix));
             } catch(NumberFormatException e) { }
             // fall through to a slower but emca-compliant method
             for(int i=start;i<length;i++) {
@@ -134,8 +125,8 @@ public class JSScope extends JSCallable {
                 n = n*radix + digit;
                 if(n < 0) return NaN; // overflow;
             }
-            if(n <= Integer.MAX_VALUE) return new Integer(sign*(int)n);
-            return new Long((long)sign*n);
+            if(n <= Integer.MAX_VALUE) return JS.N(sign*(int)n);
+            return JS.N((long)sign*n);
         }
 
         private Object parseFloat(Object arg) {
@@ -148,7 +139,7 @@ public class JSScope extends JSCallable {
             // trailing garbage
             while(start < end) {
                 try {
-                    return new Double(s.substring(start,length));
+                    return JS.N(s.substring(start,length));
                 } catch(NumberFormatException e) { }
                 end--;
             }
diff --git a/src/org/xwt/js/JSTrap.java b/src/org/xwt/js/JSTrap.java
deleted file mode 100644 (file)
index 56a076a..0000000
+++ /dev/null
@@ -1,56 +0,0 @@
-// Copyright 2003 Adam Megacz, see the COPYING file for licensing [GPL]
-package org.xwt.js;
-
-import java.util.*;
-import org.xwt.util.*;
-import java.io.*;
-
-/**
- *  This class encapsulates a single trap placed on a given node. The
- *  traps for a given property name on a given box are maintained as a
- *  linked list stack, with the most recently placed trap at the head
- *  of the list.
- */
-public class JSTrap {
-
-    JSTrappable trapee = null;   ///< the box on which this trap was placed
-    JSFunction f = null;                 ///< the function for this trap
-    JSTrap next = null;          ///< the next trap down the trap stack
-    Object name = null;          ///< the property that the trap was placed on
-
-    private JSTrap(JSTrappable b, String n, JSFunction f, JSTrap nx) { trapee = b; name = n; this.f = f; this.next = nx; }
-
-    /** adds a trap, avoiding duplicates */
-    public static void addTrap(JSTrappable trapee, Object name, JSFunction f) {
-        for(JSTrap t = trapee.getTrap(name); t != null; t = t.next) if (t.f == f) return;
-        trapee.putTrap(name, new JSTrap(trapee, name.toString(), f, (JSTrap)trapee.getTrap(name)));
-    }
-
-    /** deletes a trap, if present */
-    public static void delTrap(JSTrappable trapee, Object name, JSFunction f) {
-        JSTrap t = (JSTrap)trapee.getTrap(name);
-        if (t == null) return;
-        if (t.f == f) { trapee.putTrap(t.name, t.next); return; }
-        for(; t.next != null; t = t.next) if (t.next.f == f) { t.next = t.next.next; return; }
-    }
-
-    /** objects onto which traps may be placed */
-    public static interface JSTrappable {
-        public abstract JSTrap getTrap(Object key);
-        public abstract void putTrap(Object key, JSTrap trap);
-        public abstract void putAndTriggerJSTraps(Object key, Object value);
-    }
-
-    static class JSTrapScope extends JSScope {
-        JSTrap t;
-        Object val = null;
-        boolean cascadeHappened = false;
-        public JSTrapScope(JSScope parent, JSTrap t, Object val) { super(parent); this.t = t; this.val = val; }
-        public Object get(Object key) {
-            if (key.equals("trapee")) return t.trapee;
-            if (key.equals("trapname")) return t.name;
-            return super.get(key);
-        }
-    }
-}
-
index 1e2494e..5a6e02e 100644 (file)
@@ -77,6 +77,7 @@ class Lexer implements Tokens {
     
     // Token Subtype Handlers /////////////////////////////////////////////////////////
 
+    // FIXME: convert to a string switch
     private int getKeyword(String s) throws IOException {
         char c;
         switch (s.length()) {
@@ -224,11 +225,8 @@ class Lexer implements Tokens {
             }
         }
         
-        if (!isInteger) this.number = new Double(dval);
-        else if (Byte.MIN_VALUE <= longval && longval <= Byte.MAX_VALUE) this.number = new Byte((byte)longval);
-        else if (Short.MIN_VALUE <= longval && longval <= Short.MAX_VALUE) this.number = new Short((short)longval);
-        else if (Integer.MIN_VALUE <= longval && longval <= Integer.MAX_VALUE) this.number = new Integer((int)longval);
-        else this.number = new Double(longval);
+        if (!isInteger) this.number = JS.N(dval);
+        else this.number = JS.N(longval);
         return NUMBER;
     }
     
@@ -370,7 +368,7 @@ class Lexer implements Tokens {
             accumulator.append((char)lastread);
         }
         public String getString() throws IOException {
-            String ret = accumulator.toString();
+            String ret = accumulator.toString().intern();
             accumulator = null;
             return ret;
         }