From 67eeff476179a91ae930ea89cbecde22132ca532 Mon Sep 17 00:00:00 2001 From: megacz Date: Fri, 30 Jan 2004 07:41:12 +0000 Subject: [PATCH 1/1] 2003/11/13 05:04:22 darcs-hash:20040130074112-2ba56-d47745d9943423898465fe18811dabc174904f04.gz --- src/org/xwt/Picture.java | 39 +-- src/org/xwt/PixelBuffer.java | 16 +- src/org/xwt/Platform.java | 31 +- src/org/xwt/Res.java | 270 ++++----------- src/org/xwt/SOAP.java | 32 +- src/org/xwt/Scheduler.java | 21 +- src/org/xwt/Surface.java | 52 +-- src/org/xwt/Template.java | 92 +++--- src/org/xwt/Trap.java | 153 --------- src/org/xwt/VectorGraphics.java | 77 ++++- src/org/xwt/XMLRPC.java | 69 ++-- src/org/xwt/XWT.java | 169 ++++++---- src/org/xwt/js/ByteCodes.java | 11 +- src/org/xwt/js/GlobalScopeImpl.java | 141 -------- src/org/xwt/js/Internal.java | 32 +- src/org/xwt/js/JS.java | 244 +++----------- src/org/xwt/js/{ArrayImpl.java => JSArray.java} | 80 +++-- src/org/xwt/js/JSCallable.java | 24 ++ src/org/xwt/js/JSContext.java | 71 ++++ src/org/xwt/js/{Date.java => JSDate.java} | 109 ++----- src/org/xwt/js/{Function.java => JSFunction.java} | 249 ++++++++------ src/org/xwt/js/JSMath.java | 66 ++++ src/org/xwt/js/JSObj.java | 33 ++ src/org/xwt/js/JSRegexp.java | 361 +++++++++++++++++++++ src/org/xwt/js/JSScope.java | 132 ++++++++ src/org/xwt/js/JSTrap.java | 59 ++++ src/org/xwt/js/Math.java | 110 ------- src/org/xwt/js/Parser.java | 64 ++-- 28 files changed, 1473 insertions(+), 1334 deletions(-) delete mode 100644 src/org/xwt/Trap.java delete mode 100644 src/org/xwt/js/GlobalScopeImpl.java rename src/org/xwt/js/{ArrayImpl.java => JSArray.java} (73%) create mode 100644 src/org/xwt/js/JSCallable.java create mode 100644 src/org/xwt/js/JSContext.java rename src/org/xwt/js/{Date.java => JSDate.java} (93%) rename src/org/xwt/js/{Function.java => JSFunction.java} (71%) create mode 100644 src/org/xwt/js/JSMath.java create mode 100644 src/org/xwt/js/JSObj.java create mode 100644 src/org/xwt/js/JSRegexp.java create mode 100644 src/org/xwt/js/JSScope.java create mode 100644 src/org/xwt/js/JSTrap.java delete mode 100644 src/org/xwt/js/Math.java diff --git a/src/org/xwt/Picture.java b/src/org/xwt/Picture.java index 38f9c0a..942bf03 100644 --- a/src/org/xwt/Picture.java +++ b/src/org/xwt/Picture.java @@ -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; } } diff --git a/src/org/xwt/PixelBuffer.java b/src/org/xwt/PixelBuffer.java index eb8425a..8285f41 100644 --- a/src/org/xwt/PixelBuffer.java +++ b/src/org/xwt/PixelBuffer.java @@ -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 w; note that the coordinates here are post-transform */ 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 } } diff --git a/src/org/xwt/Platform.java b/src/org/xwt/Platform.java index f8937ca..1640779 100644 --- a/src/org/xwt/Platform.java +++ b/src/org/xwt/Platform.java @@ -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(); } } diff --git a/src/org/xwt/Res.java b/src/org/xwt/Res.java index 3bf88ee..b2ae73b 100644 --- a/src/org/xwt/Res.java +++ b/src/org/xwt/Res.java @@ -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= 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(); - } } diff --git a/src/org/xwt/SOAP.java b/src/org/xwt/SOAP.java index 2771304..59fc5c0 100644 --- a/src/org/xwt/SOAP.java +++ b/src/org/xwt/SOAP.java @@ -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\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\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\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\r\n"); return content.toString(); diff --git a/src/org/xwt/Scheduler.java b/src/org/xwt/Scheduler.java index a46c75c..79fba3c 100644 --- a/src/org/xwt/Scheduler.java +++ b/src/org/xwt/Scheduler.java @@ -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); diff --git a/src/org/xwt/Surface.java b/src/org/xwt/Surface.java index 89906b5..a187981 100644 --- a/src/org/xwt/Surface.java +++ b/src/org/xwt/Surface.java @@ -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; icontent - private int content_lines = 0; ///< number of lines in content - 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 content + private int content_lines = 0; ///< number of lines in content + 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 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 index 7d9dcc1..0000000 --- a/src/org/xwt/Trap.java +++ /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 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); - } - } -} - diff --git a/src/org/xwt/VectorGraphics.java b/src/org/xwt/VectorGraphics.java index 55c0930..212052e 100644 --- a/src/org/xwt/VectorGraphics.java +++ b/src/org/xwt/VectorGraphics.java @@ -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); diff --git a/src/org/xwt/XMLRPC.java b/src/org/xwt/XMLRPC.java index e32f569..8368bf7 100644 --- a/src/org/xwt/XMLRPC.java +++ b/src/org/xwt/XMLRPC.java @@ -30,9 +30,7 @@ import org.bouncycastle.util.encoders.Base64; * convert. * */ -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 <array> tag is encountered, a null is pushed onto the * stack. When a </data> 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 <struct> 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\n"); - } else if (o instanceof org.xwt.js.Date) { + } else if (o instanceof JSDate) { sb.append(" "); - 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("\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(" \n"); - JS.Array a = (JS.Array)o; + JSArray a = (JSArray)o; for(int i=0; i\n"); @@ -269,10 +267,11 @@ class XMLRPC extends JS.Callable { tracker.put(o, Boolean.TRUE); JS j = (JS)o; sb.append(" \n"); - Object[] ids = j.keys(); - for(int i=0; i" + ids[i] + "\n"); - appendObject(j.get(ids[i].toString()), sb); + Enumeration e = j.keys(); + while(e.hasMoreElements()) { + Object key = e.nextElement(); + sb.append(" " + key + "\n"); + appendObject(j.get(key), sb); sb.append(" \n"); } sb.append(" \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("\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. */ diff --git a/src/org/xwt/XWT.java b/src/org/xwt/XWT.java index 8b0fef7..99bd691 100644 --- a/src/org/xwt/XWT.java +++ b/src/org/xwt/XWT.java @@ -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= 1 ? (JS)obStack.elementAt(0) : null; + } + } } diff --git a/src/org/xwt/js/ByteCodes.java b/src/org/xwt/js/ByteCodes.java index 9087965..a5955a5 100644 --- a/src/org/xwt/js/ByteCodes.java +++ b/src/org/xwt/js/ByteCodes.java @@ -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 index 5d46c83..0000000 --- a/src/org/xwt/js/GlobalScopeImpl.java +++ /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 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 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"); - } -} diff --git a/src/org/xwt/js/Internal.java b/src/org/xwt/js/Internal.java index cf19716..d23794b 100644 --- a/src/org/xwt/js/Internal.java +++ b/src/org/xwt/js/Internal.java @@ -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); } } } diff --git a/src/org/xwt/js/JS.java b/src/org/xwt/js/JS.java index 82370a6..6d964ea 100644 --- a/src/org/xwt/js/JS.java +++ b/src/org/xwt/js/JS.java @@ -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= 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 + ""); - - } else if (o instanceof JS.Array) { - Log.logJS(indent + name + ""); - JS.Array na = (JS.Array)o; - for(int i=0; i"); - JS s = (JS)o; - Object[] keys = s.keys(); - for(int i=0; i 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= 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 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 0) { diff --git a/src/org/xwt/js/JSCallable.java b/src/org/xwt/js/JSCallable.java new file mode 100644 index 0000000..42def05 --- /dev/null +++ b/src/org/xwt/js/JSCallable.java @@ -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 index 0000000..4eaf9b2 --- /dev/null +++ b/src/org/xwt/js/JSContext.java @@ -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); + } + } +} diff --git a/src/org/xwt/js/Date.java b/src/org/xwt/js/JSDate.java similarity index 93% rename from src/org/xwt/js/Date.java rename to src/org/xwt/js/JSDate.java index 150d738..37624b4 100644 --- a/src/org/xwt/js/Date.java +++ b/src/org/xwt/js/JSDate.java @@ -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; iunpauseable 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= 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 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= 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 index 0000000..5a2ea4b --- /dev/null +++ b/src/org/xwt/js/JSMath.java @@ -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 index 0000000..e096521 --- /dev/null +++ b/src/org/xwt/js/JSObj.java @@ -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 index 0000000..b0359ba --- /dev/null +++ b/src/org/xwt/js/JSRegexp.java @@ -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= 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 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= '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 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;iappendTo; 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 -- 1.7.10.4