2003/11/13 05:04:22
authormegacz <megacz@xwt.org>
Fri, 30 Jan 2004 07:41:12 +0000 (07:41 +0000)
committermegacz <megacz@xwt.org>
Fri, 30 Jan 2004 07:41:12 +0000 (07:41 +0000)
darcs-hash:20040130074112-2ba56-d47745d9943423898465fe18811dabc174904f04.gz

28 files changed:
src/org/xwt/Picture.java
src/org/xwt/PixelBuffer.java
src/org/xwt/Platform.java
src/org/xwt/Res.java
src/org/xwt/SOAP.java
src/org/xwt/Scheduler.java
src/org/xwt/Surface.java
src/org/xwt/Template.java
src/org/xwt/Trap.java [deleted file]
src/org/xwt/VectorGraphics.java
src/org/xwt/XMLRPC.java
src/org/xwt/XWT.java
src/org/xwt/js/ByteCodes.java
src/org/xwt/js/GlobalScopeImpl.java [deleted file]
src/org/xwt/js/Internal.java
src/org/xwt/js/JS.java
src/org/xwt/js/JSArray.java [moved from src/org/xwt/js/ArrayImpl.java with 73% similarity]
src/org/xwt/js/JSCallable.java [new file with mode: 0644]
src/org/xwt/js/JSContext.java [new file with mode: 0644]
src/org/xwt/js/JSDate.java [moved from src/org/xwt/js/Date.java with 93% similarity]
src/org/xwt/js/JSFunction.java [moved from src/org/xwt/js/Function.java with 71% similarity]
src/org/xwt/js/JSMath.java [new file with mode: 0644]
src/org/xwt/js/JSObj.java [new file with mode: 0644]
src/org/xwt/js/JSRegexp.java [new file with mode: 0644]
src/org/xwt/js/JSScope.java [new file with mode: 0644]
src/org/xwt/js/JSTrap.java [new file with mode: 0644]
src/org/xwt/js/Math.java [deleted file]
src/org/xwt/js/Parser.java

index 38f9c0a..942bf03 100644 (file)
@@ -33,32 +33,33 @@ public abstract class Picture {
     private static Cache cache = new Cache();
     private static GIF gif = new GIF();
     
-    // FIXME: return a Picture that gets filled in later
     /** turns a resource into a Picture.Source and passes it to the callback */
     public static Picture fromRes(final Res r, final Callback callback) {
         Picture ret = (Picture)cache.get(r);
         if (ret != null) return ret;
-        try {
-            Platform.inputStreamToByteArray(r.getInputStream(), new Callback() { public Object call(Object o) {
+        if (callback != null)
+            new java.lang.Thread() { public void run() {
                 try {
-                    Picture ret = null;
-                    byte[] b = (byte[])o;
-                    InputStream pbis = new ByteArrayInputStream(b);
-                    if ((b[0] & 0xff) == 'G') ret = gif.fromInputStream(pbis, r.getDescriptiveName());
-                    else if ((b[0] & 0xff) == 137) ret = new PNG().fromInputStream(pbis, r.getDescriptiveName());
-                    else if ((b[0] & 0xff) == 0xff) ret = Platform.decodeJPEG(pbis, r.getDescriptiveName());
-                    else throw new JS.Exn("couldn't figure out image type from first byte");
-                    ret.res = r;
-                    cache.put(r, ret);
-                    callback.call(ret);
-                } catch (Exception e) {
+                    final byte[] b = InputStreamToByteArray.convert(r.getInputStream());
+                    Scheduler.add(new Scheduler.Task() { public void perform() {
+                        try {
+                            Picture ret = null;
+                            InputStream pbis = new ByteArrayInputStream(b);
+                            if ((b[0] & 0xff) == 'G') ret = gif.fromInputStream(pbis, r.getDescriptiveName());
+                            else if ((b[0] & 0xff) == 137) ret = new PNG().fromInputStream(pbis, r.getDescriptiveName());
+                            else if ((b[0] & 0xff) == 0xff) ret = Platform.decodeJPEG(pbis, r.getDescriptiveName());
+                            else throw new JS.Exn("couldn't figure out image type from first byte");
+                            ret.res = r;
+                            cache.put(r, ret);
+                            callback.call(ret);
+                        } catch (Exception e) {
+                            Log.log(Picture.class, e);
+                        } } });
+                } catch (IOException e) {
                     Log.log(Picture.class, e);
+                    return;
                 }
-                return null;
-            }});
-        } catch (Exception e) {
-            Log.log(Picture.class, e);
-        }
+            } }.start();
         return null;
     }
 }
index eb8425a..8285f41 100644 (file)
@@ -25,7 +25,7 @@ public abstract class PixelBuffer {
     public abstract void drawPicture(Picture source, int dx1, int dy1, int cx1, int cy1, int cx2, int cy2);
 
     /** fill a trapezoid whose top and bottom edges are horizontal */
-    public abstract void fillTrapezoid(int x1, int x2, int y1, int x3, int x4, int y2, int color);
+    public abstract void fillJSTrapezoid(int x1, int x2, int y1, int x3, int x4, int y2, int color);
 
     /**
      *  Same as drawPicture, but only uses the alpha channel of the Picture, and is allowed to destructively modify the RGB
@@ -34,14 +34,14 @@ public abstract class PixelBuffer {
      */
     public abstract void drawPictureAlphaOnly(Picture source, int dx1, int dy1, int cx1, int cy1, int cx2, int cy2, int rgb);
 
-    // FIXME: we want floats (inter-pixel spacing) for antialiasing, but this hoses the fastpath line drawing... argh!
+    // FEATURE: we want floats (inter-pixel spacing) for antialiasing, but this hoses the fastpath line drawing... argh!
     /** draws a line of width <tt>w</tt>; note that the coordinates here are <i>post-transform</i> */
     public void drawLine(int x1, int y1, int x2, int y2, int w, int color, boolean capped) {
 
        if (y1 > y2) { int t = x1; x1 = x2; x2 = t; t = y1; y1 = y2; y2 = t; }
 
        if (x1 == x2) {
-            fillTrapezoid(x1 - w / 2, x2 + w / 2, y1 - (capped ? w / 2 : 0), x1 - w / 2, x2 + w / 2, y2 + (capped ? w / 2 : 0), color);
+            fillJSTrapezoid(x1 - w / 2, x2 + w / 2, y1 - (capped ? w / 2 : 0), x1 - w / 2, x2 + w / 2, y2 + (capped ? w / 2 : 0), color);
             return;
         }
 
@@ -51,9 +51,9 @@ public abstract class PixelBuffer {
             int last_x = x1;
             for(int y=y1; y<=y2; y++) {
                 int new_x = (int)((float)(y - y1) / slope) + x1;
-                if (slope >= 0) fillTrapezoid(last_x + 1, y != y2 ? new_x + 1 : new_x, y,
+                if (slope >= 0) fillJSTrapezoid(last_x + 1, y != y2 ? new_x + 1 : new_x, y,
                                               last_x + 1, y != y2 ? new_x + 1 : new_x, y + 1, color);
-                else fillTrapezoid(y != y2 ? new_x : new_x + 1, last_x, y,
+                else fillJSTrapezoid(y != y2 ? new_x : new_x + 1, last_x, y,
                                    y != y2 ? new_x : new_x + 1, last_x, y + 1, color);
                 last_x = new_x;
             }
@@ -80,9 +80,9 @@ public abstract class PixelBuffer {
            y2 += width * Math.sin(phi);
        }
 
-       fillTrapezoid(x1 + dx, x1 + dx, y1 - dy, x1 - dx, x1 - dx + slice, y1 + dy, color);           // top corner
-       fillTrapezoid(x2 + dx - slice, x2 + dx, y2 - dy, x2 - dx, x2 - dx, y2 + dy, color);           // bottom corner
-       fillTrapezoid(x1 - dx, x1 - dx + slice, y1 + dy, x2 + dx - slice, x2 + dx, y2 - dy, color);   // middle
+       fillJSTrapezoid(x1 + dx, x1 + dx, y1 - dy, x1 - dx, x1 - dx + slice, y1 + dy, color);           // top corner
+       fillJSTrapezoid(x2 + dx - slice, x2 + dx, y2 - dy, x2 - dx, x2 - dx, y2 + dy, color);           // bottom corner
+       fillJSTrapezoid(x1 - dx, x1 - dx + slice, y1 + dy, x2 + dx - slice, x2 + dx, y2 - dy, color);   // middle
     }
 
 }
index f8937ca..1640779 100644 (file)
@@ -226,8 +226,7 @@ public class Platform {
 
     /** displays a platform-specific "open file" dialog and returns the chosen filename, or null if the user hit cancel */
     protected String _fileDialog(String suggestedFileName, boolean write) { return null; }
-    public static String fileDialog(String suggestedFileName, boolean write) {
-        // FIXME: put self in background
+    public static void fileDialog(String suggestedFileName, boolean write) throws JS.Exn {
         return platform._fileDialog(suggestedFileName, write);
     }
 
@@ -276,19 +275,6 @@ public class Platform {
         Surface ret = platform._createSurface(b, framed);
         ret.setInvisible(false);
 
-        Object titlebar = b.get("titlebar", true);
-        if (titlebar != null) ret.setTitleBarText(titlebar.toString());
-
-        Object icon = b.get("icon", true);
-        if (icon != null && icon instanceof Res) {
-            /*
-              FIXME
-            Picture pic = Picture.fromRes((Res)icon);
-            if (pic != null) ret.setIcon(pic);
-            else if (Log.on) Log.log(Platform.class, "unable to load icon " + icon);
-            */
-        }
-
         ret.setLimits(b.minwidth, b.minheight, b.maxwidth, b.maxheight);
 
         if (refreshable) {
@@ -322,21 +308,6 @@ public class Platform {
     protected Scheduler _getScheduler() { return new Scheduler(); }
     public static Scheduler getScheduler() { return platform._getScheduler(); }
     
-    /** read an input stream into a byte array and invoke callback when ready */
-    protected void _inputStreamToByteArray(final InputStream is, final Callback c) {
-        new java.lang.Thread() {
-            public void run() {
-                try {
-                    final byte[] b = InputStreamToByteArray.convert(is);
-                    Scheduler.add(new Scheduler.Task() { public void perform() { c.call(b); }});
-                } catch (IOException e) {
-                    Log.log(Platform.class, e);
-                }
-            }
-        }.start();    
-    }
-    public static void inputStreamToByteArray(InputStream is, Callback c) { platform._inputStreamToByteArray(is, c); }
-    
     public static void running() { platform._running(); }
     public void _running() { new Semaphore().block(); }
 }
index 3bf88ee..b2ae73b 100644 (file)
@@ -8,56 +8,38 @@ import org.xwt.js.*;
 import org.xwt.util.*;
 import org.bouncycastle.util.encoders.Base64;
 
-/** base class for XWT resources */
+
+/** Base class for XWT resources */
 public abstract class Res extends JS {
 
-    public abstract String getDescriptiveName();
+
+    // Public Static //////////////////////////////////////////////////////////////////////
+
+    // FIXME: move to XWT.load()?
+    public static Res fromString(String url) {
+        if (url.startsWith("http://")) return new HTTP(url);
+        else if (url.startsWith("https://")) return new HTTP(url);
+        else if (url.startsWith("data:")) return new ByteArray(Base64.decode(url.substring(5)));
+        else if (url.startsWith("utf8:")) return new ByteArray(url.substring(5).getBytes());
+        throw new JS.Exn("invalid resource specifier " + url);
+    }
+
+
+    // Base Class //////////////////////////////////////////////////////////////////////
+
     public String typeName() { return "resource"; }
 
-    /** cache of subresources so that the equality operator works on them */
+    /** so that we get the same subresource each time */
     private Hash refCache = null;
 
+    /** FIXME: needed? good idea? */
     public Template t = null;
 
-    public Res getParent() { return null; }
-
-    /** an InputStream that makes sure it is not in the MessageQueue when blocked on a read */
-    // FIXME
-    private static class BackgroundInputStream extends FilterInputStream {
-        BackgroundInputStream(InputStream i) { super(i); }
-    /*
-        private void suspend() throws IOException {
-            if (!ThreadMessage.suspendThread())
-                throw new IOException("attempt to perform background-only operation in a foreground thread");
-        }
-        private void resume() {
-            ThreadMessage.resumeThread();
-        }
-        public int read() throws IOException {
-            suspend();
-            try { return super.read(); }
-            finally { resume(); }
-        }
-        public int read(byte[] b, int off, int len) throws IOException {
-            suspend();
-            try { return super.read(b, off, len); }
-            finally { resume(); }
-        }
-    */
-    }
-
-    /** returns an InputStream containing the Resource's contents */
-    public final InputStream getInputStream() throws IOException { return new BackgroundInputStream(getInputStream("")); }
+    public final InputStream getInputStream() throws IOException { return getInputStream(""); }
     public abstract InputStream getInputStream(String path) throws IOException;
 
-    /** graft newResource in place of this resource on its parent */
-    public Res graft(Object newResource) { throw new JS.Exn("cannot graft onto this resource"); }
-
-    /** if the path of this resource does not end with extension, return a new one wit it appended */
     public Res addExtension(String extension) { return new Ref(this, extension); }
 
-    public Object[] keys() { throw new JS.Exn("cannot enumerate a resource"); } 
-    public Object put(Object key, Object val) { throw new JS.Exn("cannot put to a resource"); } 
     public Object get(Object key) {
         if ("".equals(key)) {
             Template t = Template.getTemplate(addExtension(".xwt"));
@@ -71,47 +53,34 @@ public abstract class Res extends JS {
         return ret;
     }
 
-    public static Res stringToRes(String url) {
-        if (url.indexOf('!') != -1) {
-            Res ret = new Zip(stringToRes(url.substring(0, url.lastIndexOf('!'))));
-            String subpath = url.substring(url.lastIndexOf('!') + 1);
-            if (subpath.length() > 0) ret = (Res)ret.get(subpath);
-            return ret;
-        }
-        if (url.startsWith("http://")) return new HTTP(url);
-        if (url.startsWith("https://")) return new HTTP(url);
-        if (url.startsWith("cab:")) return new Cab(stringToRes(url.substring(4)));
-        if (url.startsWith("data:")) return new ByteArray(Base64.decode(url.substring(5)));
-        if (url.startsWith("utf8:")) return new ByteArray(url.substring(5).getBytes());
-        throw new JS.Exn("invalid resource specifier " + url);
-    }
 
+
+    // Caching //////////////////////////////////////////////////////////////////////
+
+    private static class NotCacheableException extends Exception { }
+    private static NotCacheableException notCacheable = new NotCacheableException();
+
+    /** if it makes sense to cache a resource, the resource must return a unique key */
+    public String getCacheKey() throws NotCacheableException { throw notCacheable; }
+
+    // FIXME: general cleanup
     /** subclass from this if you want a CachedInputStream for each path */
     public static class CachedRes extends Res {
         private Res parent;
         private boolean disk = false;
-
-        // FIXME: security concern here
-        private String subdir = null;
-
-        public String getDescriptiveName() { return parent.getDescriptiveName(); }
+        public String getCacheKey() throws NotCacheableException { return parent.getCacheKey(); }
         private Hash cachedInputStreams = new Hash();
-        public CachedRes(Res parent, String subdir, boolean disk) {
-            this.parent = parent; this.disk = disk; this.subdir = subdir;
-        }
+        public CachedRes(Res p, String s, boolean d) { this.parent = p; this.disk = d; }
         public InputStream getInputStream(String path) throws IOException {
             CachedInputStream cis = (CachedInputStream)cachedInputStreams.get(path);
             if (cis == null) {
                 java.io.File f = null;
                 if (disk) {
-                    // FIXME ugly
-                    // FIXME need separate hash for disk/nondisk
                     f = new java.io.File(System.getProperty("user.home") +
                                          java.io.File.separatorChar + ".xwt" +
                                          java.io.File.separatorChar + "caches" +
-                                         java.io.File.separatorChar + subdir +
                                          java.io.File.separatorChar +
-                                         new String(Base64.encode(parent.getDescriptiveName().getBytes())));
+                                         new String(Base64.encode(parent.getCacheKey().getBytes())));
                     Log.log(this, "caching resource in " + f);
                     new java.io.File(f.getParent()).mkdirs();
                     if (f.exists()) return new FileInputStream(f);
@@ -123,20 +92,23 @@ public abstract class Res extends JS {
         }
     }
 
+
+    // Useful Subclasses //////////////////////////////////////////////////////////////////////
+
     /** HTTP or HTTPS resource */
     public static class HTTP extends Res {
         private String url;
-        HTTP(String url) { this.url = url; }
-        public String getDescriptiveName() { return url; }
-        public InputStream getInputStream(String path) throws IOException {
-            return new org.xwt.HTTP(url + path).GET(); }
+        HTTP(String url) { while (url.endsWith('/')) url = url.substring(0, url.length() - 1); this.url = url; }
+        public String getCacheKey() throws NotCacheableException { return url; }
+        public InputStream getInputStream(String path) throws IOException { return new org.xwt.HTTP(url + path).GET(); }
     }
 
     /** byte arrays */
     public static class ByteArray extends Res {
         private byte[] bytes;
-        ByteArray(byte[] bytes) { this.bytes = bytes; }
-        public String getDescriptiveName() { return "byte[]"; }
+        private String cacheKey = null;
+        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");
             return new ByteArrayInputStream(bytes);
@@ -146,8 +118,11 @@ public abstract class Res extends JS {
     /** a file */
     public static class File extends Res {
         private String path;
-        File(String path) { this.path = path; }
-        public String getDescriptiveName() { return "file://" + path; }
+        File(String path) {
+            while (path.endsWith(java.io.File.separatorChar)) path = path.substring(0, path.length() - 1);
+            this.path = path;
+        }
+        public String getCacheKey() throws NotCacheableException { throw notCacheable; }  // already on the disk!
         public InputStream getInputStream(String rest) throws IOException {
             return new FileInputStream((path + rest).replace('/', java.io.File.separatorChar)); }
     }
@@ -156,7 +131,7 @@ public abstract class Res extends JS {
     public static class Zip extends Res {
         private Res parent;
         Zip(Res parent) { this.parent = parent; }
-        public String getDescriptiveName() { return parent.getDescriptiveName() + "!"; }
+        public String getCacheKey() throws NotCacheableException { return parent.getCacheKey() + "!zip:"; }
         public InputStream getInputStream(String path) throws IOException {
             if (path.startsWith("/")) path = path.substring(1);
             InputStream pis = parent.getInputStream();
@@ -172,9 +147,8 @@ public abstract class Res extends JS {
     public static class Cab extends Res {
         private Res parent;
         Cab(Res parent) { this.parent = parent; }
-        public String getDescriptiveName() { return "cab[" + parent.getDescriptiveName() + "]"; }
+        public String getCacheKey() throws NotCacheableException { return parent.getCacheKey() + "!cab:"; }
         public InputStream getInputStream(String path) throws IOException {
-            // FIXME: knownlength
             if (path.startsWith("/")) path = path.substring(1);
             return new org.xwt.translators.MSPack(parent.getInputStream()).getInputStream(path);
         }
@@ -183,7 +157,7 @@ public abstract class Res extends JS {
     /** the Builtin resource */
     public static class Builtin extends Res {
        public Builtin() { };
-       public String getDescriptiveName() { return "[builtin]"; }
+       public String getCacheKey() throws NotCacheableException { throw notCacheable; }    // not cacheable
        public InputStream getInputStream(String path) throws IOException {
            if (!path.equals("")) throw new IOException("the builtin resource has no subresources");
            return Platform.getBuiltinInputStream();
@@ -195,53 +169,18 @@ public abstract class Res extends JS {
         Res parent;
         Object key;
         Ref(Res parent, Object key) { this.parent = parent; this.key = key; }
-        public String getDescriptiveName() {
-            String pdn = parent.getDescriptiveName();
-           if (pdn.equals("")) return key.toString();
-           if (!pdn.endsWith("!")) pdn += ".";
-           return pdn + key.toString();
-        }
+        public String getCacheKey() throws NotCacheableException { return parent.getCacheKey() + "/" + key; }
         public Res addExtension(String extension) {
-            return (key instanceof String && ((String)key).endsWith(extension)) ? this : new Ref(parent, key + extension);
-        }
-        public InputStream getInputStream(String path) throws IOException {
-            return parent.getInputStream("/" + key + path);
-        }
-        public Res getParent() { return parent; }
-        public Res graft(Object newResource) { return new Graft(parent, key, newResource); }
-    }
-
-    // FEATURE: eliminate code duplication with JS.Graft
-    /** shadow resource which replaces the graft */
-    public static class Graft extends Res {
-        Res graftee;
-        Object replaced_key;
-        Object replaced_val;
-        Graft(Res graftee, Object key, Object val) { this.graftee = 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 InputStream getInputStream(String s) throws IOException { return graftee.getInputStream(s); }
-        public Object get(Object key) { return replaced_key.equals(key) ? replaced_val : graftee.get(key); }
-        public String getDescriptiveName() { return graftee.getDescriptiveName(); }
-        public Res getParent() { return graftee.getParent(); }
-        public Object callMethod(Object method, Array args, boolean checkOnly) throws JS.Exn {
-            if (!replaced_key.equals(method)) return graftee.callMethod(method, args, checkOnly);
-            if (replaced_val instanceof Callable) return checkOnly ? Boolean.TRUE : ((Callable)replaced_val).call(args);
-            if (checkOnly) return Boolean.FALSE;
-            throw new JS.Exn("attempt to call non-function");
-        }
-        public Number coerceToNumber() { return graftee.coerceToNumber(); }
-        public String coerceToString() { return graftee.coerceToString(); }
-        public boolean coerceToBoolean() { return graftee.coerceToBoolean(); }
-        public String typeName() { return graftee.typeName(); }
+            return ((String)key).endsWith(extension) ? this : new Ref(parent, key + extension); }
+        public InputStream getInputStream(String path) throws IOException { return parent.getInputStream("/" + key + path); }
     }
 
     /** shadow resource which replaces the graft */
     public static class ProgressWatcher extends Res {
         final Res watchee;
-        Function callback;
-        ProgressWatcher(Res watchee, Function callback) { this.watchee = watchee; this.callback = callback; }
-        public String getDescriptiveName() { return watchee.getDescriptiveName(); }
+        JSFunction callback;
+        ProgressWatcher(Res watchee, JSFunction callback) { this.watchee = watchee; this.callback = callback; }
+        public String getCacheKey() throws NotCacheableException { return watchee.getCacheKey(); }
         public InputStream getInputStream(String s) throws IOException {
             final InputStream is = watchee.getInputStream(s);
             return new FilterInputStream(is) {
@@ -255,107 +194,14 @@ public abstract class Res extends JS {
                         int ret = super.read(b, off, len);
                         if (ret != 1) bytesDownloaded += ret;
                         Scheduler.add(new Scheduler.Task() { public void perform() {
-                            JS.Array args = new JS.Array();
+                            JSArray args = new JSArray();
                             args.addElement(new Integer(bytesDownloaded));
                             args.addElement(new Integer(is instanceof KnownLength ? ((KnownLength)is).getLength() : 0));
-                            // FIXME
-                            //new JS.Context(callback, null, args).resume();
+                            callback.call(args);
                         } });
                         return ret;
                     }
                 };
         }
     }
-
-    public Object callMethod(Object method, Array args, boolean checkOnly) throws JS.Exn {
-        if (method.equals("getUTF")) {
-            if (checkOnly) return Boolean.TRUE;
-            if (args.length() != 0) return null;
-            try {
-                CharArrayWriter caw = new CharArrayWriter();
-                InputStream is = getInputStream();
-                BufferedReader r = new BufferedReader(new InputStreamReader(is));
-                char[] buf = new char[1024];
-                while(true) {
-                    int numread = r.read(buf, 0, 1024);
-                    if (numread == -1) break;
-                    caw.write(buf, 0, numread);
-                }
-                return caw.toString();
-            } catch (IOException e) {
-                if (Log.on) Log.log(Res.class, "IO Exception while reading from file");
-                if (Log.on) Log.log(Res.class, e);
-                throw new JS.Exn("error while reading from Resource");
-            }
-        } else if (method.equals("getDOM")) {
-            if (checkOnly) return Boolean.TRUE;
-            if (args.length() != 0) return null;
-            return new XMLHelper().doParse();
-        }
-        if (checkOnly) return Boolean.FALSE;
-        return null;
-    }
-
-    private class XMLHelper extends XML {
-        Vector obStack = new Vector();
-        public XMLHelper() { super(BUFFER_SIZE); }
-        public void startElement(XML.Element c) throws XML.SchemaException {
-            JS o = new JS.Obj();
-            o.put("$name", c.localName);
-            for(int i=0; i<c.len; i++) o.put(c.keys[i], c.vals[i]);
-            o.put("$numchildren", new Integer(0));
-            obStack.addElement(o);
-        }
-        public void endElement(XML.Element c) throws XML.SchemaException {
-            if (obStack.size() == 1) return;
-            JS me = (JS)obStack.lastElement();
-            obStack.setSize(obStack.size() - 1);
-            JS parent = (JS)obStack.lastElement();
-            int numchildren = ((Integer)parent.get("$numchildren")).intValue();
-            parent.put("$numchildren", new Integer(numchildren + 1));
-            parent.put(new Integer(numchildren), me);
-        }
-        public void characters(char[] ch, int start, int length) throws XML.SchemaException {
-            String s = new String(ch, start, length);
-            JS parent = (JS)obStack.lastElement();
-            int numchildren = ((Integer)parent.get("$numchildren")).intValue();
-            Object lastChild = parent.get(new Integer(numchildren - 1));
-            if (lastChild instanceof String) {
-                parent.put(new Integer(numchildren - 1), lastChild + s);
-            } else {
-                parent.put("$numchildren", new Integer(numchildren + 1));
-                parent.put(new Integer(numchildren), s);
-            }
-        }
-        public void whitespace(char[] ch, int start, int length) {}
-        public JS doParse() throws JS.Exn {
-            try { 
-                InputStream is = getInputStream();
-                BufferedReader r = new BufferedReader(new InputStreamReader(is));
-                parse(r);
-            } catch (XML.XMLException e) {
-                throw new JS.Exn("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");
-            }
-            return obStack.size() >= 1 ? (JS)obStack.elementAt(0) : null;
-        }
-    }
-
-    public void writeTo(OutputStream os) throws IOException {
-        InputStream is = getInputStream();
-        byte[] buf = new byte[1024];
-        while(true) {
-            int numread = is.read(buf, 0, 1024);
-            if (numread == -1) break;
-            if (Log.on) Log.log(this, "wrote " + numread + " bytes");
-            os.write(buf, 0, numread);
-        }
-        os.flush();
-
-        // we have to close this because flush() doesn't work on Win32-GCJ
-        os.close();
-    }
 }
index 2771304..59fc5c0 100644 (file)
@@ -43,7 +43,7 @@ class SOAP extends XMLRPC {
         if (name.equals("SOAP-ENV:Fault")) fault = true;
  
         // add a generic struct; we'll change this if our type is different
-        objects.addElement(new JS.Obj());
+        objects.addElement(new JSObj());
 
         for(int i=0; i<keys.length; i++) {
             String key = keys[i];
@@ -67,9 +67,9 @@ class SOAP extends XMLRPC {
                 } else if (value.endsWith("null")) {
                     objects.removeElementAt(objects.size() - 1);
                     objects.addElement(null);
-                } else if (value.endsWith("arrayType") || value.endsWith("JS.Array") || key.endsWith("arrayType")) {
+                } else if (value.endsWith("arrayType") || value.endsWith("JSArray") || key.endsWith("arrayType")) {
                     objects.removeElementAt(objects.size() - 1);
-                    objects.addElement(new JS.Array());
+                    objects.addElement(new JSArray());
                 }
             }
         }
@@ -147,9 +147,9 @@ class SOAP extends XMLRPC {
         if (objects.size() < 2) return;
 
         // our parent "should" be an aggregate type -- add ourselves to it.
-        if (parent != null && parent instanceof JS.Array) {
+        if (parent != null && parent instanceof JSArray) {
             objects.removeElementAt(objects.size() - 1);
-            ((JS.Array)parent).addElement(me);
+            ((JSArray)parent).addElement(me);
 
         } else if (parent != null && parent instanceof JS) {
             objects.removeElementAt(objects.size() - 1);
@@ -221,8 +221,8 @@ class SOAP extends XMLRPC {
             }
             sb.append("</" + name + ">\r\n");
 
-        } else if (o instanceof JS.Array) {
-            JS.Array a = (JS.Array)o;
+        } else if (o instanceof JSArray) {
+            JSArray a = (JSArray)o;
             sb.append("                <" + name + " SOAP-ENC:arrayType=\"xsd:ur-type[" + a.length() + "]\">");
             for(int i=0; i<a.length(); i++) appendObject("item", a.elementAt(i), sb);
             sb.append("</" + name + ">\r\n");
@@ -230,13 +230,17 @@ class SOAP extends XMLRPC {
         } else if (o instanceof JS) {
             JS j = (JS)o;
             sb.append("                <" + name + ">");
-            Object[] ids = j.keys();
-            for(int i=0; i<ids.length; i++) appendObject(ids[i].toString(), j.get(ids[i].toString()), sb);
+            Enumeration e = j.keys();
+            while(e.hasMoreElements()) {
+                Object key = e.nextElement();
+                appendObject(key, j.get(key), sb);
+            }
             sb.append("</" + name + ">\r\n");
+
         }
     }
 
-    protected String send(JS.Array args, HTTP http) throws JS.Exn, IOException {
+    protected String send(JSArray args, HTTP http) throws JS.Exn, IOException {
         // build up the request
         StringBuffer content = new StringBuffer();
         content.append("SOAPAction: " + action + "\r\n\r\n");
@@ -252,9 +256,11 @@ class SOAP extends XMLRPC {
         content.append(nameSpace != null ? " xmlns=\"" + nameSpace + "\"" : "");
         content.append(">\r\n");
         if (args.length() > 0) {
-            Object[] o = ((JS)args.elementAt(0)).keys();
-            for(int i=0; i<o.length; i++)
-                appendObject(o[i].toString(), ((JS)args.elementAt(0)).get(o[i].toString()), content);
+            Enumeration e = ((JS)args.elementAt(0)).keys();
+            while(e.hasMoreElements()) {
+                Object key = e.nextElement();
+                appendObject(key, ((JS)args.elementAt(0)).get(key), content);
+            }
         }
         content.append("    </" + methodname + "></SOAP-ENV:Body></SOAP-ENV:Envelope>\r\n");
         return content.toString();
index a46c75c..79fba3c 100644 (file)
@@ -10,34 +10,35 @@ public class Scheduler {
 
     // Public API Exposed to org.xwt /////////////////////////////////////////////////
 
+    private static Scheduler singleton;
     public static abstract class Task { public abstract void perform(); }
     public static void add(Task t) { singleton.runnable.append(t); }
-    public static void init() { singleton.run(); }
+    public static void init() {
+        if (singleton != null) return;
+        singleton = Platform.getScheduler();
+        singleton.run();
+    }
 
 
     // API which must be supported by subclasses /////////////////////////////////////
 
-    protected Scheduler() { }
-
     /**
-     *  INVARIANT: all scheduler implementations MUST invoke
-     *  Surface.renderAll() after performing a Task if no tasks remain
-     *  in the queue.  A scheduler may choose to invoke
-     *  Surface.renderAll() more often than that if it so chooses.
+     *  INVARIANT: all scheduler implementations MUST invoke Surface.renderAll() after performing a Task if no tasks remain
+     *  in the queue.  A scheduler may choose to invoke Surface.renderAll() more often than that if it so chooses.
      */
     public void run() { defaultRun(); }
+    protected Scheduler() { }
 
 
-    // Default Implementation /////////////////////////////////////
+    // Default Implementation //////////////////////////////////////////////////////
 
     protected static Queue runnable = new Queue(50);
-    private static final Scheduler singleton = Platform.getScheduler();
     public void defaultRun() {
         while(true) {
             Task t = (Task)runnable.remove(true);
             try {
                 t.perform();
-                // FIXME: be smarter about this
+                // FEATURE: be smarter about this
                 Surface.renderAll();
             } catch (Exception e) {
                 Log.log(Scheduler.class, "Task threw an exception: " + e);
index 89906b5..a187981 100644 (file)
@@ -17,7 +17,6 @@ import java.util.*;
  *  Scheduler-time (the size/position/state at the time that the
  *  now-executing message was enqueued). This distinction is important.
  */
-// FIXME: put the scar box back in
 public abstract class Surface extends PixelBuffer {
 
     public int getWidth() { return root == null ? 0 : root.width; }
@@ -25,7 +24,6 @@ public abstract class Surface extends PixelBuffer {
         
     // Static Data ////////////////////////////////////////////////////////////////////////////////
 
-    // FIXME
     private abstract static class Message extends Scheduler.Task {
         public abstract void perform();
         public Object call(Object arg) { perform(); return null; }
@@ -111,7 +109,7 @@ public abstract class Surface extends PixelBuffer {
             final Box who = Box.whoIs(root, mousex, mousey);
             Scheduler.add(new Message() { public void perform() {
                 Platform.clipboardReadEnabled = true;
-                root.put("Press3", Boolean.TRUE);
+                root.putAndTriggerJSTraps("Press3", Boolean.TRUE);
                 Platform.clipboardReadEnabled = false;
             }});
         }
@@ -172,18 +170,18 @@ public abstract class Surface extends PixelBuffer {
         public KMessage(String k) { key = k; }
         public void perform() {
             if (key.equals("C-v") || key.equals("A-v")) Platform.clipboardReadEnabled = true;
-            /* FIXME
             outer: for(int i=0; i<keywatchers.size(); i++) {
                 Box b = (Box)keywatchers.elementAt(i);
-                for(Box cur = b; cur != null; cur = cur.getParent())
-                    if ((cur.flags & cur.INVISIBLE_FLAG) != 0) continue outer;
-                b.put("KeyPressed", key);
+                for(Box cur = b; cur != null; cur = cur.parent)
+                    if (!cur.test(cur.VISIBLE)) continue outer;
+                b.putAndTriggerJSTraps("KeyPressed", key);
             }
-            */
             Platform.clipboardReadEnabled = false;
         }
     }
 
+    Vec keywatchers = new Vec();
+
     /** sends a KeyReleased message; subclasses should not add the C- or A- prefixes, nor should they capitalize alphabet characters */
     protected final void KeyReleased(final String key) {
         if (key == null) return;
@@ -191,14 +189,12 @@ public abstract class Surface extends PixelBuffer {
         else if (key.toLowerCase().equals("control")) control = false;
         else if (key.toLowerCase().equals("shift")) shift = false;
         Scheduler.add(new Message() { public void perform() {
-            /* FIXME
             outer: for(int i=0; i<keywatchers.size(); i++) {
                 Box b = (Box)keywatchers.elementAt(i);
-                for(Box cur = b; cur != null; cur = cur.getParent())
-                    if ((cur.flags & cur.INVISIBLE_FLAG) != 0) continue outer;
-                b.put("KeyReleased", key);
+                for(Box cur = b; cur != null; cur = cur.parent)
+                    if (!cur.test(cur.VISIBLE)) continue outer;
+                b.putAndTriggerJSTraps("KeyReleased", key);
             }
-            */
         }});
     }
 
@@ -225,7 +221,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.put("Move", Boolean.TRUE);
+                    root.putAndTriggerJSTraps("Move", Boolean.TRUE);
 
                 root.Move(oldmousex, oldmousey, mousex, mousey);
                 if (!cursor.equals(oldcursor)) syncCursor();
@@ -236,7 +232,7 @@ public abstract class Surface extends PixelBuffer {
     protected final void SizeChange(final int width, final int height) {
         Scheduler.add(new Message() { public void perform() {
             if (width == root.width && height == root.height) return;
-            root.needs_reflow = true;
+            root.set(REFLOW);
             do { abort = false; root.reflow(width, height); } while(abort);
         }});
         abort = true;
@@ -246,7 +242,7 @@ public abstract class Surface extends PixelBuffer {
         Scheduler.add(new Message() { public void perform() {
             root.x = x;
             root.y = y;
-            root.put("PosChange", Boolean.TRUE);
+            root.putAndTriggerJSTraps("PosChange", Boolean.TRUE);
         }});
     }
 
@@ -286,15 +282,23 @@ public abstract class Surface extends PixelBuffer {
         Refresh();
     }
 
+    public static Surface fromBox(Box b) {
+        for(int i=0; i<allSurfaces.size(); i++) {
+            Surface s = (Surface)allSurfaces.elementAt(i);
+            if (s.root == b) return s;
+        }
+        return null;
+    }
+
     public Surface(Box root) {
 
         this.root = root;
-        if (root.surface != null && root.surface.root == root) root.surface.dispose(false);
+        Surface old = fromBox(root);
+        if (old != null) old.dispose(false);
         else root.remove();
-        root.surface = this;
 
         // make sure the root is properly sized
-        do { abort = false; root.reflow(); } while(abort);
+        do { abort = false; root.reflow(root.width, root.height); } while(abort);
 
         root.dirty();
         Refresh();
@@ -308,7 +312,7 @@ public abstract class Surface extends PixelBuffer {
         // make sure the root is properly sized
         do {
             abort = false;
-            root.reflow();
+            root.reflow(root.width, root.height);
             setSize(root.width, root.height);
             // update mouseinside and trigger Enter/Leave as a result of box size/position changes
             String oldcursor = cursor;
@@ -317,7 +321,7 @@ public abstract class Surface extends PixelBuffer {
             if (!cursor.equals(oldcursor)) syncCursor();
         } while(abort);
 
-        Box.sizePosChangesSinceLastRender = 0;
+        //Box.sizePosChangesSinceLastRender = 0;
         int[][] dirt = dirtyRegions.flush();
         for(int i = 0; dirt != null && i < dirt.length; i++) {
             if (dirt[i] == null) continue;
@@ -364,7 +368,7 @@ public abstract class Surface extends PixelBuffer {
             Scheduler.add(this);
         }
         
-        public void perform() { boxContainingMouse.put(name, value); }
+        public void perform() { boxContainingMouse.putAndTriggerJSTraps(name, value); }
         public String toString() { return "SimpleMessage [name=" + name + ", value=" + value + "]"; }
 
     }
@@ -388,9 +392,9 @@ public abstract class Surface extends PixelBuffer {
             backbuffer.drawPictureAlphaOnly(source, dx, dy, cx1, cy1, cx2, cy2, argb);
         }
 
-        public void fillTrapezoid(int x1, int x2, int y1, int x3, int x4, int y2, int color) {
+        public void fillJSTrapezoid(int x1, int x2, int y1, int x3, int x4, int y2, int color) {
             screenDirtyRegions.dirty(Math.min(x1, x3), y1, Math.max(x2, x4) - Math.min(x1, x3), y2 - y1);
-            backbuffer.fillTrapezoid(x1, x2, y1, x3, x4, y2, color); }
+            backbuffer.fillJSTrapezoid(x1, x2, y1, x3, x4, y2, color); }
 
         public void render() {
             super.render();
index 9e6699b..6956027 100644 (file)
@@ -25,31 +25,31 @@ public class Template {
 
     // Instance Members ///////////////////////////////////////////////////////
 
-    String id = null;                  ///< the id of this box
-    String redirect = null;            ///< the id of the redirect target; only meaningful on a root node
-    private String[] keys;             ///< keys to be "put" to instances of this template; elements correspond to those of vals
-    private Object[] vals;             ///< values to be "put" to instances of this template; elements correspond to those of keys
-    private Vec children = new Vec();  ///< during XML parsing, this holds the list of currently-parsed children; null otherwise
-    private int numunits = -1;         ///< see numUnits(); -1 means that this value has not yet been computed
+    String id = null;                     ///< the id of this box
+    String redirect = null;               ///< the id of the redirect target; only meaningful on a root node
+    private String[] keys;                ///< keys to be "put" to instances of this template; elements correspond to those of vals
+    private Object[] vals;                ///< values to be "put" to instances of this template; elements correspond to those of keys
+    private Vec children = new Vec();     ///< during XML parsing, this holds the list of currently-parsed children; null otherwise
+    private int numunits = -1;            ///< see numUnits(); -1 means that this value has not yet been computed
 
-    private Function script = null;       ///< the script on this node
-    private String fileName = "unknown";             ///< the filename this node came from; used only for debugging
-    private Vec preapply = new Vec();                ///< templates that should be preapplied (in the order of application)
+    private JSFunction script = null;       ///< the script on this node
+    private String fileName = "unknown";  ///< the filename this node came from; used only for debugging
+    private Vec preapply = new Vec();     ///< templates that should be preapplied (in the order of application)
 
 
     // Instance Members that are only meaningful on root Template //////////////////////////////////////
 
-    private JS.Scope staticScope = null;             ///< the scope in which the static block is executed
-    private Function staticscript = null; ///< the script on the static node of this template, null already performed
+    private JSScope staticJSScope = null;   ///< the scope in which the static block is executed
+    private JSFunction staticscript = null;  ///< the script on the static node of this template, null already performed
 
 
     // Only used during parsing /////////////////////////////////////////////////////////////////
 
-    private StringBuffer content = null; ///< during XML parsing, this holds partially-read character data; null otherwise
-    private int content_start = 0;       ///< line number of the first line of <tt>content</tt>
-    private int content_lines = 0;       ///< number of lines in <tt>content</tt>
-    private int startLine = -1;          ///< the line number that this element starts on
-    private final Res r;                 ///< the resource we came from
+    private StringBuffer content = null;   ///< during XML parsing, this holds partially-read character data; null otherwise
+    private int content_start = 0;         ///< line number of the first line of <tt>content</tt>
+    private int content_lines = 0;         ///< number of lines in <tt>content</tt>
+    private int startLine = -1;            ///< the line number that this element starts on
+    private final Res r;                   ///< the resource we came from
 
 
     // Static data/methods ///////////////////////////////////////////////////////////////////
@@ -70,7 +70,7 @@ public class Template {
     public static Res resolveStringToResource(String str, XWT xwt, boolean permitAbsolute) {
         // URL
         if (str.indexOf("://") != -1) {
-            if (permitAbsolute) return Res.stringToRes(str);
+            if (permitAbsolute) return Res.fromString(str);
             Log.log(Template.class, "absolute URL " + str + " not permitted here");
             return null;
         }
@@ -92,21 +92,21 @@ public class Template {
     private Template(Res r) { this.r = r; }
 
     /** called before this template is applied or its static object can be externally referenced */
-    JS.Scope getStatic() {
-        if (staticScope == null) staticScope = new JS.Scope(null);
-        if (staticscript == null) return staticScope;
-        Function temp = staticscript;
+    JSScope getStatic() {
+        if (staticJSScope == null) staticJSScope = new JSScope(null);
+        if (staticscript == null) return staticJSScope;
+        JSFunction temp = staticscript;
         staticscript = null;
-        new JS.Context(temp, staticScope).resume();
-        return staticScope;
+        JSContext.invoke(temp.cloneWithNewParentJSSCope(staticJSScope));
+        return staticJSScope;
     }
     
     /** Applies the template to Box b
      *  @param pboxes a vector of all box parents on which to put $-references
      *  @param ptemplates a vector of the fileNames to recieve private references on the pboxes
      */
-    void apply(Box b, JS.Callable c, XWT xwt) { apply(b, c, xwt, null); }
-    void apply(Box b, JS.Callable callback, XWT xwt, PerInstantiationScope parentPis) {
+    void apply(Box b, XWT xwt) { apply(b, xwt, null); }
+    void apply(Box b, XWT xwt, PerInstantiationJSScope parentPis) {
 
         getStatic();
 
@@ -114,24 +114,22 @@ public class Template {
         for(int i=0; i<preapply.size(); i++) {
             Template t = getTemplate(resolveStringToResource((String)preapply.elementAt(i), xwt, false));
             if (t == null) throw new RuntimeException("unable to resolve resource " + preapply.elementAt(i));
-            t.apply(b, callback, xwt);
+            t.apply(b, xwt);
         }
 
-        PerInstantiationScope pis = new PerInstantiationScope(b, xwt, parentPis, staticScope);
+        PerInstantiationJSScope pis = new PerInstantiationJSScope(b, xwt, parentPis, staticJSScope);
         for (int i=0; children != null && i<children.size(); i++) {
-            Box kid = new Box();
-            ((Template)children.elementAt(i)).apply(kid, callback, xwt, pis);
-
-            // FIXME: tailcall?
-            b.put(b.numChildren(), kid);
+            Box kid = new BoxTree();
+            ((Template)children.elementAt(i)).apply(kid, xwt, pis);
+            b.putAndTriggerTraps(b.numChildren(), kid);
         }
 
-        if (script != null) new JS.Context(script, pis).resume();
+        if (script != null) JSContext.invoke(script.cloneWithNewParentJSScope(pis));
 
         for(int i=0; keys != null && i<keys.length; i++)
-            if (vals[i] instanceof String && ((String)vals[i]).charAt(0) == '$') b.put(keys[i], pis.get(vals[i]));
-            else if ("image".equals(keys[i])) b.put("image", resolveStringToResource((String)vals[i], xwt, true));
-            else if (keys[i] != null) b.put(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]);
     }
 
 
@@ -175,7 +173,7 @@ public class Template {
                 if (nameOfHeaderNodeBeingProcessed != null) throw new XML.SchemaException("can't nest header nodes");
                 nameOfHeaderNodeBeingProcessed = c.localName;
                 if (c.localName.equals("doc")) {
-                    // FIXME: ignore
+                    // FEATURE
                 } else if (c.localName.equals("static")) {
                     if (t.staticscript != null)
                         throw new XML.SchemaException("the <static> header node may not appear more than once");
@@ -279,8 +277,8 @@ public class Template {
             }
         }
 
-        private Function parseScript(boolean isstatic) {
-            Function thisscript = null;
+        private JSFunction parseScript(boolean isstatic) {
+            JSFunction thisscript = null;
             try {
                 thisscript = JS.parse(t.fileName + (isstatic ? "._" : ""), t.content_start, new StringReader(t.content.toString()));
             } catch (IOException ioe) {
@@ -337,17 +335,17 @@ public class Template {
         public void whitespace(char[] ch, int start, int length) throws XML.SchemaException { }
     }
 
-    private static class PerInstantiationScope extends JS.Scope {
+    private static class PerInstantiationJSScope extends JSScope {
         XWT xwt = null;
-        PerInstantiationScope parentBoxPis = null;
-        JS.Scope myStatic = null;
+        PerInstantiationJSScope parentBoxPis = null;
+        JSScope myStatic = null;
         void putDollar(String key, Box target) {
             if (parentBoxPis != null) parentBoxPis.putDollar(key, target);
             declare("$" + key);
             put("$" + key, target);
         }
-        public PerInstantiationScope(Scope parentScope, XWT xwt, PerInstantiationScope parentBoxPis, JS.Scope myStatic) {
-            super(parentScope);
+        public PerInstantiationJSScope(JSScope parentJSScope, XWT xwt, PerInstantiationJSScope parentBoxPis, JSScope myStatic) {
+            super(parentJSScope);
             this.parentBoxPis = parentBoxPis;
             this.xwt = xwt;
             this.myStatic = myStatic;
@@ -358,9 +356,9 @@ public class Template {
             if (key.equals("static")) return myStatic;
             return super.get(key);
         }
-        public Object put(Object key, Object val) {
-            if (super.has(key)) return super.put(key, val);
-            return super.put(key, val);
+        public void put(Object key, Object val) {
+            if (super.has(key)) super.put(key, val);
+            else super.put(key, val);
         }
     }
 
diff --git a/src/org/xwt/Trap.java b/src/org/xwt/Trap.java
deleted file mode 100644 (file)
index 7d9dcc1..0000000
+++ /dev/null
@@ -1,153 +0,0 @@
-// Copyright 2003 Adam Megacz, see the COPYING file for licensing [GPL]
-package org.xwt;
-
-import java.util.*;
-import org.xwt.js.*;
-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 Trap {
-
-    // Static Data //////////////////////////////////////////////////////////////
-
-    private static Function cascadeHelper = null;
-    private static String cascadeHelperText =
-        "return function(q) { var ret = arguments.doTrap;" +
-        "if (ret != false && !arguments.didCascade) arguments.cascade = q; };";
-    static {
-        try {
-            cascadeHelper = JS.parse("cascadeHelper", 1, new StringReader(cascadeHelperText));
-            cascadeHelper = (Function)new JS.Context(cascadeHelper, null).resume();
-        } catch (Exception e) {
-            Log.log(Trap.class, e);
-        }
-    }
-
-    /** List of properties that cannot be trapped */
-    private static final Hash PROHIBITED = new Hash(120, 3);
-
-    static {
-        String[] p = new String[] {
-            "shrink", "hshrink", "vshrink", "x", "y",
-            "width", "height", "flex", "colspan", "rowspan", "cols",
-            "rows", "align", "invisible", "absolute", "globalx",
-            "globaly", "minwidth", "minheight", "height", "width",
-            "maxwidth", "maxheight", "numchildren", "hpad", "vpad",
-            "buffered", "cursor", "mousex", "mousey",
-            "mouseinside", "thisbox", "indexof", "path", "font", "fontsize"
-        };
-        for(int i=0; i<p.length; i++) PROHIBITED.put(p[i], Boolean.TRUE);
-    };
-
-
-    // Instance Members ////////////////////////////////////////////////////////
-
-    /** the box on which this trap was placed */
-    private Box trapee = null;
-
-    /** the function for this trap */
-    Function f = null;
-
-    /** the next trap down the trap stack */
-    private Trap next = null;
-
-    /** the property that the trap was placed on */
-    private Object name = null;
-
-
-    // Static Methods //////////////////////////////////////////////////////////////////////////
-
-    /**
-     *  adds a trap.
-     *  @param trapee the box to place the trap on
-     *  @param name the name of the property to trap on
-     *  @param f the function to place as a trap
-     */
-    static void addTrap(Box trapee, Object name, Function f) {
-
-        // check if this script has already placed a trap on this property
-        for(Trap t = (Trap)trapee.get(name, Trap.class); t != null; t = t.next)
-            if (t.f == f) return;
-        
-        // actually place the trap
-        trapee.put2(name, Trap.class, new Trap(trapee, name.toString(), f, (Trap)trapee.get(name, Trap.class)));
-    }
-
-
-    /**
-     *  deletes a trap.
-     *  @param trapee the box to remove the trap from
-     *  @param name the name of the property to trap on
-     *  @param f the function to remove
-     */
-    static void delTrap(Box trapee, Object name, Function f) {
-        Trap t = (Trap)trapee.get(name, Trap.class);
-        if (t.f == f) { trapee.put2(name, Trap.class, t.next); return; }
-        for(; t.next != null; t = t.next)
-            if (t.next.f == f) { t.next = t.next.next; return; }
-        Log.logJS("warning: tried to remove a trap that had not been placed");
-    }
-
-
-    // Instance Methods //////////////////////////////////////////////////////////////////////////
-
-    private Trap(Box b, String n, Function f, Trap nx)
-    { trapee = b; name = n; this.f = f; this.next = nx; }
-
-    // Read Traps //////////////////////////////////////////////////////////////////////
-
-    public Object perform() {
-        if (f.getNumFormalArgs() > 0) return cascade();
-        else return new JS.TailCall().set(f, new TrapArgs(this));
-    }
-
-    public Object cascade() {
-        if (next != null) return next.perform();
-        else return trapee.get(name, true);
-    }
-
-    // Write Traps //////////////////////////////////////////////////////////////////////
-
-    public Object perform(Object val) {
-        if (f.getNumFormalArgs() == 0) return cascade(val);
-        else return new JS.TailCall().set(cascadeHelper, new TrapArgs(this, val));
-    }
-    
-    public Object cascade(Object val) {
-        if (next != null) return next.perform(val);
-        else return trapee.put(name, val, true);
-    }
-
-    // Args ///////////////////////////////////////////////////////////////////////////
-
-    private static class TrapArgs extends JS.Array {
-        private Trap t;
-        public boolean cascadeHappened = false;
-        public TrapArgs(Trap t) { this.t = t; }
-        public TrapArgs(Trap t, Object value) { this.t = t; addElement(value); }
-        
-        public Object put(Object key, Object val) {
-            if (key.equals("cascade")) { cascadeHappened = true; return t.cascade(val); }
-            else return super.put(key, val);
-        }
-
-        public Object get(Object key) {
-            // common case
-            if(!(key instanceof String)) return super.get(key);
-            if (key.equals("trapee")) return t.trapee;
-            if (key.equals("doTrap")) return new JS.TailCall().set(t.f, this);
-            if (key.equals("didCascade")) return cascadeHappened ? Boolean.TRUE : Boolean.FALSE;
-            if (key.equals("trapname")) return t.name;
-            if (key.equals("cascade")) return t.cascade();
-            if (key.equals("callee")) return t.f;
-            return super.get(key);
-        }
-    }
-}
-
index 55c0930..212052e 100644 (file)
@@ -40,6 +40,7 @@ public final class VectorGraphics {
     // Public entry points /////////////////////////////////////////////////////////////////
 
     public static VectorPath parseVectorPath(String s) {
+        if (s == null) return null;
         PathTokenizer t = new PathTokenizer(s);
         VectorPath ret = new VectorPath();
         char last_command = 'M';
@@ -119,6 +120,72 @@ public final class VectorGraphics {
 
     // PathTokenizer //////////////////////////////////////////////////////////////////////////////
 
+    public static Affine parseTransform(String t) {
+        if (t == null) return null;
+        t = t.trim();
+        Affine ret = VectorGraphics.Affine.identity();
+        while (t.length() > 0) {
+            if (t.startsWith("skewX(")) {
+                // FIXME
+                
+            } else if (t.startsWith("shear(")) {
+                // FIXME: nonstandard; remove this
+                ret.multiply(VectorGraphics.Affine.shear(Float.parseFloat(t.substring(t.indexOf('(') + 1, t.indexOf(')')))));
+                
+            } else if (t.startsWith("skewY(")) {
+                // FIXME
+                
+            } else if (t.startsWith("rotate(")) {
+                String sub = t.substring(t.indexOf('(') + 1, t.indexOf(')'));
+                if (sub.indexOf(',') != -1) {
+                    float angle = Float.parseFloat(sub.substring(0, sub.indexOf(',')));
+                    sub = sub.substring(sub.indexOf(',') + 1);
+                    float cx = Float.parseFloat(sub.substring(0, sub.indexOf(',')));
+                    sub = sub.substring(sub.indexOf(',') + 1);
+                    float cy = Float.parseFloat(sub);
+                    ret.multiply(VectorGraphics.Affine.translate(cx, cy));
+                    ret.multiply(VectorGraphics.Affine.rotate(angle));
+                    ret.multiply(VectorGraphics.Affine.translate(-1 * cx, -1 * cy));
+                } else {
+                    ret.multiply(VectorGraphics.Affine.rotate(Float.parseFloat(t.substring(t.indexOf('(') + 1, t.indexOf(')')))));
+                }
+                
+            } else if (t.startsWith("translate(")) {
+                String sub = t.substring(t.indexOf('(') + 1, t.indexOf(')'));
+                if (sub.indexOf(',') > -1) {
+                    ret.multiply(VectorGraphics.Affine.translate(Float.parseFloat(t.substring(t.indexOf('(') + 1, t.indexOf(','))),
+                                                                 Float.parseFloat(t.substring(t.indexOf(',') + 1, t.indexOf(')')))));
+                } else {
+                    ret.multiply(VectorGraphics.Affine.translate(Float.parseFloat(t.substring(t.indexOf('(') + 1, t.indexOf(','))), 0));
+                }
+                
+            } else if (t.startsWith("flip(")) {
+                String which = t.substring(t.indexOf('(') + 1, t.indexOf(')'));
+                ret.multiply(VectorGraphics.Affine.flip(which.equals("horizontal"), which.equals("vertical")));
+                
+            } else if (t.startsWith("scale(")) {
+                String sub = t.substring(t.indexOf('(') + 1, t.indexOf(')'));
+                if (sub.indexOf(',') > -1) {
+                    ret.multiply(VectorGraphics.Affine.scale(Float.parseFloat(t.substring(t.indexOf('(') + 1, t.indexOf(','))),
+                                                             Float.parseFloat(t.substring(t.indexOf(',') + 1, t.indexOf(')')))));
+                } else {
+                    ret.multiply(VectorGraphics.Affine.scale(Float.parseFloat(t.substring(t.indexOf('(') + 1, t.indexOf(','))),
+                                                             Float.parseFloat(t.substring(t.indexOf('(') + 1, t.indexOf(',')))));
+                }
+                
+            } else if (t.startsWith("matrix(")) {
+                // FIXME: is this mapped right?
+                float d[] = new float[6];
+                StringTokenizer st = new StringTokenizer(t, ",", false);
+                for(int i=0; i<6; i++)
+                    d[i] = Float.parseFloat(st.nextToken());
+                ret.multiply(new VectorGraphics.Affine(d[0], d[1], d[2], d[3], d[4], d[5]));
+            }
+            t = t.substring(t.indexOf(')') + 1).trim();
+        }
+        return ret;
+    }
+    
     public static final float PX_PER_INCH = 72;
     public static final float INCHES_PER_CM = (float)0.3937;
     public static final float INCHES_PER_MM = INCHES_PER_CM / 10;
@@ -553,7 +620,7 @@ public final class VectorGraphics {
                     if (leftSegment == rightSegment || rightSegment == Integer.MAX_VALUE) break;
                     if (leftSegment != -1)
                         if ((useEvenOdd && count % 2 != 0) || (!useEvenOdd && count != 0))
-                            paint.fillTrapezoid(intercept(edges[leftSegment], y0, true, true),
+                            paint.fillJSTrapezoid(intercept(edges[leftSegment], y0, true, true),
                                                 intercept(edges[rightSegment], y0, true, true), y0,
                                                 intercept(edges[leftSegment], y1, true, true),
                                                 intercept(edges[rightSegment], y1, true, true), y1,
@@ -642,14 +709,14 @@ public final class VectorGraphics {
 
     public static interface Paint {
        public abstract void
-            fillTrapezoid(int tx1, int tx2, int ty1, int tx3, int tx4, int ty2, PixelBuffer buf);
+            fillJSTrapezoid(int tx1, int tx2, int ty1, int tx3, int tx4, int ty2, PixelBuffer buf);
     }
 
     public static class SingleColorPaint implements Paint {
         int color;
         public SingleColorPaint(int color) { this.color = color; }
-        public void fillTrapezoid(int x1, int x2, int y1, int x3, int x4, int y2, PixelBuffer buf) {
-            buf.fillTrapezoid(x1, x2, y1, x3, x4, y2, color);
+        public void fillJSTrapezoid(int x1, int x2, int y1, int x3, int x4, int y2, PixelBuffer buf) {
+            buf.fillJSTrapezoid(x1, x2, y1, x3, x4, y2, color);
         }
     }
 
@@ -682,7 +749,7 @@ public final class VectorGraphics {
        int[] stop_colors;
        float[] stop_offsets;
 
-       public void fillTrapezoid(float tx1, float tx2, float ty1, float tx3, float tx4, float ty2, PixelBuffer buf) {
+       public void fillJSTrapezoid(float tx1, float tx2, float ty1, float tx3, float tx4, float ty2, PixelBuffer buf) {
             Affine a = buf.a;
            Affine inverse = a.copy().invert();
            float slope1 = (tx3 - tx1) / (ty2 - ty1);
index e32f569..8368bf7 100644 (file)
@@ -30,9 +30,7 @@ import org.bouncycastle.util.encoders.Base64;
  *         convert.
  *  </ol>
  */
-class XMLRPC extends JS.Callable {
-
-    public Object[] keys() { throw new Error("not implemented"); }
+class XMLRPC extends JSCallable {
 
     /** the url to connect to */
     protected String url = null;
@@ -57,7 +55,7 @@ class XMLRPC extends JS.Callable {
      *
      *  If an &lt;array&gt; tag is encountered, a null is pushed onto the
      *  stack. When a &lt;/data&gt; is encountered, we search back on the
-     *  stack to the last null, replace it with a NativeJS.Array, and
+     *  stack to the last null, replace it with a NativeJSArray, and
      *  insert into it all elements above it on the stack.
      *
      *  If a &lt;struct&gt; tag is encountered, a JSObject is pushed
@@ -84,7 +82,7 @@ class XMLRPC extends JS.Callable {
         public void startElement(XML.Element c) {
             content.reset();
             if (c.localName.equals("fault")) fault = true;
-            else if (c.localName.equals("struct")) objects.setElementAt(new JS.Obj(), objects.size() - 1);
+            else if (c.localName.equals("struct")) objects.setElementAt(new JSObj(), objects.size() - 1);
             else if (c.localName.equals("array")) objects.setElementAt(null, objects.size() - 1);
             else if (c.localName.equals("value")) objects.addElement("");
         }
@@ -121,8 +119,8 @@ class XMLRPC extends JS.Callable {
                 if (i > 0) s = s.substring(i);
                 
                 try {
-                    org.xwt.js.Date nd = new org.xwt.js.Date();
-                    double date = org.xwt.js.Date.date_msecFromDate(Double.valueOf(s.substring(0, 4)).doubleValue(),
+                    JSDate nd = new JSDate();
+                    double date = JSDate.date_msecFromDate(Double.valueOf(s.substring(0, 4)).doubleValue(),
                                                                     Double.valueOf(s.substring(4, 6)).doubleValue() - 1,
                                                                     Double.valueOf(s.substring(6, 8)).doubleValue(),
                                                                     Double.valueOf(s.substring(9, 11)).doubleValue(),
@@ -130,7 +128,7 @@ class XMLRPC extends JS.Callable {
                                                                     Double.valueOf(s.substring(15, 17)).doubleValue(),
                                                                     (double)0
                                                                     );
-                    nd.jsFunction_setTime(org.xwt.js.Date.internalUTC(date));
+                    nd.jsJSFunction_setTime(JSDate.internalUTC(date));
                     objects.setElementAt(nd, objects.size() - 1);
                     
                 } catch (Exception e) {
@@ -148,7 +146,7 @@ class XMLRPC extends JS.Callable {
             } else if (c.localName.equals("data")) {
                 int i;
                 for(i=objects.size() - 1; objects.elementAt(i) != null; i--);
-                JS.Array arr = new JS.Array();
+                JSArray arr = new JSArray();
                 for(int j = i + 1; j<objects.size(); j++) arr.put(new Integer(j - i - 1), objects.elementAt(j));
                 objects.setElementAt(arr, i);
                 objects.setSize(i + 1);
@@ -237,9 +235,9 @@ class XMLRPC extends JS.Callable {
             }
             sb.append("</string></value>\n");
 
-        } else if (o instanceof org.xwt.js.Date) {
+        } else if (o instanceof JSDate) {
             sb.append("                <value><dateTime.iso8601>");
-            java.util.Date d = new java.util.Date(((org.xwt.js.Date)o).getRawTime());
+            java.util.Date d = new java.util.Date(((JSDate)o).getRawTime());
             sb.append(d.getYear() + 1900);
             if (d.getMonth() + 1 < 10) sb.append('0');
             sb.append(d.getMonth() + 1);
@@ -256,11 +254,11 @@ class XMLRPC extends JS.Callable {
             sb.append(d.getSeconds());
             sb.append("</dateTime.iso8601></value>\n");
 
-        } else if (o instanceof JS.Array) {
+        } else if (o instanceof JSArray) {
             if (tracker.get(o) != null) throw new JS.Exn("attempted to send multi-ref data structure via XML-RPC");
             tracker.put(o, Boolean.TRUE);
             sb.append("                <value><array><data>\n");
-            JS.Array a = (JS.Array)o;
+            JSArray a = (JSArray)o;
             for(int i=0; i<a.length(); i++) appendObject(a.elementAt(i), sb);
             sb.append("                </data></array></value>\n");
 
@@ -269,10 +267,11 @@ class XMLRPC extends JS.Callable {
             tracker.put(o, Boolean.TRUE);
             JS j = (JS)o;
             sb.append("                <value><struct>\n");
-            Object[] ids = j.keys();
-            for(int i=0; i<ids.length; i++) {
-                sb.append("                <member><name>" + ids[i] + "</name>\n");
-                appendObject(j.get(ids[i].toString()), sb);
+            Enumeration e = j.keys();
+            while(e.hasMoreElements()) {
+                Object key = e.nextElement();
+                sb.append("                <member><name>" + key + "</name>\n");
+                appendObject(j.get(key), sb);
                 sb.append("                </member>\n");
             }
             sb.append("                </struct></value>\n");
@@ -283,9 +282,7 @@ class XMLRPC extends JS.Callable {
         }
     }
 
-    // this is synchronized in case multiple threads try to make a call on the same object... in the future, change this
-    // behavior to use pipelining.
-    public synchronized Object call2(JS.Array args) throws JS.Exn, IOException {
+    public Object call_(JSArray args) throws JS.Exn, IOException {
         if (Log.verbose) Log.log(this, "call to " + url + " : " + methodname);
 
         if (tracker == null) tracker = new Hash();
@@ -294,7 +291,7 @@ class XMLRPC extends JS.Callable {
         if (objects == null) objects = new Vec();
         else objects.setSize(0);
 
-        String content = send(args, http);
+        final String content = send(args, http);
         if (Log.verbose) {
             String s;
             BufferedReader br2 = new BufferedReader(new StringReader(content));
@@ -320,13 +317,13 @@ class XMLRPC extends JS.Callable {
                             return ret;
                         }
                     });
-            return recieve(br);
+            return null;
         } finally {
             is.close();
         }
     }
 
-    protected String send(JS.Array args, HTTP http) throws JS.Exn, IOException {
+    protected String send(JSArray args, HTTP http) throws JS.Exn, IOException {
         StringBuffer content = new StringBuffer();
         content.append("\r\n");
         content.append("<?xml version=\"1.0\"?>\n");
@@ -359,19 +356,19 @@ class XMLRPC extends JS.Callable {
         return objects.elementAt(0);
     }
 
-    public final Object call(final JS.Array args) throws JS.Exn {
-        final JS.Context cx = JS.Context.current();
-        Scheduler.add(new Scheduler.Task() { public void perform() {
-            Object ret;
-            try {
-                ret = call2(args);
-            } catch (IOException se) {
-                if (Log.on) Log.log(this, se);
-                throw new JS.Exn("socket exception: " + se);
-            }
-            cx.resume(ret);
-        } });
-        return JS.Context.pause;
+    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;
     }
 
     /** When you get a property from an XMLRPC, it just returns another XMLRPC with the property name tacked onto methodname. */
index 8b0fef7..99bd691 100644 (file)
@@ -11,119 +11,129 @@ 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 JS.Obj {
+public final class XWT extends JSCallable {
 
     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 {
+        String key;
+        Sub(String key) { this.key = key; }
+        public void put(Object key, Object val) { XWT.this.put(this.key + "." + key, val); }
+        public Object get(Object key) { return XWT.this.get(this.key + "." + key); }
+        public Object call(Object method, JSArray args) { return XWT.call(method == null ? key : this.key + "." + method, args); }
+    }
+
     public Object get(Object name) {
         //#switch(name)
         case "math": return xwtMath;
         case "string": return xwtString;
-        case "date": return new org.xwt.js.Date();
+        case "date": return new JSDate();
         case "origin": return Main.origin;
-        case "box": return new Box();
+        case "box": return new BoxTree();
+        case "ui": return sub("ui");
+        case "ui.key": return sub("ui.key");
         case "ui.key.alt": return Surface.alt ? Boolean.TRUE : Boolean.FALSE;
         case "ui.key.control": return Surface.control ? Boolean.TRUE : Boolean.FALSE;
         case "ui.key.shift": return Surface.shift ? Boolean.TRUE : Boolean.FALSE;
         case "ui.clipboard": return Platform.getClipBoard();
         case "ui.maxdim": return new Integer(Short.MAX_VALUE);
-        case "ui.key.alt.name": return Platform.altKeyName();
+        case "ui.key.name": return sub("ui.key.name");
+        case "ui.key.name.alt": return Platform.altKeyName();
+        case "ui.screen": return sub("ui.screen");
         case "ui.screen.width": return new Integer(Platform.getScreenWidth());
         case "ui.screen.height": return new Integer(Platform.getScreenHeight());
         case "fs.home": return System.getProperty("user.home");
         case "fs.temp": return System.getProperty("java.io.tempdir");
+        case "ui.mouse": return sub("ui.mouse");
         case "ui.mouse.button":
             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);
+        case "undocumented": return sub("undocumented");
+        case "undocumented.internal": return sub("undocumented.internal");
         //#end
         return rr.get(name);
     }
 
-    public Object put(Object name, final Object value) {
+    public void put(Object name, final Object value) {
         //#switch(name)
         case "thread":
-            Scheduler.add(new Scheduler.Task() { public void perform() { new JS.Context((Function)value, null).resume(); } });
-            return null;
-        case "ui.clipboard": Platform.setClipBoard((String)value); return null;
-        case "ui.frame": Platform.createSurface((Box)value, true, true); return null;
-        case "ui.window": Platform.createSurface((Box)value, false, true); return null;
+            Scheduler.add(new Scheduler.Task() { public void perform() { JSContext.newJSContext((JSFunction)value, null); } });
+        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);
         case "undocumented.internal.proxyAuthorization":
             HTTP.Proxy.Authorization.authorization = value.toString();
             HTTP.Proxy.Authorization.waitingForUser.release();
-            return null;
         //#end
-        return super.put(name, value);
     }
 
-    public Object callMethod(Object name, boolean checkOnly, JS.Array args) throws JS.Exn {
+    public Object call(Object name, JSArray args) throws JS.Exn {
         Object a = args.elementAt(0);
         Object b = args.elementAt(1);
         Object c = args.elementAt(2);
-        if (name.equals("date")) return new org.xwt.js.Date(args);
-        else if (args.length() == 0 && name.equals("thread.yield")) return checkOnly ? T : sleep(0);
+        if (name.equals("date")) return new org.xwt.js.JSDate(args);
+        else if (args.length() == 0 && name.equals("thread.yield")) { sleep(0); return null; }
         else if (args.length() == 2) {
             //#switch(name)
-            case "res.watch": return checkOnly ? T : new Res.ProgressWatcher((Res)a, (Function)b);
-            case "soap": return checkOnly ? T : new SOAP((String)args.elementAt(0), "", (String)args.elementAt(1), null);
-            case "apply": if (checkOnly) return T;
-                Template.getTemplate((Res)args.elementAt(1)).apply((Box)args.elementAt(0), null, XWT.this);
+            case "res.watch": return new Res.ProgressWatcher((Res)a, (JSFunction)b);
+            case "soap": return new SOAP((String)args.elementAt(0), "", (String)args.elementAt(1), null);
+            case "apply":
+                Template.getTemplate((Res)args.elementAt(1)).apply((Box)args.elementAt(0), XWT.this);
                 return args.elementAt(0);
             //#end
         } else if (args.length() == 3 && name.equals("soap")) {
             if (name.equals("soap")) {
-                if (checkOnly) return T;
                 return new SOAP((String)args.elementAt(0), "", (String)args.elementAt(1), (String)args.elementAt(2));
             } else if (name.equals("graft")) {
-                if (checkOnly) return T;
                 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)");
-                // FIXME if (a instanceof Res) return new Res.Graft((Res)a, b, c);
+                if (a instanceof Res) return new Res.Graft((Res)a, b, c);
                 return new JS.Graft((JS)a, b, c);
             }
         } else if (args.length() == 1) {
             //#switch(name)
-            case "ui.browser": if (checkOnly) return T; Platform.newBrowserWindow((String)a); return null;
-            case "clone": return checkOnly ? T : new XWT((Res)a);
-            case "res.unzip": return checkOnly ? T : new Res.Zip((Res)a);
-            case "res.uncab": return checkOnly ? T : new Res.Cab((Res)a);
-            case "res.cache": return checkOnly ? T : new Res.CachedRes((Res)a, "resources", true);
-            case "res.fromURL": return checkOnly ? T : Res.stringToRes((String)a);
-            case "thread.sleep": return checkOnly ? T : sleep(JS.toInt(a));
-            case "log.println": if (checkOnly) return T; Log.logJS(this, a== null ? "**null**" : a.toString()); return null;
-            case "log.dump": if (checkOnly) return T; JS.recurse("", "", a); return null;
-            case "regexp": return checkOnly ? T : new Regexp(a, null);
-            case "rpc.xml": return checkOnly ? T : new XMLRPC((String)a, "");
-            case "rpc.soap": return checkOnly ? T : new SOAP((String)a, "", null, null);
-            case "crypto.rsa": /* FIXME */ break;
-            case "crypto.md5": /* FIXME */ break;
-            case "crypto.sha1": /* FIXME */ break;
-            case "crypto.rc4": /* FIXME */ break;
-            case "stream.parse.html": /* FIXME */ break;
-            case "stream.parse.xml": /* FIXME */ break;
-            case "stream.parse.utf8": /* FIXME */ break;
+            case "ui.browser": Platform.newBrowserWindow((String)a); return null;
+            case "clone": return new XWT((Res)a);
+            case "res.unzip": return new Res.Zip((Res)a);
+            case "res.uncab": return new Res.Cab((Res)a);
+            case "res.cache": return new Res.CachedRes((Res)a, "resources", true);
+            case "res.fromURL": return Res.fromString((String)a);
+            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": JS.recurse("", "", a); return null;
+            case "regexp": return new JSRegexp(a, null);
+            case "rpc.xml": return new XMLRPC((String)a, "");
+            case "rpc.soap": return new SOAP((String)a, "", null, null);
+            case "crypto.rsa": /* FEATURE */ break;
+            case "crypto.md5": /* FEATURE */ break;
+            case "crypto.sha1": /* FEATURE */ break;
+            case "crypto.rc4": /* FEATURE */ break;
+            case "stream.parse.html": /* FEATURE */ break;
+            case "stream.parse.xml": /* FEATURE */ break;
+            case "stream.parse.utf8": /* FEATURE */ break;
             //#end
         }
-        return super.callMethod(name, args, checkOnly);
+        return null;
     }
 
-    // FIXME
-    public static Object sleep(final int i) {
-        final JS.Context jsthread = JS.Context.current();
+    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() {
-                jsthread.resume();
-            } }); } }.start();
-        return JS.Context.pause;
+        new Thread() {
+            public void run() {
+                try { Thread.sleep(i); } catch (InterruptedException e) { }
+                Scheduler.add(new Scheduler.Task() { public void perform() { callback.call(null); } });
+            }
+        }.start();
     }
     
-    public static final org.xwt.js.Math xwtMath = new org.xwt.js.Math() {
-            private JS gs = new JS.GlobalScope();
+    public static final JSMath xwtMath = new JSMath() {
+            private JS gs = new JSScope.Global(null);
             public Object get(Object key) {
                 //#switch(key)
                 case "isNaN": return gs.get("isNaN");
@@ -135,8 +145,9 @@ public final class XWT extends JS.Obj {
             }
         };
 
-    public static final JS.Obj xwtString = new JS.Obj(true) {
-            private JS gs = new JS.GlobalScope();
+    public static final JSObj xwtString = new JSObj() {
+            private JS gs = new JSScope.Global(null);
+            public void put(Object key, Object val) { }
             public Object get(Object key) {
                 //#switch(key)
                 case "parseInt": return gs.get("parseInt");
@@ -152,4 +163,52 @@ public final class XWT extends JS.Obj {
                 return super.get(key);
             }
         };
+
+    private class XMLHelper extends XML {
+        Vector obStack = new Vector();
+        public XMLHelper() { super(BUFFER_SIZE); }
+        public void startElement(XML.Element c) throws XML.SchemaException {
+            JS o = new JSObj();
+            o.put("$name", c.localName, null);
+            for(int i=0; i<c.len; i++) o.put(c.keys[i], c.vals[i]);
+            o.put("$numchildren", new Integer(0), null);
+            obStack.addElement(o);
+        }
+        public void endElement(XML.Element c) throws XML.SchemaException {
+            if (obStack.size() == 1) return;
+            JS me = (JS)obStack.lastElement();
+            obStack.setSize(obStack.size() - 1);
+            JS parent = (JS)obStack.lastElement();
+            int numchildren = ((Integer)parent.get("$numchildren")).intValue();
+            parent.put("$numchildren", new Integer(numchildren + 1), null);
+            parent.put(new Integer(numchildren), me, null);
+        }
+        public void characters(char[] ch, int start, int length) throws XML.SchemaException {
+            String s = new String(ch, start, length);
+            JS parent = (JS)obStack.lastElement();
+            int numchildren = ((Integer)parent.get("$numchildren")).intValue();
+            Object lastChild = parent.get(new Integer(numchildren - 1));
+            if (lastChild instanceof String) {
+                parent.put(new Integer(numchildren - 1), lastChild + s, null);
+            } else {
+                parent.put("$numchildren", new Integer(numchildren + 1), null);
+                parent.put(new Integer(numchildren), s, null);
+            }
+        }
+        public void whitespace(char[] ch, int start, int length) {}
+        public JS doParse() throws JS.Exn {
+            try { 
+                InputStream is = getInputStream();
+                BufferedReader r = new BufferedReader(new InputStreamReader(is));
+                parse(r);
+            } catch (XML.XMLException e) {
+                throw new JS.Exn("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");
+            }
+            return obStack.size() >= 1 ? (JS)obStack.elementAt(0) : null;
+        }
+    }
 }
index 9087965..a5955a5 100644 (file)
@@ -54,16 +54,16 @@ interface ByteCodes {
     /** pop two elements; call stack[-n](stack[-n+1], stack[-n+2]...) where n is the number of args to the function */
     public static final byte CALL = -15;         
 
-    /** pop an element; push a JS.JS.Array containing the keys of the popped element */
+    /** pop an element; push a JS.JSArray containing the keys of the popped element */
     public static final byte PUSHKEYS = -16;     
 
     /** swap the top two elements on the stack */
     public static final byte SWAP = -17;         
 
-    /** execute the ForthBlock pointed to by the literal in a fresh scope with parentScope==THIS */
+    /** execute the ForthBlock pointed to by the literal in a fresh scope with parentJSScope==THIS */
     public static final byte NEWSCOPE = -18;        
 
-    /** execute the ForthBlock pointed to by the literal in a fresh scope with parentScope==THIS */
+    /** execute the ForthBlock pointed to by the literal in a fresh scope with parentJSScope==THIS */
     public static final byte OLDSCOPE = -19;
 
     /** push a copy of the top stack element */
@@ -83,13 +83,10 @@ interface ByteCodes {
     /** finish a finally block and carry out whatever instruction initiated the finally block */
     public static final byte FINALLY_DONE = -24;
     
-    /** same as CALL, except that the function is on top of the arguments instead of beneath them */
-    public static final byte CALL_REVERSED = -25; 
-
     public static final String[] bytecodeToString = new String[] {
         "", "", "LITERAL", "ARRAY", "OBJECT", "NEWFUNCTION", "DECLARE", "TOPSCOPE",
         "GET", "GET_PRESERVE", "PUT", "JT", "JF", "JMP", "POP", "CALL", "PUSHKEYS",
         "SWAP", "NEWSCOPE", "OLDSCOPE", "DUP", "LABEL", "LOOP", "CALLMETHOD",
-        "FINALLY_DONE", "CALL_REVERSED"
+        "FINALLY_DONE"
     };
 }
diff --git a/src/org/xwt/js/GlobalScopeImpl.java b/src/org/xwt/js/GlobalScopeImpl.java
deleted file mode 100644 (file)
index 5d46c83..0000000
+++ /dev/null
@@ -1,141 +0,0 @@
-package org.xwt.js;
-
-class GlobalScopeImpl extends JS.Scope {
-    private final static Double NaN = new Double(Double.NaN);
-    private final static Double POSITIVE_INFINITY = new Double(Double.POSITIVE_INFINITY);
-    
-    public GlobalScopeImpl(JS.Scope parent) {
-        super(parent);
-    }
-    public Object get(Object key) {
-        if(key.equals("NaN")) return NaN;
-        if(key.equals("Infinity")) return POSITIVE_INFINITY;
-        if(key.equals("undefined")) return null;
-        return super.get(key);
-    }
-    public Object callMethod(Object method, JS.Array args, boolean checkOnly) {
-        if(method.equals("parseInt")) {
-            if(checkOnly) return Boolean.TRUE;
-            return parseInt(args);
-        } else if(method.equals("parseFloat")) {
-            if(checkOnly) return Boolean.TRUE;
-            return parseFloat(args);
-        } else if(method.equals("isNaN")) {
-            if(checkOnly) return Boolean.TRUE;
-            return isNaN(args);
-        } else if(method.equals("isFinite")) {
-            if(checkOnly) return Boolean.TRUE;
-            return isFinite(args);
-        } else if(method.equals("decodeURI")) {
-            if(checkOnly) return Boolean.TRUE;
-            return decodeURI(args);
-        } else if(method.equals("decodeURIComponent")) {
-            if(checkOnly) return Boolean.TRUE;
-            return decodeURIComponent(args);
-        } else if(method.equals("encodeURI")) {
-            if(checkOnly) return Boolean.TRUE;
-            return encodeURI(args);
-        } else if(method.equals("encodeURIComponent")) {
-            if(checkOnly) return Boolean.TRUE;
-            return encodeURIComponent(args);
-        } else if(method.equals("escape")) {
-            if(checkOnly) return Boolean.TRUE;
-            return escape(args);
-        } else if(method.equals("unescape")) {
-            if(checkOnly) return Boolean.TRUE;
-            return unescape(args);
-        } else if(method.equals("stringFromCharCode")) {
-            if(checkOnly) return Boolean.TRUE;
-            return stringFromCharCode(args);
-        }
-        return super.callMethod(method,args,checkOnly);
-    }
-    private Object stringFromCharCode(JS.Array 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(JS.Array args) {
-        String s = args.length() > 0 ? args.elementAt(0).toString() : "";
-        int radix = args.length() > 1 ? toInt(args.elementAt(1)) : 0;
-        int start = 0;
-        int length = s.length();
-        int sign = 1;
-        long n = 0;
-        if(radix != 0 && (radix < 2 || radix > 36)) return NaN;
-        while(start < length && Character.isWhitespace(s.charAt(start))) start++;
-        if((length >= start+1) && (s.charAt(start) == '+' || s.charAt(start) == '-')) {
-            sign = s.charAt(start) == '+' ? 1 : -1;
-            start++;
-        }
-        if(radix == 0 && length >= start+1 && s.charAt(start) == '0') {
-            start++;
-            if(length >= start+1 && (s.charAt(start) == 'x' || s.charAt(start) == 'X')) {
-                start++;
-                radix = 16;
-            } else {
-                radix = 8;
-                if(length == start || Character.digit(s.charAt(start+1),8)==-1) return new Integer(0);
-            }
-        }
-        if(radix == 0) radix = 10;
-        if(length == start || Character.digit(s.charAt(start),radix) == -1) return NaN;
-        // try the fast way first
-        try {
-            String s2 = start == 0 ? s : s.substring(start);
-            return new Integer(sign*Integer.parseInt(s2,radix));
-        } catch(NumberFormatException e) { }
-        // fall through to a slower but emca-compliant method
-        for(int i=start;i<length;i++) {
-            int digit = Character.digit(s.charAt(i),radix);
-            if(digit < 0) break;
-            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);
-    }
-    private Object parseFloat(JS.Array args) {
-        String s = args.length() > 0 ? args.elementAt(0).toString() : "";
-        int start = 0;
-        int length = s.length();
-        while(start < length && Character.isWhitespace(s.charAt(0))) start++;
-        int end = length;
-        // as long as the string has no trailing garbage,this is fast, its slow with
-        // trailing garbage
-        while(start < end) {
-            try {
-                return new Double(s.substring(start,length));
-            } catch(NumberFormatException e) { }
-            end--;
-        }
-        return NaN;
-    }
-    private Object isNaN(JS.Array args) {
-        double d = args.length() > 0 ? toDouble(args.elementAt(0)) : Double.NaN;
-        return d == d ? Boolean.FALSE : Boolean.TRUE;
-    }
-    private Object isFinite(JS.Array args) {
-        double d = args.length() > 0 ? toDouble(args.elementAt(0)) : Double.NaN;
-        return (d == d && !Double.isInfinite(d)) ? Boolean.TRUE : Boolean.FALSE;
-    }
-    private Object decodeURI(JS.Array args) {
-        throw new JS.Exn("decodeURI is unimplemented");
-    }
-    private Object decodeURIComponent(JS.Array args) {
-        throw new JS.Exn("decodeURIComponent is unimplemented");
-    }
-    private Object encodeURI(JS.Array args) {
-        throw new JS.Exn("encodeURI is unimplemented");
-    }
-    private Object encodeURIComponent(JS.Array args) {
-        throw new JS.Exn("encodeURIComponent is unimplemented");
-    }
-    private Object escape(JS.Array args) {
-        throw new JS.Exn("escape is unimplemented");
-    }
-    private Object unescape(JS.Array args) {
-        throw new JS.Exn("unescape is unimplemented");
-    }
-}
index cf19716..d23794b 100644 (file)
@@ -10,7 +10,7 @@ class Internal {
     // Package Helper Methods //////////////////////////////////////////////////////////////
 
     private static Object wrapInt(int i) { return new Integer(i); }
-    static Object callMethodOnPrimitive(Object o, Object method, JS.Array args) {
+    static Object callMethodOnPrimitive(Object o, Object method, JSArray args) {
         int alength = args.length();
         String s;
         if(o instanceof Number) {
@@ -74,13 +74,13 @@ class Internal {
             return wrapInt(s.lastIndexOf(search,start));            
         }
         if(method.equals("match")) {
-            return Regexp.stringMatch(s,args);
+            return JSRegexp.stringMatch(s,args);
         }
         if(method.equals("replace")) {
-            return Regexp.stringReplace(s,args);
+            return JSRegexp.stringReplace(s,args);
         }
         if(method.equals("search")) {
-            return Regexp.stringSearch(s,args);
+            return JSRegexp.stringSearch(s,args);
         }
         if(method.equals("slice")) {
             int a = alength >= 1 ? JS.toInt(args.elementAt(0)) : 0;
@@ -95,7 +95,7 @@ class Internal {
             return s.substring(a,b);
         }
         if(method.equals("split")){
-            return Regexp.stringSplit(s,args);
+            return JSRegexp.stringSplit(s,args);
         } 
         if(method.equals("toLowerCase")) return s.toLowerCase();
         if(method.equals("toUpperCase")) return s.toUpperCase();
@@ -104,14 +104,14 @@ class Internal {
     }
     
     static Object getFromPrimitive(Object o, Object key) {
-        boolean returnCallable = false;
+        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"))
-                returnCallable = true;
+                returnJSCallable = true;
         }
-        if(!returnCallable) {
+        if(!returnJSCallable) {
             // the string stuff applies to everything
             String s = o.toString();
             
@@ -124,22 +124,22 @@ class Internal {
                 key.equals("seatch") || key.equals("slice") || key.equals("split") || key.equals("toLowerCase") ||
                 key.equals("toUpperCase") || key.equals("toString") || key.equals("substr")
             )
-                returnCallable = true;
+                returnJSCallable = true;
         }
-        if(returnCallable) {
+        if(returnJSCallable) {
             final Object target = o;
             final String method = key.toString();
-            return new JS.Callable() {
-                public Object call(JS.Array args) { return callMethodOnPrimitive(target,method,args); }
+            return new JSCallable() {
+                public Object call(JSArray args) { return callMethodOnPrimitive(target,method,args); }
             };
         }
         return null;
     }
     
-    static class CallableStub extends JS.Callable {
+    static class JSCallableStub extends JSCallable {
         private Object method;
-        private JS.Obj obj;
-        public CallableStub(JS.Obj obj, Object method) { this.obj = obj; this.method = method; }
-        public Object call(JS.Array args) { return obj.callMethod(method,args,false); }
+        private JSObj obj;
+        public JSCallableStub(JSObj obj, Object method) { this.obj = obj; this.method = method; }
+        public Object call(JSArray args) { return ((JSCallable)obj).call(method,args); }
     }
 }
index 82370a6..6d964ea 100644 (file)
@@ -9,18 +9,15 @@ 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.  The static
- *  methods, fields, and inner classes of JS define the publicly
- *  visible API for the XWT JavaScript engine; code outside this
- *  package should never depend on anything not defined in this file.
+ *  Object which can be manipulated by JavaScript code.
  */
 public abstract class JS { 
 
     // Public Helper Methods //////////////////////////////////////////////////////////////////////
 
     /** parse and compile a function */
-    public static Function parse(String sourceName, int firstLine, Reader sourceCode) throws IOException {
-        return new Function(sourceName, firstLine, sourceCode, null);
+    public static JSFunction parse(String sourceName, int firstLine, Reader sourceCode) throws IOException {
+        return new JSFunction(sourceName, firstLine, sourceCode, null);
     }
 
     /** coerce an object to a Boolean */
@@ -60,7 +57,7 @@ public abstract class JS {
         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");
     }
-    
+
     /** coerce an object to a String */
     public static String toString(Object o) {
         if(o == null) return "null";
@@ -78,64 +75,19 @@ public abstract class JS {
     // Instance Methods ////////////////////////////////////////////////////////////////////
  
     public abstract Object get(Object key) throws JS.Exn; 
-    public abstract Object put(Object key, Object val) throws JS.Exn; 
-    public abstract Object[] keys(); 
+    public void put(Object key, Object val) throws JS.Exn { throw new JS.Exn("you cannot put to this object"); }
+
+    public Enumeration keys() { throw new Error("you cannot apply for..in to a " + this.getClass().getName()); }
 
-    public Object callMethod(Object method, Array args, boolean checkOnly) throws JS.Exn {
-        if(checkOnly) return Boolean.FALSE;
-       Object o = get(method);
-       if(o instanceof JS.Callable) {
-           return ((JS.Callable)o).call(args);
-       } else if(o == null) {
-           throw new JS.Exn("Attempted to call non-existent method: " + method);
-       } else {
-           throw new JS.Exn("Attempted to call a non-method: " +method);
-       }
-    }
-    
     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"); }
+
     public String typeName() { return "object"; }
 
 
     // Inner Classes /////////////////////////////////////////////////////////////////////////
 
-    /** A sensible implementation of the abstract methods in the JS class */
-    public static class Obj extends JS {
-
-        // FIXME: move these to an interface so they're optional
-        // 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;
-
-        // FIXME: be smart here; perhaps intern
-        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 Boolean B(boolean b) { return b ? Boolean.TRUE : Boolean.FALSE; }
-
-        private Hash entries = null;
-        private boolean sealed = false;
-        public Obj() { this(false); }
-        public Obj(boolean sealed) { this.sealed = sealed; }
-        public void setSeal(boolean sealed) { this.sealed = sealed; }      ///< a sealed object cannot have its properties modified
-        public Object put(Object key, Object val) { put2(key, null, val); return null; }
-        protected void put2(Object key, Object key2, Object val) {
-            if (sealed) return;
-            if (entries == null) entries = new Hash();
-            entries.put(key, key2, val); }
-        public Object[] keys() { return entries == null ? new Object[0] : entries.keys(); }
-        public Object get(Object key) { return get(key, null); }
-        protected Object get(Object key, Object key2) {
-            if (entries == null) return null;
-            if(key2 == null && callMethod((String)key, null, true) == Boolean.TRUE)
-                return new Internal.CallableStub(this, key);
-            return entries.get(key, key2);
-        }
-    }
-
     /** An exception which can be thrown and caught by JavaScript code */
     public static class Exn extends RuntimeException { 
         private Object js = null; 
@@ -145,172 +97,54 @@ public abstract class JS {
         public Object getObject() { return js; } 
     } 
 
-    /** The publicly-visible face of JavaScript Array objects */
-    public static class Array extends ArrayImpl {
-        public Array() { }
-        public Array(int size) { super(size); }
-        public void setSize(int i) { super.setSize(i); }
-        public int length() { return super.length(); }
-        public Object elementAt(int i) { return super.elementAt(i); }
-        public void addElement(Object o) { super.addElement(o); }
-        public void setElementAt(Object o, int i) { super.setElementAt(o, i); }
-        public Object get(Object key) { return super._get(key); }
-        public Object put(Object key, Object val) { super._put(key, val); return null; }
-    }
-
-    /** Any object which becomes part of the scope chain must support this interface */ 
-    public static class Scope extends ScopeImpl { 
-        public Scope(Scope parentScope) { this(parentScope, false); }
-        public Scope(Scope parentScope, boolean sealed) { super(parentScope, sealed); }
-        public boolean has(Object key) { return super.has(key); }
-        public Object get(Object key) { return super._get(key); }
-        public Object put(Object key, Object val) { super._put(key, val); return null; }
-        public void declare(String s) { super.declare(s); }
-    } 
-
     /** the result of a graft */
-    public static class Graft extends JS {
+    public static class Graft extends JSCallable {
         private JS graftee;
         private Object replaced_key;
         private Object replaced_val;
-        public Graft(JS graftee, Object key, Object val) {
-            if (graftee instanceof Array) throw new JS.Exn("can't graft onto Arrays (yet)");
-            if (graftee instanceof Callable) throw new JS.Exn("can't graft onto Callables (yet)");
-            if (graftee instanceof Scope) throw new JS.Exn("can't graft onto Scopes (yet)");
-            this.graftee = graftee;
+        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 Object put(Object key, Object val) { graftee.put(key, val); return null; }
-        public Object callMethod(Object method, Array args, boolean checkOnly) throws JS.Exn {
-            if (!replaced_key.equals(method)) return graftee.callMethod(method, args, checkOnly);
-            if (replaced_val instanceof Callable) return checkOnly ? Boolean.TRUE : ((Callable)replaced_val).call(args);
-            if (checkOnly) return Boolean.FALSE;
-            throw new JS.Exn("attempt to call non-function");
+        public void put(Object key, Object val) { graftee.put(key, val); }
+        public void call(Object method, JSArray args) {
+            if (replaced_key.equals(method)) return ((JSCallable)replaced_val).call(null, args);
+            else if (graftee instanceof JSCallable) return 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 Object[] keys() {
-            Object[] ret = graftee.keys();
-            for(int i=0; i<ret.length; i++) if (replaced_key.equals(ret[i])) return ret;
-            Object[] ret2 = new Object[ret.length + 1];
-            System.arraycopy(ret, 0, ret2, 0, ret.length);
-            ret2[ret2.length - 1] = replaced_key;
-            return ret2;
-        }
-    }
-
-    /** anything that is callable with the () operator and wasn't compiled from JS code */
-    public static abstract class Callable extends JS.Obj {
-        public abstract Object call(JS.Array args) throws JS.Exn;
-    }
-
-    /** a scope that is populated with js objects and functions normally found in the global scope */
-    public static class GlobalScope extends GlobalScopeImpl {
-        public GlobalScope() { this(null); }
-        public GlobalScope(JS.Scope parent) { super(parent); }
-    }
-
-    public static class TailCall {
-        Function func = null;
-        JS.Array args = null;
-        public TailCall() { }
-        public TailCall set(Function func) { this.func = func; this.args = new JS.Array(); return this; }
-        public TailCall set(Function func, JS.Array args) { this.func = func; this.args = args; return this; }
-    }
-    /** encapsulates the state of a JavaScript "thread" (ie stack) */
-    public static class Context {
-
-
-        // Statics //////////////////////////////////////////////////////////////////////
-
-        /**
-         *  Return this from call/get/put in order to make the interpreter pause.  The interpreter will expect a value
-         *  (the return from the call or the get) to be pushed onto the stack when it is resumed.
-         */
-        public static Object pause = new Object();
-
-        private int getLine_() { return current().f == null ? -1 : (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 */
-        public static JS.Context current() { return (JS.Context)threadToContext.get(Thread.currentThread()); }
-        private static Hashtable threadToContext = new Hashtable();
-
-
-        // Instance members and methods //////////////////////////////////////////////////////////////////////
-
-        /** the currently-executing Function */
-        Function f = null;
-
-        /** the currently-executing scope */
-        public Scope scope = null;  // FIXME: do we really need this?  the function should contain this info
-
-        /** the object stack */
-        Vec stack = new Vec();
-
-        /** the program counter */
-        int pc = 0;
-
-        public Context(Function function, Scope scope) {
-            if (scope == null) scope = new Scope(function.parentScope);
-            stack.push(new Function.CallMarker(this));
-            stack.push(new JS.Array());
-            this.f = function;
-            this.scope = scope;
-        }
-
-        public Object resume() { return resume(null); }
-        public Object resume(Object o) {
-            Thread t = Thread.currentThread();
-            Context old = (Context)threadToContext.get(t);
-            try {
-                threadToContext.put(t, this);
-                stack.push(o);
-                return Function.eval(this);
-            } finally {
-                if (old == null) threadToContext.remove(t);
-                else threadToContext.put(t, old);
-            }
-        }
-
-    }
-
-    public static void recurse(String indent, String name, Object o) {
-        if (!name.equals("")) name += " : ";
-
-        if (o == null) {
-            Log.logJS(indent + name + "<null>");
-
-        } else if (o instanceof JS.Array) {
-            Log.logJS(indent + name + "<array>");
-            JS.Array na = (JS.Array)o;
-            for(int i=0; i<na.length(); i++)
-                recurse(indent + "  ", i + "", na.elementAt(i));
-
-        } else if (o instanceof JS) {
-            Log.logJS(indent + name + "<object>");
-            JS s = (JS)o;
-            Object[] keys = s.keys();
-            for(int i=0; i<keys.length; i++)
-                if (keys[i] != null)
-                    recurse(indent + "  ", keys[i].toString(),
-                            (keys[i] instanceof Integer) ?
-                            s.get(((Integer)keys[i])) : s.get(keys[i].toString()));
-
-        } else {
-            Log.logJS(indent + name + o);
-
+        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;
+                        }
+                    }
+                };
         }
     }
-
 } 
 
 
similarity index 73%
rename from src/org/xwt/js/ArrayImpl.java
rename to src/org/xwt/js/JSArray.java
index 84b33d1..880e64f 100644 (file)
@@ -1,15 +1,15 @@
 // Copyright 2003 Adam Megacz, see the COPYING file for licensing [GPL] 
-
 package org.xwt.js; 
+
 import org.xwt.util.*; 
 import java.io.*;
 import java.util.*;
 
-/** A JavaScript Array */
-class ArrayImpl extends JS.Obj {
+/** A JavaScript JSArray */
+public class JSArray extends JSCallable {
     private Vec vec = new Vec();
-    public ArrayImpl() { }
-    public ArrayImpl(int size) { vec.setSize(size); }
+    public JSArray() { }
+    public JSArray(int size) { vec.setSize(size); }
     private static int intVal(Object o) {
         if (o instanceof Number) {
             int intVal = ((Number)o).intValue();
@@ -22,19 +22,16 @@ class ArrayImpl extends JS.Obj {
         return Integer.parseInt(s);
     }
     
-    public Object callMethod(Object method, JS.Array args,boolean justChecking) {
+    public Object call(Object method, JSArray args) {
         if(method.equals("push")) {
-            if(justChecking) return Boolean.TRUE;
             for(int i=0;i<args.length();i++)
                 vec.push(args.elementAt(i));
             return new Integer(vec.size());
         }
         if(method.equals("pop")) {
-            if(justChecking) return Boolean.TRUE;
             return vec.pop(); // this'll return null on size()==0 
         }
         if(method.equals("shift")) {
-            if(justChecking) return Boolean.TRUE;
             if(length() > 0) {
                 Object o = vec.elementAt(0);
                 vec.removeElementAt(0);
@@ -44,24 +41,21 @@ class ArrayImpl extends JS.Obj {
             }
         }
         if(method.equals("unshift")) {
-            if(justChecking) return Boolean.TRUE;
             // FEATURE: could be optimized a bit with some help from Vec
             for(int i=0;i<args.length();i++)
                 vec.insertElementAt(args.elementAt(i),i);
             return new Integer(vec.size());
         }
-        if(method.equals("slice")) return justChecking ? Boolean.TRUE : slice(args);
-        if(method.equals("join")) return justChecking ? Boolean.TRUE : join(args);
-        if(method.equals("reverse")) return justChecking ? Boolean.TRUE : reverse(args);
-        if(method.equals("toString")) return justChecking ? Boolean.TRUE : join(",");
-        if(method.equals("sort")) return justChecking ? Boolean.TRUE : sort(args);
-        if(method.equals("splice")) return justChecking ? Boolean.TRUE : splice(args);
-        return super.callMethod(method,args,justChecking);
+        if(method.equals("slice")) return slice(args);
+        if(method.equals("join")) return join(args);
+        if(method.equals("reverse")) return reverse(args);
+        if(method.equals("toString")) return join(",");
+        if(method.equals("sort")) return sort(args);
+        if(method.equals("splice")) return splice(args);
+        throw new JS.Exn("unknown method " + method);
     }
         
-        
-    // we use _get instead of get solely to work around a GCJ bug
-    public Object _get(Object key) throws JS.Exn {
+    public Object get(Object key) throws JS.Exn {
         if (key.equals("length")) return new Long(vec.size());
                 
         int i = intVal(key);
@@ -72,8 +66,8 @@ class ArrayImpl extends JS.Obj {
             return null;
         }
     }
-    // we use _put instead of put solely to work around a GCJ bug
-    public void _put(Object key, Object val) {
+
+    public void put(Object key, Object val) {
         if (key.equals("length")) vec.setSize(toNumber(val).intValue());
         int i = intVal(key);
         if (i == Integer.MIN_VALUE) super.put(key, val);
@@ -82,23 +76,27 @@ class ArrayImpl extends JS.Obj {
             vec.setElementAt(val, i);
         }
     }
-    public Object[] keys() {
-        Object[] sup = super.keys();
-        Object[] ret = new Object[vec.size() + 1 + sup.length];
-        System.arraycopy(sup, 0, ret, vec.size(), sup.length);
-        for(int i=0; i<vec.size(); i++) ret[i] = new Integer(i);
-        ret[vec.size()] = "length";
-        return ret;
+
+    public Enumeration keys() {
+        return new Enumeration() {
+                int cur = 0;
+                public boolean hasMoreElements() { return cur >= vec.size(); }
+                public Object nextElement() {
+                    if (cur >= vec.size()) throw new NoSuchElementException();
+                    return new Integer(cur++);
+                }
+            };
     }
+
     public void setSize(int i) { vec.setSize(i); }
     public int length() { return vec.size(); }
     public Object elementAt(int i) { return vec.elementAt(i); }
     public void addElement(Object o) { vec.addElement(o); }
     public void setElementAt(Object o, int i) { vec.setElementAt(o, i); }
-    
+    public int size() { return vec.size(); }
     public String typeName() { return "array"; }
         
-    private Object join(JS.Array args) {
+    private Object join(JSArray args) {
         return join(args.length() == 0 ? "," : JS.toString(args.elementAt(0)));
     }
     
@@ -116,7 +114,7 @@ class ArrayImpl extends JS.Obj {
         return sb.toString();
     }
     
-    private Object reverse(JS.Array args) {
+    private Object reverse(JSArray args) {
         Vec oldVec = vec;
         int size = oldVec.size();
         if(size < 2) return this;
@@ -126,7 +124,7 @@ class ArrayImpl extends JS.Obj {
         return this;
     }
     
-    private Object slice(JS.Array args) {
+    private Object slice(JSArray args) {
         int length = length();
         int start = JS.toInt(args.length() < 1 ? null : args.elementAt(0));
         int end = args.length() < 2 ? length : JS.toInt(args.elementAt(1));
@@ -136,7 +134,7 @@ class ArrayImpl extends JS.Obj {
         if(end < 0) end = 0;
         if(start > length) start = length;
         if(end > length) end = length;
-        JS.Array a = new JS.Array(end-start);
+        JSArray a = new JSArray(end-start);
         for(int i=0;i<end-start;i++)
             a.setElementAt(elementAt(start+i),i);
         return a;
@@ -147,16 +145,16 @@ class ArrayImpl extends JS.Obj {
             return JS.toString(a).compareTo(JS.toString(b));
         }
     };
-    private Object sort(JS.Array args) {
+    private Object sort(JSArray args) {
         Object tmp = args.length() < 1 ? null : args.elementAt(0);
-        if(tmp instanceof JS.Callable) {
-            final JS.Array funcArgs = new JS.Array(2);
-            final JS.Callable jsFunc = (JS.Callable) tmp;
+        if(tmp instanceof JSCallable) {
+            final JSArray funcArgs = new JSArray(2);
+            final JSCallable jsFunc = (JSCallable) tmp;
             vec.sort(new Vec.CompareFunc() {
                 public int compare(Object a, Object b) {
                     funcArgs.setElementAt(a,0);
                     funcArgs.setElementAt(b,1);
-                    return JS.toInt(jsFunc.call(funcArgs));
+                    return JS.toInt(jsFunc.call(null, funcArgs));
                 }
             });
         } else {
@@ -165,7 +163,7 @@ class ArrayImpl extends JS.Obj {
         return this;
     }
     
-    private Object splice(JS.Array args) {
+    private Object splice(JSArray args) {
         int oldLength = length();
         int start = JS.toInt(args.length() < 1 ? null : args.elementAt(0));
         int deleteCount = JS.toInt(args.length() < 2 ? null : args.elementAt(1));
@@ -178,7 +176,7 @@ class ArrayImpl extends JS.Obj {
         if(deleteCount > oldLength-start) deleteCount = oldLength-start;
         int newLength = oldLength - deleteCount + newCount;
         int lengthChange = newLength - oldLength;
-        JS.Array ret = new JS.Array(deleteCount);
+        JSArray ret = new JSArray(deleteCount);
         for(int i=0;i<deleteCount;i++)
             ret.setElementAt(elementAt(start+i),i);
         if(lengthChange > 0) {
diff --git a/src/org/xwt/js/JSCallable.java b/src/org/xwt/js/JSCallable.java
new file mode 100644 (file)
index 0000000..42def05
--- /dev/null
@@ -0,0 +1,24 @@
+package org.xwt.js;
+
+/** anything that is callable with the () operator and wasn't compiled from JS code */
+public abstract class JSCallable extends JSObj {
+    public Object call0(Object method) throws JS.Exn {
+        JSArray args = new JSArray();
+        return call(method, args);
+    }
+    public Object call1(Object method, Object arg1) throws JS.Exn {
+        JSArray args = new JSArray();
+        args.addElement(arg1);
+        return call(method, args);
+    }
+    public Object call2(Object method, Object arg1, Object arg2) throws JS.Exn {
+        JSArray args = new JSArray();
+        args.addElement(arg1);
+        args.addElement(arg2);
+        return call(method, args);
+    }
+    public Object call(Object method, JSArray args) throws JS.Exn {
+        throw new JS.Exn("cannot invoke this method with " + args.size() + " arguments");
+    }
+}
+
diff --git a/src/org/xwt/js/JSContext.java b/src/org/xwt/js/JSContext.java
new file mode 100644 (file)
index 0000000..4eaf9b2
--- /dev/null
@@ -0,0 +1,71 @@
+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;
+
+    JSFunction f = null;          ///< the currently-executing JSFunction
+    JSScope scope = null;
+    Vec stack = new Vec();        ///< the object stack
+    int pc = 0;                   ///< the program counter
+    boolean pauseable;
+
+    public static void invokePauseable(JSFunction function) { new JSContext(f, true).invoke(new JSArray()); }
+
+    JSContext(JSFunction f, boolean pauseable) {
+        this.pauseable = pauseable;
+        this.f = f;
+        scope = new JSScope(function.parentJSScope);
+    }
+
+    void invoke(JSArray args) {
+        stack.push(new JSFunction.CallMarker(cx));
+        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) {
+            paused = false;
+            stack.push(o);
+            resume();
+            return null;
+        } };
+    }
+    
+    private void resume() {
+        Thread t = Thread.currentThread();
+        JSContext old = (JSContext)threadToJSContext.get(t);
+        threadToJSContext.put(t, this);
+        try {
+            JSFunction.eval(this);
+        } finally {
+            if (old == null) threadToJSContext.remove(t);
+            else threadToJSContext.put(t, old);
+        }
+    }
+}
similarity index 93%
rename from src/org/xwt/js/Date.java
rename to src/org/xwt/js/JSDate.java
index 150d738..37624b4 100644 (file)
@@ -47,9 +47,9 @@ import java.text.SimpleDateFormat;
  * See ECMA 15.9.
  * @author Mike McCabe
  */
-public class Date extends JS.Obj {
+public class JSDate extends JSCallable {
 
-    public Date() {
+    public JSDate() {
         if (thisTimeZone == null) {
             // j.u.TimeZone is synchronized, so setting class statics from it
             // should be OK.
@@ -60,253 +60,206 @@ public class Date extends JS.Obj {
 
     public String coerceToString() { return date_format(date, FORMATSPEC_FULL); }
 
-    public Object callMethod(Object name, JS.Array args_, boolean checkOnly) {
+    public Object call(Object name, JSArray args_) {
         Object[] args = new Object[args_.length()];
         for(int i=0; i<args.length; i++) args[i] = args_.elementAt(i);
 
         if (name.equals(getIdName(ConstructorId_UTC))) { 
-            if (checkOnly) return Boolean.TRUE;
-            return new Double(jsStaticFunction_UTC(args));
+            return new Double(jsStaticJSFunction_UTC(args));
             
         } else if (name.equals(getIdName(ConstructorId_parse))) { 
-            if (checkOnly) return Boolean.TRUE;
-            return new Double(jsStaticFunction_parse(args[0].toString()));
+            return new Double(jsStaticJSFunction_parse(args[0].toString()));
             
         } else if (name.equals(getIdName(Id_constructor))) {
-            if (checkOnly) return Boolean.TRUE;
-            return new Date(args_);
+            return new JSDate(args_);
             
         } else if (name.equals(getIdName(Id_toString))) {
-            if (checkOnly) return Boolean.TRUE;
             double t = date;
             return date_format(t, FORMATSPEC_FULL);
 
         } else if (name.equals(getIdName(Id_toTimeString))) {
-            if (checkOnly) return Boolean.TRUE;
             double t = date;
             return date_format(t, FORMATSPEC_TIME);
 
         } else if (name.equals(getIdName(Id_toDateString))) {
-            if (checkOnly) return Boolean.TRUE;
             double t = date;
             return date_format(t, FORMATSPEC_DATE);
 
         } else if (name.equals(getIdName(Id_toLocaleString))) {
-            if (checkOnly) return Boolean.TRUE;
             double t = date;
-            return jsFunction_toLocaleString(t);
+            return jsJSFunction_toLocaleString(t);
 
         } else if (name.equals(getIdName(Id_toLocaleTimeString))) {
-            if (checkOnly) return Boolean.TRUE;
             double t = date;
-            return jsFunction_toLocaleTimeString(t);
+            return jsJSFunction_toLocaleTimeString(t);
 
         } else if (name.equals(getIdName(Id_toLocaleDateString))) {
-            if (checkOnly) return Boolean.TRUE;
             double t = date;
-            return jsFunction_toLocaleDateString(t);
+            return jsJSFunction_toLocaleDateString(t);
 
         } else if (name.equals(getIdName(Id_toUTCString))) {
-            if (checkOnly) return Boolean.TRUE;
             double t = date;
-            return jsFunction_toUTCString(t);
+            return jsJSFunction_toUTCString(t);
 
         } else if (name.equals(getIdName(Id_valueOf))) { 
-            if (checkOnly) return Boolean.TRUE;
             return new Double(this.date);
 
         } else if (name.equals(getIdName(Id_getTime))) { 
-            if (checkOnly) return Boolean.TRUE;
             return new Double(this.date);
 
         } else if (name.equals(getIdName(Id_getYear))) {
-            if (checkOnly) return Boolean.TRUE;
             double t = date;
-            t = jsFunction_getYear(t);
+            t = jsJSFunction_getYear(t);
             return new Double(t);
 
         } else if (name.equals(getIdName(Id_getFullYear))) {
-            if (checkOnly) return Boolean.TRUE;
             double t = date;
             t = YearFromTime(LocalTime(t));
             return new Double(t);
 
         } else if (name.equals(getIdName(Id_getUTCFullYear))) {
-            if (checkOnly) return Boolean.TRUE;
             double t = date;
             t = YearFromTime(t);
             return new Double(t);
 
         } else if (name.equals(getIdName(Id_getMonth))) {
-            if (checkOnly) return Boolean.TRUE;
             double t = date;
             t = MonthFromTime(LocalTime(t));
             return new Double(t);
                 
         } else if (name.equals(getIdName(Id_getUTCMonth))) {
-            if (checkOnly) return Boolean.TRUE;
             double t = date;
             t = MonthFromTime(t);
             return new Double(t);
 
         } else if (name.equals(getIdName(Id_getDate))) {
-            if (checkOnly) return Boolean.TRUE;
             double t = date;
             t = DateFromTime(LocalTime(t));
             return new Double(t);
 
         } else if (name.equals(getIdName(Id_getUTCDate))) {
-            if (checkOnly) return Boolean.TRUE;
             double t = date;
             t = DateFromTime(t);
             return new Double(t);
 
         } else if (name.equals(getIdName(Id_getDay))) {
-            if (checkOnly) return Boolean.TRUE;
             double t = date;
             t = WeekDay(LocalTime(t));
             return new Double(t);
 
         } else if (name.equals(getIdName(Id_getUTCDay))) {
-            if (checkOnly) return Boolean.TRUE;
             double t = date;
             t = WeekDay(t);
             return new Double(t);
 
         } else if (name.equals(getIdName(Id_getHours))) {
-            if (checkOnly) return Boolean.TRUE;
             double t = date;
             t = HourFromTime(LocalTime(t));
             return new Double(t);
 
         } else if (name.equals(getIdName(Id_getUTCHours))) {
-            if (checkOnly) return Boolean.TRUE;
             double t = date;
             t = HourFromTime(t);
             return new Double(t);
 
         } else if (name.equals(getIdName(Id_getMinutes))) {
-            if (checkOnly) return Boolean.TRUE;
             double t = date;
             t = MinFromTime(LocalTime(t));
             return new Double(t);
 
         } else if (name.equals(getIdName(Id_getUTCMinutes))) {
-            if (checkOnly) return Boolean.TRUE;
             double t = date;
             t = MinFromTime(t);
             return new Double(t);
 
         } else if (name.equals(getIdName(Id_getSeconds))) {
-            if (checkOnly) return Boolean.TRUE;
             double t = date;
             t = SecFromTime(LocalTime(t));
             return new Double(t);
 
         } else if (name.equals(getIdName(Id_getUTCSeconds))) {
-            if (checkOnly) return Boolean.TRUE;
             double t = date;
             t = SecFromTime(t);
             return new Double(t);
                 
         } else if (name.equals(getIdName(Id_getMilliseconds))) {
-            if (checkOnly) return Boolean.TRUE;
             double t = date;
             t = msFromTime(LocalTime(t));
             return new Double(t);
 
         } else if (name.equals(getIdName(Id_getUTCMilliseconds))) {
-            if (checkOnly) return Boolean.TRUE;
             double t = date;
             t = msFromTime(t);
             return new Double(t);
 
         } else if (name.equals(getIdName(Id_getTimezoneOffset))) {
-            if (checkOnly) return Boolean.TRUE;
             double t = date;
-            t = jsFunction_getTimezoneOffset(t);
+            t = jsJSFunction_getTimezoneOffset(t);
             return new Double(t);
 
         } else if (name.equals(getIdName(Id_setTime))) { 
-            if (checkOnly) return Boolean.TRUE;
-            return new Double(this.jsFunction_setTime(_toNumber(args, 0)));
+            return new Double(this.jsJSFunction_setTime(_toNumber(args, 0)));
 
         } else if (name.equals(getIdName(Id_setMilliseconds))) { 
-            if (checkOnly) return Boolean.TRUE;
             return new Double(this.
                                makeTime(args, 1, true));
 
         } else if (name.equals(getIdName(Id_setUTCMilliseconds))) { 
-            if (checkOnly) return Boolean.TRUE;
             return new Double(this.
                                makeTime(args, 1, false));
 
         } else if (name.equals(getIdName(Id_setSeconds))) { 
-            if (checkOnly) return Boolean.TRUE;
             return new Double(this.
                                makeTime(args, 2, true));
 
         } else if (name.equals(getIdName(Id_setUTCSeconds))) { 
-            if (checkOnly) return Boolean.TRUE;
             return new Double(this.
                                makeTime(args, 2, false));
 
         } else if (name.equals(getIdName(Id_setMinutes))) { 
-            if (checkOnly) return Boolean.TRUE;
             return new Double(this.
                                makeTime(args, 3, true));
 
         } else if (name.equals(getIdName(Id_setUTCMinutes))) { 
-            if (checkOnly) return Boolean.TRUE;
             return new Double(this.
                                makeTime(args, 3, false));
 
         } else if (name.equals(getIdName(Id_setHours))) { 
-            if (checkOnly) return Boolean.TRUE;
             return new Double(this.
                                makeTime(args, 4, true));
 
         } else if (name.equals(getIdName(Id_setUTCHours))) { 
-            if (checkOnly) return Boolean.TRUE;
             return new Double(this.
                                makeTime(args, 4, false));
 
         } else if (name.equals(getIdName(Id_setDate))) { 
-            if (checkOnly) return Boolean.TRUE;
             return new Double(this.
                                makeDate(args, 1, true));
 
         } else if (name.equals(getIdName(Id_setUTCDate))) { 
-            if (checkOnly) return Boolean.TRUE;
             return new Double(this.
                                makeDate(args, 1, false));
 
         } else if (name.equals(getIdName(Id_setMonth))) { 
-            if (checkOnly) return Boolean.TRUE;
             return new Double(this.
                                makeDate(args, 2, true));
 
         } else if (name.equals(getIdName(Id_setUTCMonth))) { 
-            if (checkOnly) return Boolean.TRUE;
             return new Double(this.
                                makeDate(args, 2, false));
 
         } else if (name.equals(getIdName(Id_setFullYear))) { 
-            if (checkOnly) return Boolean.TRUE;
             return new Double(this.
                                makeDate(args, 3, true));
 
         } else if (name.equals(getIdName(Id_setUTCFullYear))) { 
-            if (checkOnly) return Boolean.TRUE;
             return new Double(this.
                                makeDate(args, 3, false));
 
         } else if (name.equals(getIdName(Id_setYear))) { 
-            if (checkOnly) return Boolean.TRUE;
             return new Double(this.
-                               jsFunction_setYear(_toNumber(args, 0)));
+                               jsJSFunction_setYear(_toNumber(args, 0)));
         }
-        if (checkOnly) return Boolean.FALSE;
         return null;
     }
 
@@ -667,7 +620,7 @@ public class Date extends JS.Obj {
 
 
     private static final int MAXARGS = 7;
-    private static double jsStaticFunction_UTC(Object[] args) {
+    private static double jsStaticJSFunction_UTC(Object[] args) {
         double array[] = new double[MAXARGS];
         int loop;
         double d;
@@ -935,7 +888,7 @@ public class Date extends JS.Obj {
         return msec;
     }
 
-    private static double jsStaticFunction_parse(String s) {
+    private static double jsStaticJSFunction_parse(String s) {
         return date_parseString(s);
     }
 
@@ -945,7 +898,7 @@ public class Date extends JS.Obj {
 
     private static String date_format(double t, int format) {
         if (t != t)
-            return jsFunction_NaN_date_str;
+            return jsJSFunction_NaN_date_str;
 
         StringBuffer result = new StringBuffer(60);
         double local = LocalTime(t);
@@ -1031,10 +984,10 @@ public class Date extends JS.Obj {
     private static double _toNumber(Object[] o, int index) { return JS.toDouble(o[index]); }
     private static double toDouble(double d) { return d; }
 
-    public Date(JS.Array args_) {
+    public JSDate(JSArray args_) {
         Object[] args = new Object[args_.length()];
         for(int i=0; i<args.length; i++) args[i] = args_.elementAt(i);
-        Date obj = this;
+        JSDate obj = this;
 
         // if called as a constructor with no args,
         // return a new Date with the current time.
@@ -1098,7 +1051,7 @@ public class Date extends JS.Obj {
     }
 
     /* constants for toString, toUTCString */
-    private static String jsFunction_NaN_date_str = "Invalid Date";
+    private static String jsJSFunction_NaN_date_str = "Invalid Date";
 
     private static String[] days = {
         "Sun","Mon","Tue","Wed","Thu","Fri","Sat"
@@ -1113,13 +1066,13 @@ public class Date extends JS.Obj {
                                           java.text.DateFormat formatter)
     {
         if (t != t)
-            return jsFunction_NaN_date_str;
+            return jsJSFunction_NaN_date_str;
 
         java.util.Date tempdate = new java.util.Date((long) t);
         return formatter.format(tempdate);
     }
 
-    private static String jsFunction_toLocaleString(double date) {
+    private static String jsJSFunction_toLocaleString(double date) {
         if (localeDateTimeFormatter == null)
             localeDateTimeFormatter =
                 DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG);
@@ -1127,21 +1080,21 @@ public class Date extends JS.Obj {
         return toLocale_helper(date, localeDateTimeFormatter);
     }
 
-    private static String jsFunction_toLocaleTimeString(double date) {
+    private static String jsJSFunction_toLocaleTimeString(double date) {
         if (localeTimeFormatter == null)
             localeTimeFormatter = DateFormat.getTimeInstance(DateFormat.LONG);
 
         return toLocale_helper(date, localeTimeFormatter);
     }
 
-    private static String jsFunction_toLocaleDateString(double date) {
+    private static String jsJSFunction_toLocaleDateString(double date) {
         if (localeDateFormatter == null)
             localeDateFormatter = DateFormat.getDateInstance(DateFormat.LONG);
 
         return toLocale_helper(date, localeDateFormatter);
     }
 
-    private static String jsFunction_toUTCString(double date) {
+    private static String jsJSFunction_toUTCString(double date) {
         StringBuffer result = new StringBuffer(60);
 
         String dateStr = Integer.toString(DateFromTime(date));
@@ -1187,17 +1140,17 @@ public class Date extends JS.Obj {
         return result.toString();
     }
 
-    private static double jsFunction_getYear(double date) {
+    private static double jsJSFunction_getYear(double date) {
         int result = YearFromTime(LocalTime(date));
         result -= 1900;
         return result;
     }
 
-    private static double jsFunction_getTimezoneOffset(double date) {
+    private static double jsJSFunction_getTimezoneOffset(double date) {
         return (date - LocalTime(date)) / msPerMinute;
     }
 
-    public double jsFunction_setTime(double time) {
+    public double jsJSFunction_setTime(double time) {
         this.date = TimeClip(time);
         return this.date;
     }
@@ -1278,11 +1231,11 @@ public class Date extends JS.Obj {
         return date;
     }
 
-    private double jsFunction_setHours(Object[] args) {
+    private double jsJSFunction_setHours(Object[] args) {
         return makeTime(args, 4, true);
     }
 
-    private double jsFunction_setUTCHours(Object[] args) {
+    private double jsJSFunction_setUTCHours(Object[] args) {
         return makeTime(args, 4, false);
     }
 
@@ -1355,7 +1308,7 @@ public class Date extends JS.Obj {
         return date;
     }
 
-    private double jsFunction_setYear(double year) {
+    private double jsJSFunction_setYear(double year) {
         double day, result;
         if (year != year || Double.isInfinite(year)) {
             this.date = Double.NaN;
similarity index 71%
rename from src/org/xwt/js/Function.java
rename to src/org/xwt/js/JSFunction.java
index f31d0ff..1d068e9 100644 (file)
@@ -4,10 +4,15 @@ package org.xwt.js;
 import org.xwt.util.*;
 import java.io.*;
 
-/** a JavaScript function, compiled into bytecode */
-public class Function extends JS.Obj implements ByteCodes, Tokens {
-
-    public int getNumFormalArgs() { return numFormalArgs; }
+/** A JavaScript function, compiled into bytecode */
+public class JSFunction extends JSCallable implements ByteCodes, Tokens {
+
+    /** Note: code gets run in an <i>unpauseable</i> context. */
+    public Object call(JSArray args) {
+        Context cx = new JSContext(this, false);
+        cx.invoke(args);
+        return cx.stack.pop();
+    }
 
 
     // Fields and Accessors ///////////////////////////////////////////////
@@ -19,13 +24,13 @@ public class Function extends JS.Obj implements ByteCodes, Tokens {
     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
-    JS.Scope parentScope;          ///< the default scope to use as a parent scope when executing this
+    JSScope parentJSScope;          ///< the default scope to use as a parent scope when executing this
 
 
     // Constructors ////////////////////////////////////////////////////////
 
-    private Function cloneWithNewParentScope(JS.Scope s) {
-        Function ret = new Function(sourceName, firstLine, s);
+    public JSFunction cloneWithNewParentJSScope(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
         ret.op = this.op;
@@ -36,14 +41,14 @@ public class Function extends JS.Obj implements ByteCodes, Tokens {
         return ret;
     }
 
-    private Function(String sourceName, int firstLine, JS.Scope parentScope) {
+    private JSFunction(String sourceName, int firstLine, JSScope parentJSScope) {
         this.sourceName = sourceName;
         this.firstLine = firstLine;
-        this.parentScope = parentScope;
+        this.parentJSScope = parentJSScope;
     }
 
-    protected Function(String sourceName, int firstLine, Reader sourceCode, JS.Scope parentScope) throws IOException {
-        this(sourceName, firstLine, parentScope);
+    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) {
@@ -64,9 +69,9 @@ public class Function extends JS.Obj implements ByteCodes, Tokens {
     void set(int pos, int op_, Object arg_) { op[pos] = op_; arg[pos] = arg_; }
     void set(int pos, Object arg_) { arg[pos] = arg_; }
     int pop() { size--; arg[size] = null; return op[size]; }
-    void paste(Function other) { for(int i=0; i<other.size; i++) add(other.line[i], other.op[i], other.arg[i]); }
-    Function add(int line, int op_) { return add(line, op_, null); }
-    Function add(int line, int op_, Object arg_) {
+    void paste(JSFunction other) { for(int i=0; i<other.size; i++) add(other.line[i], other.op[i], other.arg[i]); }
+    JSFunction add(int line, int op_) { return add(line, op_, null); }
+    JSFunction add(int line, int op_, Object arg_) {
         if (size == op.length - 1) {
             int[] line2 = new int[op.length * 2]; System.arraycopy(this.line, 0, line2, 0, op.length); this.line = line2;
             Object[] arg2 = new Object[op.length * 2]; System.arraycopy(arg, 0, arg2, 0, arg.length); arg = arg2;
@@ -83,14 +88,13 @@ public class Function extends JS.Obj implements ByteCodes, Tokens {
     // Invoking the Bytecode ///////////////////////////////////////////////////////
 
     /** returns false if the thread has been paused */
-    static Object eval(final Context cx) throws JS.Exn {
+    static Object eval(final JSContext cx) throws JS.Exn {
+        final initialPauseCount = cx.pausecount;
         OUTER: for(;; cx.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];
-            Object returnedFromJava = null;
-            boolean checkReturnedFromJava = false;
             if(op == FINALLY_DONE) {
                 FinallyData fd = (FinallyData) cx.stack.pop();
                 if(fd == null) continue OUTER; // NOP
@@ -99,8 +103,8 @@ public class Function extends JS.Obj implements ByteCodes, Tokens {
             }
             switch(op) {
             case LITERAL: cx.stack.push(arg); break;
-            case OBJECT: cx.stack.push(new JS.Obj()); break;
-            case ARRAY: cx.stack.push(new JS.Array(JS.toNumber(arg).intValue())); break;
+            case OBJECT: cx.stack.push(new JSObj()); 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;
@@ -109,12 +113,12 @@ public class Function extends JS.Obj implements ByteCodes, Tokens {
             case POP: cx.stack.pop(); break;
             case SWAP: { Object o1 = cx.stack.pop(); Object o2 = cx.stack.pop(); cx.stack.push(o1); cx.stack.push(o2); break; }
             case DUP: cx.stack.push(cx.stack.peek()); break;
-            case NEWSCOPE: cx.scope = new JS.Scope(cx.scope); break;
-            case OLDSCOPE: cx.scope = cx.scope.getParentScope(); 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(((Function)arg).cloneWithNewParentScope(cx.scope)); break;
+            case NEWFUNCTION: cx.stack.push(((JSFunction)arg).cloneWithNewParentJSScope(cx.scope)); break;
             case LABEL: break;
 
             case TYPEOF: {
@@ -130,10 +134,9 @@ public class Function extends JS.Obj implements ByteCodes, Tokens {
 
             case PUSHKEYS: {
                 Object o = cx.stack.peek();
-                Object[] keys = ((JS)o).keys();
-                JS.Array a = new JS.Array();
-                a.setSize(keys.length);
-                for(int j=0; j<keys.length; j++) a.setElementAt(keys[j], j);
+                Enumeration e = ((JS)o).keys();
+                JSArray a = new JSArray();
+                while(e.hasMoreElements()) a.addElement(e.nextElement());
                 cx.stack.push(a);
                 break;
             }
@@ -190,9 +193,30 @@ public class Function extends JS.Obj implements ByteCodes, Tokens {
                         cx.pc = ((TryMarker)o).finallyLoc - 1;
                         continue OUTER;
                     } else if (o instanceof CallMarker) {
+                        if (cx.scope instanceof JSTrapScope) {
+                            JSTrapScope ts = (JSTrapScope)cx.scope;
+                            if (!ts.cascadeHappened) {
+                                ts.cascadeHappened = true;
+                                JSTrap t = ts.t.next;
+                                while (t != null && t.f.numFormalArgs == 0) t = t.next;
+                                if (t == null) {
+                                    ts.trappee.put(key, val);
+                                    if (cx.pausecount > initialPauseCount) return;   // we were paused
+                                } else {
+                                    cx.stack.push(o);
+                                    JSArray args = new JSArray();
+                                    args.addElement(ts.val);
+                                    cx.stack.push(ta);
+                                    cx.f = t.f;
+                                    cx.scope = new JSTrap.JSTrapScope(cx.f.parentJSScope, ts.val);
+                                    cx.pc = -1;
+                                    break;
+                                }
+                            }
+                        }
                         cx.scope = ((CallMarker)o).scope;
                         cx.pc = ((CallMarker)o).pc;
-                        cx.f = (Function)((CallMarker)o).f;
+                        cx.f = (JSFunction)((CallMarker)o).f;
                         cx.stack.push(retval);
                         continue OUTER;
                     }
@@ -210,9 +234,30 @@ public class Function extends JS.Obj implements ByteCodes, Tokens {
                     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");
-                returnedFromJava = ((JS)target).put(key, val);
-                if (returnedFromJava != null) checkReturnedFromJava = true;
-                else cx.stack.push(val);
+                JSTrap t = null;
+                if (o instanceof JSTrap.JSTrappable) {
+                    t = ((JSTrap.JSTrappable)o).getTrap(v);
+                    while (t != null && t.f.numFormalArgs == 0) t = t.next;
+                } else if (o instanceof JSTrap.JSTrapScope && key.equals("cascade")) {
+                    JSTrap.JSTrapScope ts = (JSTrap.JSTrapScope)o;
+                    t = ts.t.next;
+                    ts.cascadeHappened = true;
+                    while (t != null && t.f.numFormalArgs == 0) t = t.next;
+                    if (t == null) o = ts.t.trappee;
+                }
+                if (t != null) {
+                    cx.stack.push(new CallMarker(cx));
+                    JSArray args = new JSArray();
+                    args.addElement(val);
+                    cx.stack.push(ta);
+                    cx.f = t.f;
+                    cx.scope = new JSTrap.JSTrapScope(cx.f.parentJSScope, val);
+                    cx.pc = -1;
+                    break;
+                }
+                ((JS)target).put(key, val);
+                if (cx.pausecount > initialPauseCount) return;   // we were paused
+                cx.stack.push(val);
                 break;
             }
 
@@ -235,53 +280,87 @@ public class Function extends JS.Obj implements ByteCodes, Tokens {
                     cx.stack.push(ret);
                     break;
                 } else if (o instanceof JS) {
-                    returnedFromJava = ((JS)o).get(v);
-                    checkReturnedFromJava = true;
+                    JSTrap t = null;
+                    if (o instanceof JSTrap.JSTrappable) {
+                        t = ((JSTrap.JSTrappable)o).getTrap(v);
+                        while (t != null && t.f.numFormalArgs != 0) t = t.next;
+                    } else if (o instanceof JSTrap.JSTrapScope && key.equals("cascade")) {
+                        t = ((JSTrap.JSTrapScope)o).t.next;
+                        while (t != null && t.f.numFormalArgs != 0) t = t.next;
+                        if (t == null) o = ((JSTrap.JSTrapScope)o).t.trappee;
+                    }
+                    if (t != null) {
+                        cx.stack.push(new CallMarker(cx));
+                        JSArray args = new JSArray();
+                        cx.stack.push(ta);
+                        cx.f = t.f;
+                        cx.scope = new JSTrap.JSTrapScope(cx.f.parentJSScope, null);
+                        ((JSTrap.JSTrapScope)cx.scope).cascadeHappened = true;
+                        cx.pc = -1;
+                        break;
+                    }
+                    ret = ((JS)o).get(v);
+                    if (cx.pausecount > initialPauseCount) return;   // we were paused
+                    cx.stack.push(ret);
                     break;
                 }
                 throw je("tried to get property " + v + " from a " + o.getClass().getName());
             }
             
-            case CALL: case CALLMETHOD: case CALL_REVERSED: {
-                JS.Array arguments = new JS.Array();
+            case CALL: case CALLMETHOD: {
                 int numArgs = JS.toNumber(arg).intValue();
-                arguments.setSize(numArgs);
-                Object o = null;
-                if (op == CALL_REVERSED) o = cx.stack.pop();
-                for(int j=numArgs - 1; j >= 0; j--) arguments.setElementAt(cx.stack.pop(), j);
-                if (op != CALL_REVERSED) o = cx.stack.pop();
+                Object o = cx.stack.pop();
                 if(o == null) throw je("attempted to call null");
                 Object ret;
-
+                Object method = null;
                 if(op == CALLMETHOD) {
-                    Object method = o;
+                    method = o;
+                    if (method == null) throw new JS.Exn("cannot call the null method");
                     o = cx.stack.pop();
                     if(o instanceof String || o instanceof Number || o instanceof Boolean) {
+                        JSArray arguments = new JSArray();
+                        for(int j=numArgs - 1; j >= 0; j--) arguments.setElementAt(cx.stack.pop(), j);
                         ret = Internal.callMethodOnPrimitive(o,method,arguments);
                         cx.stack.push(ret);
                         cx.pc += 2;  // skip the GET and CALL
-                    } else if (o instanceof JS && ((JS)o).callMethod(method, arguments, true) == Boolean.TRUE) {
-                        returnedFromJava = ((JS)o).callMethod(method, arguments, false);
-                        checkReturnedFromJava = true;
-                        cx.pc += 2;  // skip the GET and CALL
+                        break;
+                    } else if (o instanceof JSCallable) {
+                        // fall through
                     } else {
                         // put the args back on the stack and let the GET followed by CALL happen
-                        for(int j=0; j<numArgs; j++) cx.stack.push(arguments.elementAt(j));
                         cx.stack.push(o);
                         cx.stack.push(method);
+                        break;
                     }
 
-                } else if (o instanceof Function) {
+                } else if (o 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 = (Function)o;
-                    cx.scope = new Scope(cx.f.parentScope);
+                    cx.f = (JSFunction)o;
+                    cx.scope = new JSScope(cx.f.parentJSScope);
                     cx.pc = -1;
-                    
-                } else {
-                    returnedFromJava = ((JS.Callable)o).call(arguments);
-                    checkReturnedFromJava = true;
+                    break;
+                }
+
+                JSCallable c = ((JSCallable)o);
+                switch(numArgs) {
+                    case 0: ret = c.call0(method); break;
+                    case 1: ret = c.call1(method, cx.stack.pop()); break;
+                    case 2: ret = c.call2(method, cx.stack.pop(), cx.stack.pop()); break;
+                    default: {
+                        JSArray arguments = new JSArray();
+                        for(int j=numArgs - 1; j >= 0; j--) arguments.setElementAt(cx.stack.pop(), j);
+                        ret = c.call(method, arguments);
+                        if (cx.pausecount > initialPauseCount) return;   // we were paused
+                        break;
+                    }
                 }
+                cx.stack.push(ret);
+                if (method != null) cx.pc += 2;  // skip the GET and CALL if this was a GETCALL
+
                 break;
             }
 
@@ -291,28 +370,18 @@ public class Function extends JS.Obj implements ByteCodes, Tokens {
                 throw new JS.Exn(o);
             }
 
-            case INC: case DEC: {
-                boolean isPrefix = JS.toBoolean(arg);
-                Object key = cx.stack.pop();
-                JS obj = (JS)cx.stack.pop();
-                Number num = JS.toNumber(obj.get(key));
-                Number val = new Double(op == INC ? num.doubleValue() + 1.0 : num.doubleValue() - 1.0);
-                obj.put(key, val);
-                cx.stack.push(isPrefix ? val : num);
-                break;
-            }
-            
             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();
-                if (val instanceof Function && obj instanceof JS.Scope) {
-                    JS.Scope parent = (JS.Scope)obj;
-                    while(parent.getParentScope() != null) parent = parent.getParentScope();
-                    if (parent instanceof org.xwt.Box) {
-                        org.xwt.Box b = (org.xwt.Box)parent;
-                        if (op == ASSIGN_ADD) b.addTrap(key, val); else b.delTrap(key, val);
+                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);
                         // skip over the "normal" implementation of +=/-=
                         cx.pc += ((Integer)arg).intValue() - 1;
                         break;
@@ -418,26 +487,6 @@ public class Function extends JS.Obj implements ByteCodes, Tokens {
                 } }
             }
 
-            // handle special directions returned from Java callouts
-            // ideally we would do this with exceptions, but they're *very* slow in gcj
-            if (checkReturnedFromJava) {
-                checkReturnedFromJava = false;
-                if (returnedFromJava == Context.pause) {
-                    cx.pc++;
-                    return Context.pause;
-                } else if (returnedFromJava instanceof TailCall) {
-                    cx.stack.push(new CallMarker(cx));
-                    cx.stack.push(((JS.TailCall)returnedFromJava).args);
-                    cx.f = ((JS.TailCall)returnedFromJava).func;
-                    cx.scope = new Scope(cx.f.parentScope);
-                    cx.pc = -1;
-                } else {
-                    cx.stack.push(returnedFromJava);
-                }
-                continue OUTER;
-            }
-
-
         } catch(JS.Exn e) {
             while(cx.stack.size() > 0) {
                 Object o = cx.stack.pop();
@@ -474,7 +523,7 @@ public class Function extends JS.Obj implements ByteCodes, Tokens {
 
     // Debugging //////////////////////////////////////////////////////////////////////
 
-    public String toString() { return "Function [" + sourceName + ":" + firstLine + "]"; }
+    public String toString() { return "JSFunction [" + sourceName + ":" + firstLine + "]"; }
     public String dump() {
         StringBuffer sb = new StringBuffer(1024);
         sb.append("\n" + sourceName + ": " + firstLine + "\n");
@@ -504,10 +553,10 @@ public class Function extends JS.Obj implements ByteCodes, Tokens {
 
     static class EvaluatorException extends RuntimeException { public EvaluatorException(String s) { super(s); } }
     static EvaluatorException ee(String s) {
-        throw new EvaluatorException(Context.getSourceName() + ":" + Context.getLine() + " " + s);
+        throw new EvaluatorException(JSContext.getSourceName() + ":" + JSContext.getLine() + " " + s);
     }
     static JS.Exn je(String s) {
-        throw new JS.Exn(Context.getSourceName() + ":" + Context.getLine() + " " + s);
+        throw new JS.Exn(JSContext.getSourceName() + ":" + JSContext.getLine() + " " + s);
     }
 
 
@@ -515,9 +564,9 @@ public class Function extends JS.Obj implements ByteCodes, Tokens {
 
     public static class CallMarker {
         int pc;
-        Scope scope;
-        Function f;
-        public CallMarker(Context cx) { pc = cx.pc + 1; scope = cx.scope; f = cx.f; }
+        JSScope scope;
+        JSFunction f;
+        public CallMarker(JSContext cx) { pc = cx.pc + 1; scope = cx.scope; f = cx.f; }
     }
     
     public static class CatchMarker { public CatchMarker() { } }
@@ -526,8 +575,8 @@ public class Function extends JS.Obj implements ByteCodes, Tokens {
     public static class LoopMarker {
         public int location;
         public String label;
-        public JS.Scope scope;
-        public LoopMarker(int location, String label, JS.Scope scope) {
+        public JSScope scope;
+        public LoopMarker(int location, String label, JSScope scope) {
             this.location = location;
             this.label = label;
             this.scope = scope;
@@ -536,8 +585,8 @@ public class Function extends JS.Obj implements ByteCodes, Tokens {
     public static class TryMarker {
         public int catchLoc;
         public int finallyLoc;
-        public JS.Scope scope;
-        public TryMarker(int catchLoc, int finallyLoc, JS.Scope scope) {
+        public JSScope scope;
+        public TryMarker(int catchLoc, int finallyLoc, JSScope scope) {
             this.catchLoc = catchLoc;
             this.finallyLoc = finallyLoc;
             this.scope = scope;
diff --git a/src/org/xwt/js/JSMath.java b/src/org/xwt/js/JSMath.java
new file mode 100644 (file)
index 0000000..5a2ea4b
--- /dev/null
@@ -0,0 +1,66 @@
+// Copyright 2003 Adam Megacz, see the COPYING file for licensing [GPL] 
+
+package org.xwt.js; 
+import org.xwt.util.*; 
+import java.io.*;
+import java.util.*;
+
+/** The JavaScript Math object */
+public class JSMath extends JSObj {
+
+    public static JSMath singleton = new JSMath();
+
+    public Object call(Object method, JSArray args) {
+        if (method == null) throw new JS.Exn("you cannot call this object");
+        //#switch(method)
+        case "ceil": return new Long((long)java.lang.Math.ceil(JS.toNumber(args.elementAt(0)).doubleValue()));
+        case "floor": return new Long((long)java.lang.Math.floor(JS.toNumber(args.elementAt(0)).doubleValue()));
+        case "round": return new Long((long)java.lang.Math.round(JS.toNumber(args.elementAt(0)).doubleValue()));
+        case "min": return new Double(java.lang.Math.min(JS.toNumber(args.elementAt(0)).doubleValue(),
+                                                         JS.toNumber(args.elementAt(1)).doubleValue()));
+        case "max": return new Double(java.lang.Math.max(JS.toNumber(args.elementAt(0)).doubleValue(),
+                                                         JS.toNumber(args.elementAt(1)).doubleValue()));
+        case "pow": return new Double(java.lang.Math.pow(JS.toNumber(args.elementAt(0)).doubleValue(),
+                                                         JS.toNumber(args.elementAt(1)).doubleValue()));
+        case "atan2": return new Double(java.lang.Math.atan2(JS.toNumber(args.elementAt(0)).doubleValue(),
+                                                             JS.toNumber(args.elementAt(1)).doubleValue()));
+        case "abs": return new Double(java.lang.Math.abs(JS.toNumber(args.elementAt(0)).doubleValue()));
+        case "sin": return new Double(java.lang.Math.sin(JS.toNumber(args.elementAt(0)).doubleValue()));
+        case "cos": return new Double(java.lang.Math.cos(JS.toNumber(args.elementAt(0)).doubleValue()));
+        case "tan": return new Double(java.lang.Math.tan(JS.toNumber(args.elementAt(0)).doubleValue()));
+        case "asin": return new Double(java.lang.Math.asin(JS.toNumber(args.elementAt(0)).doubleValue()));
+        case "acos": return new Double(java.lang.Math.acos(JS.toNumber(args.elementAt(0)).doubleValue()));
+        case "atan": return new Double(java.lang.Math.atan(JS.toNumber(args.elementAt(0)).doubleValue()));
+        case "sqrt": return new Double(java.lang.Math.sqrt(JS.toNumber(args.elementAt(0)).doubleValue()));
+        case "exp": return new Double(java.lang.Math.exp(JS.toNumber(args.elementAt(0)).doubleValue()));
+        case "log": return new Double(java.lang.Math.log(JS.toNumber(args.elementAt(0)).doubleValue()));
+        case "random": return new Double(java.lang.Math.random());
+        //#end
+        return null;
+    }
+
+    private static final Double E       = new Double(java.lang.Math.E);
+    private static final Double PI      = new Double(java.lang.Math.PI);
+    private static final Double LN10    = new Double(java.lang.Math.log(10));
+    private static final Double LN2     = new Double(java.lang.Math.log(2));
+    private static final Double LOG10E  = new Double(1/java.lang.Math.log(10));
+    private static final Double LOG2E   = new Double(1/java.lang.Math.log(2));
+    private static final Double SQRT1_2 = new Double(1/java.lang.Math.sqrt(2));
+    private static final Double SQRT2   = new Double(java.lang.Math.sqrt(2));
+
+    public void put(Object key, Object val) { return; }
+
+    public Object get(Object key, Object val) {
+        //#switch(key)
+        case "E": return E;
+        case "LN10": return LN10;
+        case "LN2": return LN2;
+        case "LOG10E": return LOG10E;
+        case "LOG2E": return LOG2E;
+        case "PI": return PI;
+        case "SQRT1_2": return SQRT1_2;
+        case "SQRT2": return SQRT2;
+        //#end
+        return null;
+    }
+}
diff --git a/src/org/xwt/js/JSObj.java b/src/org/xwt/js/JSObj.java
new file mode 100644 (file)
index 0000000..e096521
--- /dev/null
@@ -0,0 +1,33 @@
+package org.xwt.js;
+import java.util.*;
+import org.xwt.util.*;
+
+// FEATURE: static slots for four objects?
+/** A sensible implementation of the abstract methods in the JS class */
+public class JSObj extends JS {
+    
+    // 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
+    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 Boolean B(boolean b) { return b ? Boolean.TRUE : Boolean.FALSE; }
+    
+    private static Enumeration emptyEnumeration = new Enumeration() {
+            public boolean hasMoreElements() { return false; }
+            public Object nextElement() { throw new NoSuchElementException(); }
+        };
+    
+    private Hash entries = null;
+    public JSObj() { }
+    public Enumeration keys() { return entries == null ? emptyEnumeration : entries.keys(); }
+    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); }
+}
diff --git a/src/org/xwt/js/JSRegexp.java b/src/org/xwt/js/JSRegexp.java
new file mode 100644 (file)
index 0000000..b0359ba
--- /dev/null
@@ -0,0 +1,361 @@
+package org.xwt.js;
+
+import gnu.regexp.*;
+
+public class JSRegexp extends JSCallable {
+    private boolean global;
+    private RE re;
+    private int lastIndex;
+    
+    public JSRegexp(Object arg0, Object arg1) throws JS.Exn {
+        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 sFlags = null;
+            int flags = 0;
+            if(arg1 != null) sFlags = (String)arg1;
+            if(sFlags == null) sFlags = "";
+            for(int i=0;i<sFlags.length();i++) {
+                switch(sFlags.charAt(i)) {
+                    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) + "\"");
+                }
+            }
+            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));
+        }
+    }
+
+    public Object call(Object method, JSArray args) throws JS.Exn {
+        if (method.equals("exec")) {
+            return exec(args);
+        } else if (method.equals("test")) {
+            return test(args);
+        } else if (method.equals("toString")) {
+            return toString();
+        }
+        return null;
+    }
+    
+    // gcj bug...
+    public Object get(Object key) { return _get(key); }
+    public void put(Object key,Object value) { _put(key,value); }
+
+    public Object _get(Object key) {
+        if(key.equals("lastIndex")) return new Integer(lastIndex);
+        return super.get(key);
+    }
+    
+    public void _put(Object key, Object value) {
+        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) {
+        JSObj ret = new JSObj();
+        ret.put("index",new Integer(match.getStartIndex()));
+        ret.put("input",s);
+        int n = re.getNumSubs();
+        ret.put("length",new Integer(n+1));
+        ret.put("0",match.toString());
+        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(JSArray args)  throws JS.Exn {
+        if(args.length() < 1) throw new JS.Exn("Not enough args to match");
+        String s = args.elementAt(0).toString();
+        
+        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('/');
+        sb.append(_get("source"));
+        sb.append('/');
+        if(global) sb.append('g');
+        if(Boolean.TRUE.equals(_get("ignoreCase"))) sb.append('i');
+        if(Boolean.TRUE.equals(_get("multiline"))) sb.append('m');
+        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);
+        String s = o.toString();
+        RE re;
+        JSRegexp regexp = null;
+        if(arg0 instanceof JSRegexp) {
+            regexp = (JSRegexp) arg0;
+            re = regexp.re;
+        } else {
+            re = newRE(arg0.toString(),0);
+        }
+        
+        if(regexp == null) {
+            REMatch match = re.getMatch(s);
+            return matchToExecResult(match,re,s);
+        }
+        if(!regexp.global)
+            return regexp.exec(s);
+        
+        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();
+        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);
+        String s = o.toString();
+        RE re;
+        if(arg0 instanceof JSRegexp)
+            re = ((JSRegexp)arg0).re;
+        else
+            re = newRE(arg0.toString(),0);
+        REMatch match = re.getMatch(s);
+        if(match == null) return new Integer(-1);
+        return new Integer(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);
+        String s = o.toString();
+        RE re;
+        JSCallable replaceFunc = null;
+        String replaceString = null;
+        JSRegexp regexp = null;
+        if(arg0 instanceof JSRegexp) {
+            regexp = (JSRegexp) arg0;
+            re = regexp.re;
+        } else {
+            re = newRE(arg0.toString(),0);
+        }
+        if(arg1 instanceof JSCallable)
+            replaceFunc = (JSCallable) arg1;
+        else
+            replaceString = arg1.toString();
+        REMatch[] matches;
+        if(regexp != null && regexp.global) {
+            matches = re.getAllMatches(s);
+            if(regexp != null) {
+                if(matches.length > 0)
+                    regexp.lastIndex = matches[matches.length-1].getEndIndex();
+                else
+                    regexp.lastIndex = s.length();
+            }
+        } else {
+            REMatch match = re.getMatch(s);
+            if(match != null)
+                matches = new REMatch[]{ match };
+            else
+                matches = new REMatch[0];
+        }
+        
+        StringBuffer sb = new StringBuffer(s.length());
+        int pos = 0;
+        char[] sa = s.toCharArray();
+        for(int i=0;i<matches.length;i++) {
+            REMatch match = matches[i];
+            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));
+                }
+                a.addElement(new Integer(match.getStartIndex()));
+                a.addElement(s);
+                Object ret = replaceFunc.call(a, null);
+                sb.append(ret.toString());
+                */
+            } else {
+                sb.append(mySubstitute(match,replaceString,s));
+            }
+        }
+        int end = matches.length == 0 ? 0 : matches[matches.length-1].getEndIndex();
+        sb.append(sa,end,sa.length-end);
+        return sb.toString();
+    }
+    
+    private static String mySubstitute(REMatch match, String s, String source) {
+        StringBuffer sb = new StringBuffer();
+        int i,n;
+        char c,c2;
+        for(i=0;i<s.length()-1;i++) {
+           c = s.charAt(i);
+            if(c != '$') {
+                sb.append(c);
+                continue;
+            }
+            i++;
+            c = s.charAt(i);
+            switch(c) {
+                case '0': case '1': case '2': case '3': case '4':
+                case '5': case '6': case '7': case '8': case '9':
+                    if(i < s.length()-1 && (c2 = s.charAt(i+1)) >= '0' && c2 <= '9') {
+                        n = (c - '0') * 10 + (c2 - '0');
+                        i++;
+                    } else {
+                        n = c - '0';
+                    }
+                    if(n > 0)
+                        sb.append(match.toString(n));
+                    break;
+                case '$':
+                    sb.append('$'); break;
+                case '&':
+                    sb.append(match.toString()); break;
+                case '`':
+                    sb.append(source.substring(0,match.getStartIndex())); break;
+                case '\'':
+                    sb.append(source.substring(match.getEndIndex())); break;
+                default:
+                    sb.append('$');
+                    sb.append(c);
+            }
+        }
+        if(i < s.length()) sb.append(s.charAt(i));
+        return sb.toString();
+    }
+                    
+    
+    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));
+        if(limit < 0) limit = Integer.MAX_VALUE;
+        if(limit == 0) return new JSArray();
+        
+        RE re = null;
+        JSRegexp regexp = null;
+        String sep = null;
+        JSArray ret = new JSArray();
+        int p = 0;
+        
+        if(arg0 instanceof JSRegexp) {
+            regexp = (JSRegexp) arg0;
+            re = regexp.re;
+        } else {
+            sep = arg0.toString();
+        }
+        
+        // special case this for speed. additionally, the code below doesn't properly handle
+        // zero length strings
+        if(sep != null && sep.length()==0) {
+            int len = s.length();
+            for(int i=0;i<len;i++)
+                ret.addElement(s.substring(i,i+1));
+            return ret;
+        }
+        
+        OUTER: while(p < s.length()) {
+            if(re != null) {
+                REMatch m = re.getMatch(s,p);
+                if(m == null) break OUTER;
+                boolean zeroLength = m.getStartIndex() == m.getEndIndex();
+                ret.addElement(s.substring(p,zeroLength ? m.getStartIndex()+1 : m.getStartIndex()));
+                p = zeroLength ? p + 1 : m.getEndIndex();
+                if(!zeroLength) {
+                    for(int i=1;i<=re.getNumSubs();i++) {
+                        ret.addElement(m.toString(i));
+                        if(ret.length() == limit) break OUTER;
+                    }
+                }
+            } else {
+                int x = s.indexOf(sep,p);
+                if(x == -1) break OUTER;
+                ret.addElement(s.substring(p,x));
+                p = x + sep.length();
+            }
+            if(ret.length() == limit) break;
+        }
+        if(p < s.length() && ret.length() != limit)
+            ret.addElement(s.substring(p));
+        return ret;
+    }
+    
+    public static RE newRE(String pattern, int flags) throws JS.Exn {
+        try {
+            return new RE(pattern,flags,RESyntax.RE_SYNTAX_PERL5);
+        } catch(REException e) {
+            throw new JS.Exn(e.toString());
+        }
+    }
+    
+    private static Boolean wrapBool(boolean b) {
+        return b ? Boolean.TRUE : Boolean.FALSE;
+    }
+    
+    private static Boolean wrapBool(int n) {
+        return wrapBool(n != 0);
+    }
+    
+    public String typeName() { return "regexp"; }
+}
diff --git a/src/org/xwt/js/JSScope.java b/src/org/xwt/js/JSScope.java
new file mode 100644 (file)
index 0000000..bdf8233
--- /dev/null
@@ -0,0 +1,132 @@
+// Copyright 2003 Adam Megacz, see the COPYING file for licensing [GPL] 
+package org.xwt.js; 
+
+import org.xwt.util.*; 
+import java.io.*;
+import java.util.*;
+
+/** Implementation of a JavaScript JSScope */
+public class JSScope extends JSCallable { 
+    private JSScope parentJSScope;
+    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 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 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);
+    }
+
+    public void put(Object key, Object val) {
+        if (parentJSScope != null && !has(key)) parentJSScope.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 Object get(Object key) {
+            if(key.equals("NaN")) return NaN;
+            if(key.equals("Infinity")) return POSITIVE_INFINITY;
+            if(key.equals("undefined")) return null;
+            return super.get(key);
+        }
+
+        public Object call1(Object method, Object arg0) {
+            //#switch(method)
+            case "parseInt": return parseInt(arg0, 0);
+            case "isNaN": { double d = toDouble(arg0); return d == d ? F : T; }
+            case "isFinite": { double d = toDouble(arg0); return (d == d && !Double.isFinite(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");
+            case "stringFromCharCode": return stringFromCharCode(arg0);
+            //#end
+            return null;
+        }
+
+        public Object call2(Object method, Object arg1, Object arg2) {
+            if (method.equals("parseInt")) return parseInt(arg1, arg2);
+            return null;
+        }
+
+        private Object stringFromCharCode(Object arg) {
+            char buf[] = new char[args.length()];
+            for(int i=0;i<args.length();i++) buf[i] = (char)(JS.toInt(arg) & 0xffff);
+            return new String(buf);
+        }
+
+        private Object parseInt(Object arg, int radix) {
+            String s = (String)arg;
+            int start = 0;
+            int length = s.length();
+            int sign = 1;
+            long n = 0;
+            if(radix != 0 && (radix < 2 || radix > 36)) return NaN;
+            while(start < length && Character.isWhitespace(s.charAt(start))) start++;
+            if((length >= start+1) && (s.charAt(start) == '+' || s.charAt(start) == '-')) {
+                sign = s.charAt(start) == '+' ? 1 : -1;
+                start++;
+            }
+            if(radix == 0 && length >= start+1 && s.charAt(start) == '0') {
+                start++;
+                if(length >= start+1 && (s.charAt(start) == 'x' || s.charAt(start) == 'X')) {
+                    start++;
+                    radix = 16;
+                } else {
+                    radix = 8;
+                    if(length == start || Character.digit(s.charAt(start+1),8)==-1) return new Integer(0);
+                }
+            }
+            if(radix == 0) radix = 10;
+            if(length == start || Character.digit(s.charAt(start),radix) == -1) return NaN;
+            // try the fast way first
+            try {
+                String s2 = start == 0 ? s : s.substring(start);
+                return new Integer(sign*Integer.parseInt(s2,radix));
+            } catch(NumberFormatException e) { }
+            // fall through to a slower but emca-compliant method
+            for(int i=start;i<length;i++) {
+                int digit = Character.digit(s.charAt(i),radix);
+                if(digit < 0) break;
+                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);
+        }
+
+        private Object parseFloat(Object arg) {
+            String s = (String)arg;
+            int start = 0;
+            int length = s.length();
+            while(start < length && Character.isWhitespace(s.charAt(0))) start++;
+            int end = length;
+            // as long as the string has no trailing garbage,this is fast, its slow with
+            // trailing garbage
+            while(start < end) {
+                try {
+                    return new Double(s.substring(start,length));
+                } catch(NumberFormatException e) { }
+                end--;
+            }
+            return NaN;
+        }
+    }
+}
+
diff --git a/src/org/xwt/js/JSTrap.java b/src/org/xwt/js/JSTrap.java
new file mode 100644 (file)
index 0000000..baa02ef
--- /dev/null
@@ -0,0 +1,59 @@
+// 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 {
+
+    private JSTrappable trapee = null;   ///< the box on which this trap was placed
+    JSFunction f = null;                 ///< the function for this trap
+    private JSTrap next = null;          ///< the next trap down the trap stack
+    private 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);
+
+        /** puts to this value using an unpauseable context, triggering any traps */
+        public abstract void putAndTriggerTraps(Object key, Object value);
+    }
+
+    // FIXME: cascadeHappened gets set, but autocascade does not happen
+    static class JSTrapScope extends JSScope {
+        JSTrap t;
+        Object val = null;
+        boolean cascadeHappened = false;
+        public JSTrapScope(JSTrap t, Object val) { 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);
+        }
+    }
+}
+
diff --git a/src/org/xwt/js/Math.java b/src/org/xwt/js/Math.java
deleted file mode 100644 (file)
index 2bb8c4d..0000000
+++ /dev/null
@@ -1,110 +0,0 @@
-// Copyright 2003 Adam Megacz, see the COPYING file for licensing [GPL] 
-
-package org.xwt.js; 
-import org.xwt.util.*; 
-import java.io.*;
-import java.util.*;
-
-/** The JavaScript Math object */
-public class Math extends JS.Obj {
-    public static Math singleton = new Math();
-
-    private static final JS.Callable ceil = new JS.Callable() { public Object call(JS.Array args) {
-        return new Long((long)java.lang.Math.ceil(JS.toNumber(args.elementAt(0)).doubleValue())); } };
-
-    private static final JS.Callable floor = new JS.Callable() { public Object call(JS.Array args) {
-        return new Long((long)java.lang.Math.floor(JS.toNumber(args.elementAt(0)).doubleValue())); } };
-
-    private static final JS.Callable round = new JS.Callable() { public Object call(JS.Array args) {
-        return new Long((long)java.lang.Math.round(JS.toNumber(args.elementAt(0)).doubleValue())); } };
-
-    private static final JS.Callable min = new JS.Callable() { public Object call(JS.Array args) {
-        return new Double(java.lang.Math.min(JS.toNumber(args.elementAt(0)).doubleValue(),
-            JS.toNumber(args.elementAt(1)).doubleValue())); } };
-
-    private static final JS.Callable max = new JS.Callable() { public Object call(JS.Array args) {
-        return new Double(java.lang.Math.max(JS.toNumber(args.elementAt(0)).doubleValue(),
-            JS.toNumber(args.elementAt(1)).doubleValue())); } };
-
-    private static final JS.Callable pow = new JS.Callable() { public Object call(JS.Array args) {
-        return new Double(java.lang.Math.pow(JS.toNumber(args.elementAt(0)).doubleValue(),
-            JS.toNumber(args.elementAt(1)).doubleValue())); } };
-
-    private static final JS.Callable atan2 = new JS.Callable() { public Object call(JS.Array args) {
-        return new Double(java.lang.Math.atan2(JS.toNumber(args.elementAt(0)).doubleValue(),
-            JS.toNumber(args.elementAt(1)).doubleValue())); } };
-
-    private static final JS.Callable abs = new JS.Callable() { public Object call(JS.Array args) {
-        return new Double(java.lang.Math.abs(JS.toNumber(args.elementAt(0)).doubleValue())); } };
-
-    private static final JS.Callable sin = new JS.Callable() { public Object call(JS.Array args) {
-        return new Double(java.lang.Math.sin(JS.toNumber(args.elementAt(0)).doubleValue())); } };
-
-    private static final JS.Callable cos = new JS.Callable() { public Object call(JS.Array args) {
-        return new Double(java.lang.Math.cos(JS.toNumber(args.elementAt(0)).doubleValue())); } };
-
-    private static final JS.Callable tan = new JS.Callable() { public Object call(JS.Array args) {
-        return new Double(java.lang.Math.tan(JS.toNumber(args.elementAt(0)).doubleValue())); } };
-
-    private static final JS.Callable asin = new JS.Callable() { public Object call(JS.Array args) {
-        return new Double(java.lang.Math.asin(JS.toNumber(args.elementAt(0)).doubleValue())); } };
-
-    private static final JS.Callable acos = new JS.Callable() { public Object call(JS.Array args) {
-        return new Double(java.lang.Math.acos(JS.toNumber(args.elementAt(0)).doubleValue())); } };
-
-    private static final JS.Callable atan = new JS.Callable() { public Object call(JS.Array args) {
-        return new Double(java.lang.Math.atan(JS.toNumber(args.elementAt(0)).doubleValue())); } };
-
-    private static final JS.Callable sqrt = new JS.Callable() { public Object call(JS.Array args) {
-        return new Double(java.lang.Math.sqrt(JS.toNumber(args.elementAt(0)).doubleValue())); } };
-
-    private static final JS.Callable exp = new JS.Callable() { public Object call(JS.Array args) {
-        return new Double(java.lang.Math.exp(JS.toNumber(args.elementAt(0)).doubleValue())); } };
-
-    private static final JS.Callable log = new JS.Callable() { public Object call(JS.Array args) {
-        return new Double(java.lang.Math.log(JS.toNumber(args.elementAt(0)).doubleValue())); } };
-
-    private static final JS.Callable random = new JS.Callable() { public Object call(JS.Array args) {
-        return new Double(java.lang.Math.random()); } };
-
-    private static final Double E       = new Double(java.lang.Math.E);
-    private static final Double PI      = new Double(java.lang.Math.PI);
-    private static final Double LN10    = new Double(java.lang.Math.log(10));
-    private static final Double LN2     = new Double(java.lang.Math.log(2));
-    private static final Double LOG10E  = new Double(1/java.lang.Math.log(10));
-    private static final Double LOG2E   = new Double(1/java.lang.Math.log(2));
-    private static final Double SQRT1_2 = new Double(1/java.lang.Math.sqrt(2));
-    private static final Double SQRT2   = new Double(java.lang.Math.sqrt(2));
-
-    protected Math() {
-        put("abs", abs);
-        put("acos", acos);
-        put("asin", asin);
-        put("atan", atan);
-        put("atan2", atan2);
-        put("ceil", ceil);
-        put("cos", cos);
-        put("exp", exp);
-        put("floor", floor);
-        put("log", log);
-        put("max", max);
-        put("min", min);
-        put("pow", pow);
-        put("random", random);
-        put("round",round);
-        put("sin", sin);
-        put("sqrt", sqrt);
-        put("tan", tan);
-
-        put("E", E);
-        put("LN10", LN10);
-        put("LN2", LN2);
-        put("LOG10E", LOG10E);
-        put("LOG2E", LOG2E);
-        put("PI", PI);
-        put("SQRT1_2", SQRT1_2);
-        put("SQRT2", SQRT2);
-
-        setSeal(true);
-    }
-}
index 5e37596..ffb12d8 100644 (file)
@@ -4,8 +4,10 @@ package org.xwt.js;
 import org.xwt.util.*;
 import java.io.*;
 
+// FEATURE intern Integers/Numbers
+
 /**
- *  Parses a stream of lexed tokens into a tree of Function's.
+ *  Parses a stream of lexed tokens into a tree of JSFunction's.
  *
  *  There are three kinds of things we parse: blocks, statements, and
  *  expressions.
@@ -73,7 +75,7 @@ class Parser extends Lexer implements ByteCodes {
 
     /** for debugging */
     public static void main(String[] s) throws Exception {
-        Function block = new Function("stdin", 0, new InputStreamReader(System.in), null);
+        JSFunction block = new JSFunction("stdin", 0, new InputStreamReader(System.in), null);
         if (block == null) return;
         System.out.println(block);
     }
@@ -142,14 +144,14 @@ class Parser extends Lexer implements ByteCodes {
      *  bytecodes for that expression to <tt>appendTo</tt>; the
      *  appended bytecodes MUST grow the stack by exactly one element.
      */ 
-    private void startExpr(Function appendTo, int minPrecedence) throws IOException {
+    private void startExpr(JSFunction appendTo, int minPrecedence) throws IOException {
         int saveParserLine = parserLine;
         _startExpr(appendTo, minPrecedence);
         parserLine = saveParserLine;
     }
-    private void _startExpr(Function appendTo, int minPrecedence) throws IOException {
+    private void _startExpr(JSFunction appendTo, int minPrecedence) throws IOException {
         int tok = getToken();
-        Function b = appendTo;
+        JSFunction b = appendTo;
 
         switch (tok) {
         case -1: throw pe("expected expression");
@@ -200,7 +202,12 @@ class Parser extends Lexer implements ByteCodes {
                 b.pop();
             else
                 throw pe("prefixed increment/decrement can only be performed on a valid assignment target");
-            b.add(parserLine, tok, Boolean.TRUE);
+            b.add(parserLine, GET_PRESERVE, Boolean.TRUE);
+            b.add(parserLine, LITERAL, new Integer(1));
+            b.add(parserLine, tok == INC ? ADD : SUB, null);
+            b.add(parserLine, PUT, null);
+            b.add(parserLine, SWAP, null);
+            b.add(parserLine, POP, null);
             break;
         }
         case BANG: case BITNOT: case TYPEOF: {
@@ -236,7 +243,7 @@ class Parser extends Lexer implements ByteCodes {
         case FUNCTION: {
             consume(LP);
             int numArgs = 0;
-            Function b2 = new Function(sourceName, parserLine, null, null);
+            JSFunction b2 = new JSFunction(sourceName, parserLine, null, null);
             b.add(parserLine, NEWFUNCTION, b2);
 
             // function prelude; arguments array is already on the stack
@@ -272,7 +279,7 @@ class Parser extends Lexer implements ByteCodes {
             b2.add(parserLine, POP);                                      // pop off TOPSCOPE
             
            if(peekToken() != LC)
-                throw pe("Functions must have a block surrounded by curly brackets");
+                throw pe("JSFunctions must have a block surrounded by curly brackets");
                 
             parseBlock(b2, null);                                   // the function body
 
@@ -296,12 +303,12 @@ class Parser extends Lexer implements ByteCodes {
      *  expression that modifies the assignable.  This method always
      *  decreases the stack depth by exactly one element.
      */
-    private void continueExprAfterAssignable(Function b,int minPrecedence) throws IOException {
+    private void continueExprAfterAssignable(JSFunction b,int minPrecedence) throws IOException {
         int saveParserLine = parserLine;
         _continueExprAfterAssignable(b,minPrecedence);
         parserLine = saveParserLine;
     }
-    private void _continueExprAfterAssignable(Function b,int minPrecedence) throws IOException {
+    private void _continueExprAfterAssignable(JSFunction b,int minPrecedence) throws IOException {
         if (b == null) throw new Error("got null b; this should never happen");
         int tok = getToken();
         if (minPrecedence != -1 && (precedence[tok] < minPrecedence || (precedence[tok] == minPrecedence && !isRightAssociative[tok])))
@@ -325,7 +332,14 @@ class Parser extends Lexer implements ByteCodes {
             break;
         }
         case INC: case DEC: { // postfix
-            b.add(parserLine, tok, Boolean.FALSE);
+            b.add(parserLine, GET_PRESERVE, Boolean.TRUE);
+            b.add(parserLine, LITERAL, new Integer(1));
+            b.add(parserLine, tok == INC ? ADD : SUB, null);
+            b.add(parserLine, PUT, null);
+            b.add(parserLine, SWAP, null);
+            b.add(parserLine, POP, null);
+            b.add(parserLine, LITERAL, new Integer(1));
+            b.add(parserLine, tok == INC ? SUB : ADD, null);   // undo what we just did, since this is postfix
             break;
         }
         case ASSIGN: {
@@ -336,12 +350,12 @@ class Parser extends Lexer implements ByteCodes {
             break;
         }
         case LP: {
-            int n = parseArgs(b);
+            int n = parseArgs(b, false);
 
             // if the object supports GETCALL, we use this, and jump over the following two instructions
             b.add(parserLine,CALLMETHOD,new Integer(n));
             b.add(parserLine,GET);
-            b.add(parserLine,CALL_REVERSED,new Integer(n));
+            b.add(parserLine,CALL,new Integer(n));
             break;
         }
         default: {
@@ -367,12 +381,12 @@ class Parser extends Lexer implements ByteCodes {
      *  If any bytecodes are appended, they will not alter the stack
      *  depth.
      */
-    private void continueExpr(Function b, int minPrecedence) throws IOException {
+    private void continueExpr(JSFunction b, int minPrecedence) throws IOException {
         int saveParserLine = parserLine;
         _continueExpr(b, minPrecedence);
         parserLine = saveParserLine;
     }
-    private void _continueExpr(Function b, int minPrecedence) throws IOException {
+    private void _continueExpr(JSFunction b, int minPrecedence) throws IOException {
         if (b == null) throw new Error("got null b; this should never happen");
         int tok = getToken();
         if (tok == -1) return;
@@ -383,7 +397,7 @@ class Parser extends Lexer implements ByteCodes {
 
         switch (tok) {
         case LP: {  // invocation (not grouping)
-            int n = parseArgs(b);
+            int n = parseArgs(b, true);
             b.add(parserLine, CALL, new Integer(n));
             break;
         }
@@ -461,12 +475,14 @@ class Parser extends Lexer implements ByteCodes {
     }
     
     // parse a set of comma separated function arguments, assume LP has already been consumed
-    private int parseArgs(Function b) throws IOException {
+    // if swap is true, (because the function is already on the stack) we will SWAP after each argument to keep it on top
+    private int parseArgs(JSFunction b, boolean swap) throws IOException {
         int i = 0;
         while(peekToken() != RP) {
             i++;
             if (peekToken() != COMMA) {
                 startExpr(b, NO_COMMA);
+                if (swap) b.add(parserLine, SWAP);
                 if (peekToken() == RP) break;
             }
             consume(COMMA);
@@ -476,13 +492,13 @@ class Parser extends Lexer implements ByteCodes {
     }
     
     /** Parse a block of statements which must be surrounded by LC..RC. */
-    void parseBlock(Function b) throws IOException { parseBlock(b, null); }
-    void parseBlock(Function b, String label) throws IOException {
+    void parseBlock(JSFunction b) throws IOException { parseBlock(b, null); }
+    void parseBlock(JSFunction b, String label) throws IOException {
         int saveParserLine = parserLine;
         _parseBlock(b, label);
         parserLine = saveParserLine;
     }
-    void _parseBlock(Function b, String label) throws IOException {
+    void _parseBlock(JSFunction b, String label) throws IOException {
         if (peekToken() == -1) return;
         else if (peekToken() != LC) parseStatement(b, null);
         else {
@@ -493,12 +509,12 @@ class Parser extends Lexer implements ByteCodes {
     }
 
     /** Parse a single statement, consuming the RC or SEMI which terminates it. */
-    void parseStatement(Function b, String label) throws IOException {
+    void parseStatement(JSFunction b, String label) throws IOException {
         int saveParserLine = parserLine;
         _parseStatement(b, label);
         parserLine = saveParserLine;
     }
-    void _parseStatement(Function b, String label) throws IOException {
+    void _parseStatement(JSFunction b, String label) throws IOException {
         int tok = peekToken();
         if (tok == -1) return;
         switch(tok = getToken()) {
@@ -724,8 +740,8 @@ class Parser extends Lexer implements ByteCodes {
                 b.add(parserLine, NEWSCOPE);                             // grab a fresh scope
                     
                 parseStatement(b, null);                                 // initializer
-                Function e2 =                                    // we need to put the incrementor before the test
-                    new Function(sourceName, parserLine, null, null);  // so we save the test here
+                JSFunction e2 =                                    // we need to put the incrementor before the test
+                    new JSFunction(sourceName, parserLine, null, null);  // so we save the test here
                 if (peekToken() != SEMI)
                     startExpr(e2, -1);
                 else