From: megacz Date: Fri, 30 Jan 2004 07:01:02 +0000 (+0000) Subject: 2003/06/12 17:57:37 X-Git-Tag: RC3~928 X-Git-Url: http://git.megacz.com/?p=org.ibex.core.git;a=commitdiff_plain;h=ec3ce7739bc84c0641fc57efc8da9af89372013f 2003/06/12 17:57:37 darcs-hash:20040130070102-2ba56-20b2a89a5dd3a6650c167e60bf55fdd2fb322ef2.gz --- diff --git a/src/org/xwt/MessageQueue.java b/src/org/xwt/MessageQueue.java index 6de9185..cfc388b 100644 --- a/src/org/xwt/MessageQueue.java +++ b/src/org/xwt/MessageQueue.java @@ -117,18 +117,18 @@ public class MessageQueue extends Thread { if ((m != null && m == MessageQueue.currentlyPerforming) || MessageQueue.working) { String what, where; if (m != null && m instanceof ThreadMessage) { - where = org.xwt.js.JS.getCurrentFunctionSourceName((ThreadMessage)m); + where = org.xwt.js.Context.getSourceNameAndLineForThread((ThreadMessage)m); what = "background thread"; } else if (m != null) { - where = org.xwt.js.JS.getCurrentFunctionSourceName(MessageQueue.singleton); + where = org.xwt.js.Context.getSourceNameAndLineForThread(MessageQueue.singleton); what = "event trap"; } else { - where = org.xwt.js.JS.getCurrentFunctionSourceName(MessageQueue.singleton); + where = org.xwt.js.Context.getSourceNameAndLineForThread(MessageQueue.singleton); what = "script"; } - if (Log.on) Log.log(this, "note: executing same " + what + - " for " + (System.currentTimeMillis() - t) / 1000 + "s" + - " at " + where); + long howlong = (System.currentTimeMillis() - t) / 1000; + if (howlong >= 5) + if (Log.on) Log.log(this, "note: executing same " + what + " for " + howlong + "s" + " at " + where); } else { m = MessageQueue.currentlyPerforming; t = System.currentTimeMillis(); diff --git a/src/org/xwt/Proxy.java b/src/org/xwt/Proxy.java index 81318a0..6067464 100644 --- a/src/org/xwt/Proxy.java +++ b/src/org/xwt/Proxy.java @@ -35,7 +35,7 @@ public class Proxy { public String[] excluded = null; /** the PAC script */ - public JS.Function proxyAutoConfigFunction = null; + public JS.Callable proxyAutoConfigFunction = null; public static Proxy detectProxyViaManual() { Proxy ret = new Proxy(); @@ -87,14 +87,9 @@ public class Proxy { return ret; } - //public static JS proxyAutoConfigRootScope = new ProxyAutoConfigRootScope(); - public static JS.Function getProxyAutoConfigFunction(String url) { - throw new Error("not implemented"); - // FIXME - /* + public static JS.Scope proxyAutoConfigRootScope = new ProxyAutoConfigRootScope(); + public static JS.Callable getProxyAutoConfigFunction(String url) { try { - Context cx = Context.enter(); - cx.setOptimizationLevel(-1); BufferedReader br = new BufferedReader(new InputStreamReader(new HTTP(url, true).GET())); String s = null; String script = ""; @@ -118,28 +113,27 @@ public class Proxy { if (Log.on) Log.log(Proxy.class, "DeCARPed PAC script:"); if (Log.on) Log.log(Proxy.class, script); } - - Script scr = cx.compileReader(proxyAutoConfigRootScope, new StringReader(script), "PAC script at " + url, 0, null); - scr.exec(cx, proxyAutoConfigRootScope); - return (JS.Function)proxyAutoConfigRootScope.get("FindProxyForURL", null); + + JS.Callable scr = JS.parse("PAC script at " + url, 0, new StringReader(script), proxyAutoConfigRootScope); + scr.call(new org.xwt.js.Array()); + return (JS.Callable)proxyAutoConfigRootScope.get("FindProxyForURL"); } catch (Exception e) { if (Log.on) { Log.log(Platform.class, "WPAD detection failed due to:"); - if (e instanceof EcmaError) Log.log(HTTP.class, ((EcmaError)e).getMessage() + " at " + - ((EcmaError)e).getSourceName() + ":" + ((EcmaError)e).getLineNumber()); - else if (e instanceof JS.Exn) { - try { - XWT.recursivePrintObject.call(Context.enter(), null, null, new Object[] { - ((JS.Exn)e).getValue() }); - } catch (Exception e2) { - Log.log(Platform.class, e); - } - } + if (e instanceof JS.Exn) { + try { + org.xwt.js.Array arr = new org.xwt.js.Array(); + arr.addElement(((JS.Exn)e).getObject()); + // FIXME + //XWT.recursivePrintObject.call(); + } catch (Exception e2) { + Log.log(Platform.class, e); + } + } else Log.log(Platform.class, e); } return null; } - */ } @@ -175,13 +169,14 @@ public class Proxy { // ProxyAutoConfigRootScope //////////////////////////////////////////////////////////////////// - /* - public static class ProxyAutoConfigRootScope extends JSObject { - - public String getClassName() { return "ProxyAutoConfigRootScope"; } - ProxyAutoConfigRootScope() { Context.enter().initStandardObjects(this); } + + public static class ProxyAutoConfigRootScope extends JS.Scope { + + public ProxyAutoConfigRootScope() { super(null); } - public Object get(String name, JS start) { + // FIXME: needs "standard objects" + + public Object get(Object name) { if (name.equals("isPlainHostName")) return isPlainHostName; else if (name.equals("dnsDomainIs")) return dnsDomainIs; else if (name.equals("localHostOrDomainIs")) return localHostOrDomainIs; @@ -195,54 +190,54 @@ public class Proxy { else if (name.equals("dateRange")) return dateRange; else if (name.equals("timeRange")) return timeRange; else if (name.equals("ProxyConfig")) return ProxyConfig; - else return super.get(name, start); + else return super.get(name); } - private static final JS.Object proxyConfigBindings = new JS.Object(); - private static final JS.Object ProxyConfig = new JS.Object() { - public Object get(String name, JS start) { + private static final JS.Obj proxyConfigBindings = new JS.Obj(); + private static final JS.Obj ProxyConfig = new JS.Obj() { + public Object get(Object name) { if (name.equals("bindings")) return proxyConfigBindings; return null; } }; - private static final JS.Function isPlainHostName = new JS.Function() { - public Object call(Context cx, JS thisObj, JS ctorObj, Object[] args) throws JS.Exn { - return (args[0].toString().indexOf('.') == -1) ? Boolean.TRUE : Boolean.FALSE; + private static final JS.Callable isPlainHostName = new JS.Callable() { + public Object call(org.xwt.js.Array args) throws JS.Exn { + return (args.elementAt(0).toString().indexOf('.') == -1) ? Boolean.TRUE : Boolean.FALSE; } }; - private static final JS.Function dnsDomainIs = new JS.Function() { - public Object call(Context cx, JS thisObj, JS ctorObj, Object[] args) throws JS.Exn { - return (args[0].toString().endsWith(args[1].toString())) ? Boolean.TRUE : Boolean.FALSE; + private static final JS.Callable dnsDomainIs = new JS.Callable() { + public Object call(org.xwt.js.Array args) throws JS.Exn { + return (args.elementAt(0).toString().endsWith(args.elementAt(1).toString())) ? Boolean.TRUE : Boolean.FALSE; } }; - private static final JS.Function localHostOrDomainIs = new JS.Function() { - public Object call(Context cx, JS thisObj, JS ctorObj, Object[] args) throws JS.Exn { - return (args[0].toString().equals(args[1].toString()) || - (args[0].toString().indexOf('.') == -1 && args[1].toString().startsWith(args[0].toString()))) ? + private static final JS.Callable localHostOrDomainIs = new JS.Callable() { + public Object call(org.xwt.js.Array args) throws JS.Exn { + return (args.elementAt(0).toString().equals(args.elementAt(1).toString()) || + (args.elementAt(0).toString().indexOf('.') == -1 && args.elementAt(1).toString().startsWith(args.elementAt(0).toString()))) ? Boolean.TRUE : Boolean.FALSE; } }; - private static final JS.Function isResolvable = new JS.Function() { - public Object call(Context cx, JS thisObj, JS ctorObj, Object[] args) throws JS.Exn { + private static final JS.Callable isResolvable = new JS.Callable() { + public Object call(org.xwt.js.Array args) throws JS.Exn { try { - return (InetAddress.getByName(args[0].toString()) != null) ? Boolean.TRUE : Boolean.FALSE; + return (InetAddress.getByName(args.elementAt(0).toString()) != null) ? Boolean.TRUE : Boolean.FALSE; } catch (UnknownHostException e) { return Boolean.FALSE; } } }; - private static final JS.Function isInNet = new JS.Function() { - public Object call(Context cx, JS thisObj, JS ctorObj, Object[] args) throws JS.Exn { - if (args.length != 3) return Boolean.FALSE; + private static final JS.Callable isInNet = new JS.Callable() { + public Object call(org.xwt.js.Array args) throws JS.Exn { + if (args.length() != 3) return Boolean.FALSE; try { - byte[] host = InetAddress.getByName(args[0].toString()).getAddress(); - byte[] net = InetAddress.getByName(args[1].toString()).getAddress(); - byte[] mask = InetAddress.getByName(args[2].toString()).getAddress(); + byte[] host = InetAddress.getByName(args.elementAt(0).toString()).getAddress(); + byte[] net = InetAddress.getByName(args.elementAt(1).toString()).getAddress(); + byte[] mask = InetAddress.getByName(args.elementAt(2).toString()).getAddress(); return ((host[0] & mask[0]) == net[0] && (host[1] & mask[1]) == net[1] && (host[2] & mask[2]) == net[2] && @@ -254,18 +249,18 @@ public class Proxy { } }; - private static final JS.Function dnsResolve = new JS.Function() { - public Object call(Context cx, JS thisObj, JS ctorObj, Object[] args) throws JS.Exn { + private static final JS.Callable dnsResolve = new JS.Callable() { + public Object call(org.xwt.js.Array args) throws JS.Exn { try { - return InetAddress.getByName(args[0].toString()).getHostAddress(); + return InetAddress.getByName(args.elementAt(0).toString()).getHostAddress(); } catch (UnknownHostException e) { return null; } } }; - private static final JS.Function myIpAddress = new JS.Function() { - public Object call(Context cx, JS thisObj, JS ctorObj, Object[] args) throws JS.Exn { + private static final JS.Callable myIpAddress = new JS.Callable() { + public Object call(org.xwt.js.Array args) throws JS.Exn { try { return InetAddress.getLocalHost().getHostAddress(); } catch (UnknownHostException e) { @@ -275,9 +270,9 @@ public class Proxy { } }; - private static final JS.Function dnsDomainLevels = new JS.Function() { - public Object call(Context cx, JS thisObj, JS ctorObj, Object[] args) throws JS.Exn { - String s = args[0].toString(); + private static final JS.Callable dnsDomainLevels = new JS.Callable() { + public Object call(org.xwt.js.Array args) throws JS.Exn { + String s = args.elementAt(0).toString(); int i = 0; while((i = s.indexOf('.', i)) != -1) i++; return new Integer(i); @@ -293,11 +288,11 @@ public class Proxy { return false; } - private static final JS.Function shExpMatch = new JS.Function() { - public Object call(Context cx, JS thisObj, JS ctorObj, Object[] args) throws JS.Exn { - StringTokenizer st = new StringTokenizer(args[1].toString(), "*", false); + private static final JS.Callable shExpMatch = new JS.Callable() { + public Object call(org.xwt.js.Array args) throws JS.Exn { + StringTokenizer st = new StringTokenizer(args.elementAt(1).toString(), "*", false); String[] arr = new String[st.countTokens()]; - String s = args[0].toString(); + String s = args.elementAt(0).toString(); for (int i=0; st.hasMoreTokens(); i++) arr[i] = st.nextToken(); return match(arr, s, 0) ? Boolean.TRUE : Boolean.FALSE; } @@ -305,23 +300,23 @@ public class Proxy { public static String[] days = { "SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT" }; - private static final JS.Function weekdayRange = new JS.Function() { - public Object call(Context cx, JS thisObj, JS ctorObj, Object[] args) throws JS.Exn { - TimeZone tz = (args.length < 3 || args[2] == null || !args[2].equals("GMT")) ? TimeZone.getTimeZone("UTC") : TimeZone.getDefault(); + private static final JS.Callable weekdayRange = new JS.Callable() { + public Object call(org.xwt.js.Array args) throws JS.Exn { + TimeZone tz = (args.length() < 3 || args.elementAt(2) == null || !args.elementAt(2).equals("GMT")) ? TimeZone.getTimeZone("UTC") : TimeZone.getDefault(); Calendar c = new GregorianCalendar(); c.setTimeZone(tz); - c.setTime(new Date()); - Date d = c.getTime(); + c.setTime(new java.util.Date()); + java.util.Date d = c.getTime(); int day = d.getDay(); - String d1s = args[0].toString().toUpperCase(); + String d1s = args.elementAt(0).toString().toUpperCase(); int d1 = 0, d2 = 0; for(int i=0; i\r\n"); - } else if (o instanceof JS.Array) { - JS.Array a = (JS.Array)o; + } else if (o instanceof Array) { + Array a = (Array)o; sb.append(" <" + name + " SOAP-ENC:arrayType=\"xsd:ur-type[" + a.length() + "]\">"); for(int i=0; i\r\n"); @@ -236,7 +236,7 @@ class SOAP extends XMLRPC { } } - protected String send(JS.Array args, HTTP http) throws JS.Exn, IOException { + protected String send(Array args, HTTP http) throws JS.Exn, IOException { // build up the request StringBuffer content = new StringBuffer(); content.append("SOAPAction: " + action + "\r\n\r\n"); diff --git a/src/org/xwt/SpecialBoxProperty.java b/src/org/xwt/SpecialBoxProperty.java index 00f43db..b492e6c 100644 --- a/src/org/xwt/SpecialBoxProperty.java +++ b/src/org/xwt/SpecialBoxProperty.java @@ -106,7 +106,7 @@ class SpecialBoxProperty { else if (s.equals("pink")) newcolor = pink; else if (s.equals("yellow")) newcolor = yellow; else if (s.equals("white")) newcolor = white; - else if (Log.on) Log.log(this, "invalid color \"" + s + "\" at " + JS.getCurrentFunctionSourceName()); + else if (Log.on) Log.log(this, "invalid color \"" + s + "\" at " + Context.getCurrentSourceNameAndLine()); if (newcolor == b.color) return; b.color = newcolor; b.dirty(); @@ -149,7 +149,7 @@ class SpecialBoxProperty { else if (s.equals("pink")) newtextcolor = pink; else if (s.equals("yellow")) newtextcolor = yellow; else if (s.equals("white")) newtextcolor = white; - else if (Log.on) Log.log(this, "invalid color \"" + s + "\" at " + JS.getCurrentFunctionSourceName()); + else if (Log.on) Log.log(this, "invalid color \"" + s + "\" at " + Context.getCurrentSourceNameAndLine()); if (newtextcolor == b.textcolor) return; b.textcolor = newtextcolor; b.dirty(); @@ -219,8 +219,7 @@ class SpecialBoxProperty { specialBoxProperties.put("static", new SpecialBoxProperty() { public Object get(Box b) { - JS.Function cf = JS.getCurrentFunction(); - String cfsn = cf.getSourceName(); + String cfsn = Context.getContextForThread(Thread.currentThread()).getCurrentFunction().getSourceName(); for(int i=0; if */ - public static synchronized void newthread(JS.Function f) { + public static synchronized void newthread(JS.Callable f) { ThreadMessage ret = (ThreadMessage)spare.remove(false); if (ret == null) { if (threadcount < Platform.maxThreads()) ret = new ThreadMessage(); @@ -56,7 +56,7 @@ public class ThreadMessage extends Thread implements Message { // put ourselves in the background Thread thread = Thread.currentThread(); if (!(thread instanceof ThreadMessage)) { - if (Log.on) Log.log(ThreadMessage.class, "attempt to perform background-only operation in a foreground thread at " + JS.getCurrentFunctionSourceName()); + if (Log.on) Log.log(ThreadMessage.class, "attempt to perform background-only operation in a foreground thread at " + Context.getCurrentSourceNameAndLine()); return false; } ThreadMessage mythread = (ThreadMessage)thread; @@ -79,14 +79,14 @@ public class ThreadMessage extends Thread implements Message { while (true) { try { go.block(); - f.call(new JS.Array()); + f.call(new Array()); } catch (JS.Exn e) { if (Log.on) Log.log(this, "WARNING: uncaught ecmascript exception: " + e); } done.release(); synchronized(waiting) { if (waiting.size() > 0) { - f = (JS.Function)waiting.remove(false); + f = (JS.Callable)waiting.remove(false); MessageQueue.add(this); } else if (spare.size() < 10) { spare.append(this); diff --git a/src/org/xwt/Trap.java b/src/org/xwt/Trap.java index 1ce4289..581bd2a 100644 --- a/src/org/xwt/Trap.java +++ b/src/org/xwt/Trap.java @@ -44,7 +44,7 @@ public class Trap { private JS rp = null; /** the function for this trap */ - JS.Function f = null; + CompiledFunction f = null; /** the name of the property that this trap was placed on */ private String name = null; @@ -72,7 +72,7 @@ public class Trap { * @param isreadtrap true iff this is a read (double-underscore) trap * @param rp if this trap is being placed via a rootproxy, this is that proxy object. */ - static void addTrap(Box trapee, String name, JS.Function f, boolean isreadtrap, JS rp) { + static void addTrap(Box trapee, String name, CompiledFunction f, boolean isreadtrap, JS rp) { if (PROHIBITED.get(name) != null || name.startsWith("xwt_")) { Log.log(Trap.class, "Error: you cannot place traps on special property \"" + name + "\""); @@ -80,7 +80,9 @@ public class Trap { } // find out what script is currently running - String placerNodeName = JS.getCurrentFunctionSourceName(); + CompiledFunction placer = Context.getContextForThread(Thread.currentThread()).getCurrentFunction(); + if (placer == null) { Log.log(Trap.class, "placer is null"); return; } + String placerNodeName = placer.getSourceName(); // check if this script has already placed a trap on this property if (trapee.traps == null) trapee.traps = new Hash(10, 3); @@ -108,7 +110,7 @@ public class Trap { public static Trap getTrap(Box b, String name) { if (b.traps == null) return null; - String currentFunctionNodeName = JS.getCurrentFunctionSourceName(); + String currentFunctionNodeName = Context.getContextForThread(Thread.currentThread()).getCurrentFunction().getSourceName(); for(Trap cur = (Trap)b.traps.get(name); cur != null; cur = cur.next) if (cur.placerNodeName.equals(currentFunctionNodeName)) return cur; @@ -118,10 +120,10 @@ public class Trap { /** Called by Rhino's arguments.cascade. Note: cx will be null if this was invoked from perform() rather than from a script. */ public static final CascadeFunction cascadeFunction = new CascadeFunction(); - private static class CascadeFunction extends JS.Function { - CascadeFunction() { super(-1, "java", null, null); setSeal(true); } - public Object _call(JS.Array args) { return _call(args, JS.getCurrentFunction()); } - public Object _call(JS.Array args, Function currentFunction) { + private static class CascadeFunction extends JS.Callable { + CascadeFunction() { setSeal(true); } + public Object call(Array args) { return call(args, Context.getContextForThread(Thread.currentThread()).getCurrentFunction()); } + public Object call(Array args, CompiledFunction currentFunction) { Trap currentTrap = TrapContext.get().currentTrap; if (args.length() != 0) TrapContext.get().putCascadeHappened = true; Trap t = currentTrap.next; @@ -156,7 +158,7 @@ public class Trap { private Trap() { allTraps.put(myWeak, dummy); } /** perform this trap -- arg.length == 0 if this is a get; otherwise it contains a single element to be put */ - public Object perform(JS.Array args) { + public Object perform(Array args) { TrapContext tc = TrapContext.get(); // save both thread-locals on the stack and update their values @@ -168,11 +170,11 @@ public class Trap { // invoke the trap function try { - if (!isreadtrap && args.length() == 0) return cascadeFunction._call(args, f); + if (!isreadtrap && args.length() == 0) return cascadeFunction.call(args, f); if (f == null) { if (Log.verbose) Log.log(this, "debug: reclaimed a dangling trap on property " + name); - Object ret = cascadeFunction._call(args, f); + Object ret = cascadeFunction.call(args, f); delete(); return ret; } @@ -180,7 +182,7 @@ public class Trap { Object ret = f.call(args); // autocascade if required - if (args.length() > 0 && !isreadtrap && !tc.putCascadeHappened) cascadeFunction._call(args, f); + if (args.length() > 0 && !isreadtrap && !tc.putCascadeHappened) cascadeFunction.call(args, f); return ret; diff --git a/src/org/xwt/XMLRPC.java b/src/org/xwt/XMLRPC.java index f2e8eee..ce1fab2 100644 --- a/src/org/xwt/XMLRPC.java +++ b/src/org/xwt/XMLRPC.java @@ -30,7 +30,7 @@ import org.bouncycastle.util.encoders.Base64; * convert. * */ -class XMLRPC extends JS.Function { +class XMLRPC extends JS.Callable { public Object[] keys() { throw new Error("not implemented"); } @@ -151,7 +151,7 @@ class XMLRPC extends JS.Function { } else if (c.localName.equals("data")) { int i; for(i=objects.size() - 1; objects.elementAt(i) != null; i--); - JS.Array arr = new JS.Array(); + Array arr = new Array(); for(int j = i + 1; j\n"); */ - } else if (o instanceof JS.Array) { + } else if (o instanceof Array) { 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; + Array a = (Array)o; for(int i=0; i\n"); @@ -292,7 +292,7 @@ class XMLRPC extends JS.Function { // 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 synchronized Object call2(Array args) throws JS.Exn, IOException { if (Log.verbose) Log.log(this, "call to " + url + " : " + methodname); if (tracker == null) tracker = new Hash(); @@ -333,7 +333,7 @@ class XMLRPC extends JS.Function { } } - protected String send(JS.Array args, HTTP http) throws JS.Exn, IOException { + protected String send(Array args, HTTP http) throws JS.Exn, IOException { StringBuffer content = new StringBuffer(); content.append("\r\n"); content.append("\n"); @@ -366,7 +366,7 @@ class XMLRPC extends JS.Function { return objects.elementAt(0); } - public final Object _call(JS.Array args) throws JS.Exn { + public final Object call(Array args) throws JS.Exn { if (!ThreadMessage.suspendThread()) return null; @@ -395,7 +395,6 @@ class XMLRPC extends JS.Function { } public XMLRPC(String url, String methodname, HTTP http) { - super(-1, "java", null, null); this.http = http; this.url = url; this.methodname = methodname; diff --git a/src/org/xwt/XWT.java b/src/org/xwt/XWT.java index ed8feb1..f20cf26 100644 --- a/src/org/xwt/XWT.java +++ b/src/org/xwt/XWT.java @@ -37,7 +37,7 @@ public final class XWT extends JS.Obj { } public void put(Object name, Object value) { - if (name.equals("thread") && value != null && value instanceof JS.Function) ThreadMessage.newthread((JS.Function)value); + if (name.equals("thread") && value != null && value instanceof JS.Callable) ThreadMessage.newthread((JS.Callable)value); else if (name.equals("clipboard")) Platform.setClipBoard(value.toString()); else if (name.equals("proxyAuthorization")) { // FIXME: undocumented, possibly insecure @@ -55,67 +55,30 @@ public final class XWT extends JS.Obj { put("fileSeparator", File.separator); put("homeDir", System.getProperty("user.home")); put("tempDir", System.getProperty("java.io.tempdir")); + put("math", org.xwt.js.Math.singleton); - put("math", new JS.Obj() { public Object get(Object name) { - if ("ceil".equals(name)) return new JS.Function(-1, "java", null, null) { public Object _call(JS.Array args) - { if (args.elementAt(0) == null) return null; - return new Long((long)Math.ceil(Double.parseDouble(args.elementAt(0).toString()))); } }; - else if ("floor".equals(name)) return new JS.Function(-1, "java", null, null) { public Object _call(JS.Array args) - { if (args.elementAt(0) == null) return null; - return new Long((long)Math.floor(Double.parseDouble(args.elementAt(0).toString()))); } }; - else if ("round".equals(name)) return new JS.Function(-1, "java", null, null) { public Object _call(JS.Array args) - { if (args.elementAt(0) == null) return null; - return new Long((long)Math.round(Double.parseDouble(args.elementAt(0).toString()))); } }; - else if ("abs".equals(name)) return new JS.Function(-1, "java", null, null) { public Object _call(JS.Array args) - { if (args.elementAt(0) == null) return null; - return new Long((long)Math.abs(Double.parseDouble(args.elementAt(0).toString()))); } }; - else if ("min".equals(name)) return new JS.Function(-1, "java", null, null) { public Object _call(JS.Array args) { - if (args.length() < 2 || args.elementAt(0) == null || args.elementAt(1) == null) return args.elementAt(0); - return new Double(Math.min(((Number)args.elementAt(0)).doubleValue(), - ((Number)args.elementAt(1)).doubleValue())); } }; - else if ("max".equals(name)) return new JS.Function(-1, "java", null, null) { public Object _call(JS.Array args) { - if (args.length() < 2) return args.elementAt(0); - return new Double(Math.max(((Number)args.elementAt(0)).doubleValue(), - ((Number)args.elementAt(1)).doubleValue())); } }; - else if ("cos".equals(name)) return new JS.Function(-1, "java", null, null) { public Object _call(JS.Array args) { - return new Double(Math.cos(((Number)args.elementAt(0)).doubleValue())); } }; - else if ("sin".equals(name)) return new JS.Function(-1, "java", null, null) { public Object _call(JS.Array args) { - return new Double(Math.sin(((Number)args.elementAt(0)).doubleValue())); } }; - else if ("tan".equals(name)) return new JS.Function(-1, "java", null, null) { public Object _call(JS.Array args) { - return new Double(Math.tan(((Number)args.elementAt(0)).doubleValue())); } }; - else if ("acos".equals(name)) return new JS.Function(-1, "java", null, null) { public Object _call(JS.Array args) { - return new Double(Math.acos(((Number)args.elementAt(0)).doubleValue())); } }; - else if ("asin".equals(name)) return new JS.Function(-1, "java", null, null) { public Object _call(JS.Array args) { - return new Double(Math.asin(((Number)args.elementAt(0)).doubleValue())); } }; - else if ("atan".equals(name)) return new JS.Function(-1, "java", null, null) { public Object _call(JS.Array args) { - return new Double(Math.atan(((Number)args.elementAt(0)).doubleValue())); } }; - else if ("sqrt".equals(name)) return new JS.Function(-1, "java", null, null) { public Object _call(JS.Array args) { - return new Double(Math.sqrt(((Number)args.elementAt(0)).doubleValue())); } }; - return null; - }}); - - put("newBrowserWindow", new JS.Function(-1, "java", null, null) { public Object _call(JS.Array args) throws JS.Exn { + put("newBrowserWindow", new JS.Callable() { public Object call(Array args) throws JS.Exn { if (args.length() != 1 || args.elementAt(0) == null) return null; Platform.newBrowserWindow(args.elementAt(0).toString()); return null; }}); - put("parseFloat", new JS.Function(-1, "java", null, null) { public Object _call(JS.Array args) throws JS.Exn { + put("parseFloat", new JS.Callable() { public Object call(Array args) throws JS.Exn { if (args.length() != 1 || args.elementAt(0) == null) return null; return new Float(args.elementAt(0).toString()); }}); - put("parseInt", new JS.Function(-1, "java", null, null) { public Object _call(JS.Array args) throws JS.Exn { + put("parseInt", new JS.Callable() { public Object call(Array args) throws JS.Exn { if (args.length() != 1 || args.elementAt(0) == null) return null; return new Float(args.elementAt(0).toString()); }}); - put("yield", new JS.Function(-1, "java", null, null) { public Object _call(JS.Array args) throws JS.Exn { + put("yield", new JS.Callable() { public Object call(Array args) throws JS.Exn { sleep(0); return null; }}); - put("theme", new JS.Function(-1, "java", null, null) { public Object _call(JS.Array args) throws JS.Exn { + put("theme", new JS.Callable() { public Object call(Array args) throws JS.Exn { if (args.length() != 2) return null; if (args.elementAt(0) == null || args.elementAt(1) == null) return null; for(int i=1; i 2) return null; if (args.elementAt(0) == null || (args.length() == 2 && args.elementAt(1) == null)) return null; String font = args.length() == 1 ? Platform.getDefaultFont() : args.elementAt(0).toString(); @@ -183,7 +146,7 @@ public final class XWT extends JS.Obj { else return new Integer(xwf.stringWidth(text)); }}); - put("textheight", new JS.Function(-1, "java", null, null) { public Object _call(JS.Array args) throws JS.Exn { + put("textheight", new JS.Callable() { public Object call(Array args) throws JS.Exn { if (args.length() > 1) return null; if (args.length() == 1 && args.elementAt(0) == null) return null; String font = args.length() == 0 || args.elementAt(0) == null ? Platform.getDefaultFont() : args.elementAt(0).toString(); @@ -192,19 +155,19 @@ public final class XWT extends JS.Obj { else return new Integer(xwf.getMaxAscent() + xwf.getMaxDescent()); }}); - put("newBox", new JS.Function(-1, "java", null, null) { public Object _call(JS.Array args) throws JS.Exn { + put("newBox", new JS.Callable() { public Object call(Array args) throws JS.Exn { if (args.length() > 0) Log.log(XWT.class, "DEPRECATED: xwt.newBox() with multiple arguments is deprecated; use xwt.newBox().apply()"); - JS.Function callback = null; + JS.Callable callback = null; for(int i=1; i 1 && args.elementAt(1) instanceof JS.Function ? (JS.Function)args.elementAt(1) : null); + args.length() > 1 && args.elementAt(1) instanceof JS.Callable ? (JS.Callable)args.elementAt(1) : null); return null; }}); } @@ -359,16 +322,16 @@ public final class XWT extends JS.Obj { if (!name.equals("")) name += " : "; if (o == null) { - Log.log(JS.getCurrentFunction().getSourceName(), indent + name + ""); + Log.log(Context.getCurrentSourceNameAndLine() , indent + name + ""); - } else if (o instanceof JS.Array) { - Log.log(JS.getCurrentFunction().getSourceName(), indent + name + ""); - JS.Array na = (JS.Array)o; + } else if (o instanceof Array) { + Log.log(Context.getCurrentSourceNameAndLine() , indent + name + ""); + Array na = (Array)o; for(int i=0; i"); + Log.log(Context.getCurrentSourceNameAndLine() , indent + name + ""); JS s = (JS)o; Object[] keys = s.keys(); for(int i=0; i '9') return Integer.MIN_VALUE; + return Integer.parseInt(s); + } + public Object get(Object key) throws JS.Exn { + // FIXME: HACK! + if (key.equals("cascade")) return org.xwt.Trap.cascadeFunction; + if (key.equals("trapee")) return org.xwt.Trap.currentTrapee(); + if (key.equals("length")) return new Long(vec.size()); + int i = intVal(key); + if (i == Integer.MIN_VALUE) return super.get(key); + try { + return vec.elementAt(i); + } catch (ArrayIndexOutOfBoundsException e) { + return null; + } + } + public void put(Object key, Object val) { + if (key.equals("length")) vec.setSize(toNumber(val).intValue()); + int i = intVal(key); + if (i == Integer.MIN_VALUE) super.put(key, val); + else { + if (i >= vec.size()) vec.setSize(i+1); + vec.setElementAt(val, i); + } + } + public Object[] keys() { + Object[] sup = super.keys(); + Object[] ret = new Object[vec.size() + 1 + sup.length]; + System.arraycopy(sup, 0, ret, vec.size(), sup.length); + for(int i=0; i 0) { - Object o = t.pop(); - if (o != null && o instanceof Context.CallMarker) { - t.push(retval); - return; - } - } - throw new Error("error: RETURN invoked but couldn't find a CallMarker!"); - } - - case THROW: throw new JS.Exn(t.pop()); - case TRY: break; - case TYPEOF: break; // FIXME: implement - - case BITNOT: t.push(new Long(~JS.toLong(t.pop()))); break; - case BANG: t.push(new Boolean(!JS.toBoolean(t.pop()))); break; - - case NEWFUNCTION: { - ByteCodeBlock bytes = (ByteCodeBlock)arg[i]; - t.push(new JS.Function(bytes.line, bytes.sourceName, bytes, s)); - break; - } - - case PUSHKEYS: { - Object o = t.peek(); - Object[] keys = ((JS)o).keys(); - JS.Array a = new JS.Array(); - a.setSize(keys.length); - for(int j=0; j 0) { - Object o = t.pop(); - if (o != null && o instanceof Context.LoopMarker) { - int loopInstructionLocation = ((Context.LoopMarker)o).location; - int endOfLoop = ((Integer)arg[loopInstructionLocation]).intValue() + loopInstructionLocation; - s = (JS.Scope)t.pop(); - if (op[i] == CONTINUE) { t.push(s); t.push(o); t.push(Boolean.FALSE); } - i = op[i] == BREAK ? endOfLoop - 1 : loopInstructionLocation; - continue OUTER; - } - } - throw new Error("CONTINUE/BREAK invoked but couldn't find a LoopMarker at " + sourceName + ":" + line); - - /* - ByteCodeBlock loop = (ByteCodeBlock)arg[i]; - Vec stack2 = new Vec(); - Context cur = Context.getContextForCurrentThread(); - boolean first = true; - try { - while (true) { - Boolean result; - try { - Context cx = new Context(); - cx.bindToCurrentThread(); - cx.stack.push(new Boolean(first)); - loop.eval(new JS.Scope(s)); - result = (Boolean)cx.stack.pop(); - } catch (ContinueException c) { result = Boolean.TRUE; - } catch (BreakException b) { result = Boolean.FALSE; - } - first = false; - if (result == Boolean.FALSE) break; - } - } finally { - cur.bindToCurrentThread(); - } - break; - } - */ - - case PUT: { - Object val = t.pop(); - Object key = t.pop(); - Object target = t.peek(); - if (target == null) - throw new JS.Exn(sourceName + ":" + line + ": tried to put a value to the " + key + - " property on the null value"); - if (!(target instanceof JS)) - throw new JS.Exn(sourceName + ":" + line + ": tried to put a value to the " + key + - " property on a " + target.getClass().getName()); - ((JS)target).put(key, val); - t.push(val); - break; - } - - case GET: { - Object v = t.pop(); - Object o = t.pop(); - t.push(doGet(o, v)); - break; - } - - case GET_PRESERVE: { - Object v = t.pop(); - Object o = t.peek(); - t.push(v); - t.push(doGet(o, v)); - break; - } - - case CALL: { - JS.Array arguments = new JS.Array(); - int numArgs = JS.toNumber(arg[i]).intValue(); - arguments.setSize(numArgs); - for(int j=numArgs - 1; j >= 0; j--) arguments.setElementAt(t.pop(), j); - JS.Function f = (JS.Function)t.pop(); - if (f == null) throw new JS.Exn(sourceName + ":" + line + ": attempted to call null"); - t.push(f.call(arguments)); - break; - } - - case INC: case DEC: { - boolean isPrefix = JS.toBoolean(arg[i]); - Object key = t.pop(); - JS obj = (JS)t.pop(); - Number num = JS.toNumber(obj.get(key)); - Number val = new Double(op[i] == INC ? num.doubleValue() + 1.0 : num.doubleValue() - 1.0); - obj.put(key, val); - t.push(isPrefix ? val : num); - break; - } - - default: { - Object right = t.pop(); - Object left = t.pop(); - switch(op[i]) { - - case ADD: { - Object l = left; - Object r = right; - if (l instanceof String || r instanceof String) { - if (l == null) l = "null"; - if (r == null) r = "null"; - if (l instanceof Number && ((Number)l).doubleValue() == ((Number)l).longValue()) - l = new Long(((Number)l).longValue()); - if (r instanceof Number && ((Number)r).doubleValue() == ((Number)r).longValue()) - r = new Long(((Number)r).longValue()); - t.push(l.toString() + r.toString()); break; - } - t.push(new Double(JS.toDouble(l) + JS.toDouble(r))); break; - } - - case BITOR: t.push(new Long(JS.toLong(left) | JS.toLong(right))); break; - case BITXOR: t.push(new Long(JS.toLong(left) ^ JS.toLong(right))); break; - case BITAND: t.push(new Long(JS.toLong(left) & JS.toLong(right))); break; - - case SUB: t.push(new Double(JS.toDouble(left) - JS.toDouble(right))); break; - case MUL: t.push(new Double(JS.toDouble(left) * JS.toDouble(right))); break; - case DIV: t.push(new Double(JS.toDouble(left) / JS.toDouble(right))); break; - case MOD: t.push(new Double(JS.toDouble(left) % JS.toDouble(right))); break; - - case LSH: t.push(new Long(JS.toLong(left) << JS.toLong(right))); break; - case RSH: t.push(new Long(JS.toLong(left) >> JS.toLong(right))); break; - case URSH: t.push(new Long(JS.toLong(left) >>> JS.toLong(right))); break; - - case LT: t.push(JS.toDouble(left) < JS.toDouble(right) ? Boolean.TRUE : Boolean.FALSE); break; - case LE: t.push(JS.toDouble(left) <= JS.toDouble(right) ? Boolean.TRUE : Boolean.FALSE); break; - case GT: t.push(JS.toDouble(left) > JS.toDouble(right) ? Boolean.TRUE : Boolean.FALSE); break; - case GE: t.push(JS.toDouble(left) >= JS.toDouble(right) ? Boolean.TRUE : Boolean.FALSE); break; - - case EQ: - case NE: { - Object l = left; - Object r = right; - boolean ret; - if (l == null) { Object tmp = r; r = l; l = tmp; } - if (l == null && r == null) ret = true; - else if (l instanceof Boolean) ret = new Boolean(JS.toBoolean(r)).equals(l); - else if (l instanceof Number) ret = JS.toNumber(r).doubleValue() == JS.toNumber(l).doubleValue(); - else if (l instanceof String) ret = r != null && l.equals(r.toString()); - else ret = l.equals(r); - t.push(new Boolean(op[i] == EQ ? ret : !ret)); break; - } - - default: throw new Error("unknown opcode " + op[i]); - } } - } - } - } - - public Object doGet(final Object o, final Object v) { - if (o == null) - throw new JS.Exn(sourceName + ":" + line + ": tried to get property \"" + v + "\" from the null value"); - if (o instanceof String) { - if (v.equals("length")) return new Integer(((String)o).length()); - else if (v.equals("substring")) return new JS.Function(-1, "java", null, null) { - public Object _call(JS.Array args) { - if (args.length() == 1) return ((String)o).substring(JS.toNumber(args.elementAt(0)).intValue()); - else if (args.length() == 2) return ((String)o).substring(JS.toNumber(args.elementAt(0)).intValue(), - JS.toNumber(args.elementAt(1)).intValue()); - else throw new Error("String.substring() can only take one or two arguments"); - } - }; - else if (v.equals("toLowerCase")) return new JS.Function(-1, "java", null, null) { - public Object _call(JS.Array args) { - return ((String)o).toLowerCase(); - } }; - else if (v.equals("toUpperCase")) return new JS.Function(-1, "java", null, null) { - public Object _call(JS.Array args) { - return ((String)o).toString().toUpperCase(); - } }; - else if (v.equals("charAt")) return new JS.Function(-1, "java", null, null) { - public Object _call(JS.Array args) { - return ((String)o).charAt(JS.toNumber(args.elementAt(0)).intValue()) + ""; - } }; - else if (v.equals("lastIndexOf")) return new JS.Function(-1, "java", null, null) { - public Object _call(JS.Array args) { - if (args.length() != 1) return null; - return new Integer(((String)o).lastIndexOf(args.elementAt(0).toString())); - } }; - else if (v.equals("indexOf")) return new JS.Function(-1, "java", null, null) { - public Object _call(JS.Array args) { - if (args.length() != 1) return null; - return new Integer(((String)o).indexOf(args.elementAt(0).toString())); - } }; - throw new Error("Not Implemented: propery " + v + " on String objects"); - } else if (o instanceof Boolean) { - throw new Error("Not Implemented: properties on Boolean objects"); - } else if (o instanceof Number) { - Log.log(this, "Not Implemented: properties on Number objects"); - return null; - //throw new Error("Not Implemented: properties on Number objects"); - } else if (o instanceof JS) { - return ((JS)o).get(v); - } - return null; - } - - static class EvaluatorException extends RuntimeException { - public EvaluatorException(int line, String sourceName, String s) { - super(sourceName + ":" + line + " " + s); - } - } - - static abstract class ControlTransferException extends RuntimeException { } - static class BreakException extends ControlTransferException { - public String label; - BreakException(String label) { this.label = label; } - } - static class ContinueException extends ControlTransferException { - public String label; - ContinueException(String label) { this.label = label; } - } - public static class ReturnException extends ControlTransferException { - public Object retval; - ReturnException(Object retval) { this.retval = retval; } - } - -} diff --git a/src/org/xwt/js/ByteCodes.java b/src/org/xwt/js/ByteCodes.java index 1089f64..1c73e9f 100644 --- a/src/org/xwt/js/ByteCodes.java +++ b/src/org/xwt/js/ByteCodes.java @@ -58,16 +58,16 @@ interface ByteCodes { public static final byte SWAP = -17; /** execute the ForthBlock pointed to by the literal in a fresh scope with parentScope==THIS */ - public static final byte PUSHSCOPE = -18; + public static final byte NEWSCOPE = -18; /** execute the ForthBlock pointed to by the literal in a fresh scope with parentScope==THIS */ - public static final byte POPSCOPE = -19; + public static final byte OLDSCOPE = -19; /** push a copy of the top stack element */ public static final byte DUP = -20; - /** declare a label */ - public static final byte LABEL = -21; + /** a NOP; confers a label upon the following instruction */ + public static final byte LABEL = -21; /** execute the ForthBlock pointed to by the literal until BREAK encountered; push TRUE onto the stack for the first iteration * and FALSE for all subsequent iterations */ diff --git a/src/org/xwt/js/CompiledFunction.java b/src/org/xwt/js/CompiledFunction.java new file mode 100644 index 0000000..76f2bf9 --- /dev/null +++ b/src/org/xwt/js/CompiledFunction.java @@ -0,0 +1,415 @@ +// Copyright 2003 Adam Megacz, see the COPYING file for licensing [GPL] +package org.xwt.js; + +import org.xwt.util.*; +import java.io.*; + +/** a JavaScript function, compiled into bytecode */ +public final class CompiledFunction extends JS.Callable implements ByteCodes, Tokens { + + // Fields and Accessors /////////////////////////////////////////////// + + /** the source code file that this block was drawn from */ + private String sourceName; + public String getSourceName() throws JS.Exn { return sourceName; } + + /** the first line of this function */ + private int firstLine; + public int getFirstLine() throws JS.Exn { return firstLine; } + + /** the line numbers */ + private int[] line = new int[10]; + + /** the instructions */ + private int[] op = new int[10]; + + /** the arguments to the instructions */ + private Object[] arg = new Object[10]; + + /** the number of instruction/argument pairs */ + private int size = 0; + int size() { return size; } + + /** the scope in which this function was declared */ + private JS.Scope parentScope; + JS.Scope getParentScope() { return parentScope; } + + + // Constructors //////////////////////////////////////////////////////// + + public CompiledFunction cloneWithNewParentScope(JS.Scope s) throws IOException { + CompiledFunction ret = new CompiledFunction(sourceName, firstLine, s); + ret.paste(this); + return ret; + } + + CompiledFunction(String sourceName, int firstLine, JS.Scope parentScope) throws IOException { + this.sourceName = sourceName; + this.firstLine = firstLine; + this.parentScope = parentScope; + } + + CompiledFunction(String sourceName, int firstLine, Reader sourceCode, JS.Scope parentScope) throws IOException { + this(sourceName, firstLine, parentScope); + Parser p = new Parser(sourceCode, sourceName, firstLine); + try { + while(true) { + int s = size(); + p.parseStatement(false, this); + if (s == size()) break; + } + add(-1, Tokens.RETURN); + } catch (Exception e) { + if (Log.on) Log.log(Parser.class, e); + } + } + + public Object call(Array args) throws JS.Exn { + CompiledFunction saved = (CompiledFunction)Context.currentFunction.get(Thread.currentThread()); + try { + Context.currentFunction.put(Thread.currentThread(), this); + Context cx = Context.getContextForThread(Thread.currentThread()); + int size = cx.stack.size(); + cx.stack.push(new Context.CallMarker()); + cx.stack.push(args); + + // FIXME, ugly + eval(args == null ? parentScope : new FunctionScope(sourceName, parentScope)); + + Object ret = cx.stack.pop(); + if (cx.stack.size() > size) + Log.log(this, "warning, stack grew by " + (cx.stack.size() - size) + + " elements during call to " + Context.getCurrentSourceNameAndLine()); + return ret; + } finally { + if (saved == null) Context.currentFunction.remove(Thread.currentThread()); + else Context.currentFunction.put(Thread.currentThread(), saved); + } + } + + + // Adding and Altering Bytecodes /////////////////////////////////////////////////// + + void set(int pos, int op_, Object arg_) { op[pos] = op_; arg[pos] = arg_; } + void set(int pos, Object arg_) { arg[pos] = arg_; } + void paste(CompiledFunction other) { for(int i=0; i 0 && op[pc - 1] == LABEL ? (String)arg[pc - 1] : (String)null)); + t.push(Boolean.TRUE); + break; + } + + case BREAK: + case CONTINUE: + while(t.size() > 0) { + Object o = t.pop(); + if (o instanceof Context.CallMarker) ee("break or continue not within a loop"); + if (o != null && o instanceof Context.LoopMarker) { + if (arg[pc] == null || arg[pc].equals(((Context.LoopMarker)o).label)) { + int loopInstructionLocation = ((Context.LoopMarker)o).location; + int endOfLoop = ((Integer)arg[loopInstructionLocation]).intValue() + loopInstructionLocation; + s = (JS.Scope)t.pop(); + if (op[pc] == CONTINUE) { t.push(s); t.push(o); t.push(Boolean.FALSE); } + pc = op[pc] == BREAK ? endOfLoop - 1 : loopInstructionLocation; + continue OUTER; + } + } + } + throw new Error("CONTINUE/BREAK invoked but couldn't find a LoopMarker at " + sourceName + ":" + line); + + case TRY: { + t.push(new Context.TryMarker(pc + ((Integer)arg[pc]).intValue())); + break; + } + + case RETURN: { + Object retval = t.pop(); + while(t.size() > 0) { + Object o = t.pop(); + if (o != null && o instanceof Context.CallMarker) { + t.push(retval); + return; + } + } + throw new Error("error: RETURN invoked but couldn't find a CallMarker!"); + } + + case PUT: { + Object val = t.pop(); + Object key = t.pop(); + Object target = t.peek(); + if (target == null) + throw je("tried to put a value to the " + key + " property on the null value"); + if (!(target instanceof JS)) + throw je("tried to put a value to the " + key + " property on a " + target.getClass().getName()); + ((JS)target).put(key, val); + t.push(val); + break; + } + + case GET: + case GET_PRESERVE: { + Object o, v; + if (op[pc] == GET) { + v = t.pop(); + o = t.pop(); + } else { + v = t.pop(); + o = t.peek(); + t.push(v); + } + Object ret = null; + if (o == null) throw je("tried to get property \"" + v + "\" from the null value"); + if (o instanceof String) { + ret = getFromString((String)o, v); + } else if (o instanceof Boolean) { + throw je("Not Implemented: properties on Boolean objects"); + } else if (o instanceof Number) { + Log.log(this, "Not Implemented: properties on Number objects"); + } else if (o instanceof JS) { + ret = ((JS)o).get(v); + } + t.push(ret); + break; + } + + case CALL: { + Array arguments = new Array(); + int numArgs = JS.toNumber(arg[pc]).intValue(); + arguments.setSize(numArgs); + for(int j=numArgs - 1; j >= 0; j--) arguments.setElementAt(t.pop(), j); + // FIXME: we really need to distinguish between compiled functions and callables + JS.Callable f = (JS.Callable)t.pop(); + if (f == null) throw je("attempted to call null"); + try { + t.push(f.call(arguments)); + break; + } catch (JS.Exn e) { + t.push(e); + } + } + // fall through if exception was thrown + case THROW: { + Object exn = t.pop(); + while(t.size() > 0) { + Object o = t.pop(); + if (o instanceof Context.TryMarker) { + t.push(exn); + pc = ((Context.TryMarker)o).location - 1; + continue OUTER; + } + } + throw new JS.Exn(exn); + } + + case INC: case DEC: { + boolean isPrefix = JS.toBoolean(arg[pc]); + Object key = t.pop(); + JS obj = (JS)t.pop(); + Number num = JS.toNumber(obj.get(key)); + Number val = new Double(op[pc] == INC ? num.doubleValue() + 1.0 : num.doubleValue() - 1.0); + obj.put(key, val); + t.push(isPrefix ? val : num); + break; + } + + default: { + Object right = t.pop(); + Object left = t.pop(); + switch(op[pc]) { + + case ADD: { + Object l = left; + Object r = right; + if (l instanceof String || r instanceof String) { + if (l == null) l = "null"; + if (r == null) r = "null"; + if (l instanceof Number && ((Number)l).doubleValue() == ((Number)l).longValue()) + l = new Long(((Number)l).longValue()); + if (r instanceof Number && ((Number)r).doubleValue() == ((Number)r).longValue()) + r = new Long(((Number)r).longValue()); + t.push(l.toString() + r.toString()); break; + } + t.push(new Double(JS.toDouble(l) + JS.toDouble(r))); break; + } + + case BITOR: t.push(new Long(JS.toLong(left) | JS.toLong(right))); break; + case BITXOR: t.push(new Long(JS.toLong(left) ^ JS.toLong(right))); break; + case BITAND: t.push(new Long(JS.toLong(left) & JS.toLong(right))); break; + + case SUB: t.push(new Double(JS.toDouble(left) - JS.toDouble(right))); break; + case MUL: t.push(new Double(JS.toDouble(left) * JS.toDouble(right))); break; + case DIV: t.push(new Double(JS.toDouble(left) / JS.toDouble(right))); break; + case MOD: t.push(new Double(JS.toDouble(left) % JS.toDouble(right))); break; + + case LSH: t.push(new Long(JS.toLong(left) << JS.toLong(right))); break; + case RSH: t.push(new Long(JS.toLong(left) >> JS.toLong(right))); break; + case URSH: t.push(new Long(JS.toLong(left) >>> JS.toLong(right))); break; + + case LT: case LE: case GT: case GE: { + if (left == null) left = new Integer(0); + if (right == null) right = new Integer(0); + int result = 0; + if (left instanceof String || right instanceof String) { + result = left.toString().compareTo(right.toString()); + } else { + result = (int)java.lang.Math.ceil(JS.toDouble(left) - JS.toDouble(right)); + } + t.push(new Boolean((op[pc] == LT && result < 0) || (op[pc] == LE && result <= 0) || + (op[pc] == GT && result > 0) || (op[pc] == GE && result >= 0))); + break; + } + + case EQ: + case NE: { + Object l = left; + Object r = right; + boolean ret; + if (l == null) { Object tmp = r; r = l; l = tmp; } + if (l == null && r == null) ret = true; + else if (l instanceof Boolean) ret = new Boolean(JS.toBoolean(r)).equals(l); + else if (l instanceof Number) ret = JS.toNumber(r).doubleValue() == JS.toNumber(l).doubleValue(); + else if (l instanceof String) ret = r != null && l.equals(r.toString()); + else ret = l.equals(r); + t.push(new Boolean(op[pc] == EQ ? ret : !ret)); break; + } + + default: throw new Error("unknown opcode " + op[pc]); + } } + } + } + } + + + // Helpers for Number, String, and Boolean //////////////////////////////////////// + + private Object getFromString(final String o, final Object v) { + if (v.equals("length")) return new Integer(((String)o).length()); + else if (v.equals("substring")) return new JS.Callable() { + public Object call(Array args) { + if (args.length() == 1) return ((String)o).substring(JS.toNumber(args.elementAt(0)).intValue()); + else if (args.length() == 2) return ((String)o).substring(JS.toNumber(args.elementAt(0)).intValue(), + JS.toNumber(args.elementAt(1)).intValue()); + else throw je("String.substring() can only take one or two arguments"); + } + }; + else if (v.equals("toLowerCase")) return new JS.Callable() { + public Object call(Array args) { + return ((String)o).toLowerCase(); + } }; + else if (v.equals("toUpperCase")) return new JS.Callable() { + public Object call(Array args) { + return ((String)o).toString().toUpperCase(); + } }; + else if (v.equals("charAt")) return new JS.Callable() { + public Object call(Array args) { + return ((String)o).charAt(JS.toNumber(args.elementAt(0)).intValue()) + ""; + } }; + else if (v.equals("lastIndexOf")) return new JS.Callable() { + public Object call(Array args) { + if (args.length() != 1) return null; + return new Integer(((String)o).lastIndexOf(args.elementAt(0).toString())); + } }; + else if (v.equals("indexOf")) return new JS.Callable() { + public Object call(Array args) { + if (args.length() != 1) return null; + return new Integer(((String)o).indexOf(args.elementAt(0).toString())); + } }; + throw je("Not Implemented: propery " + v + " on String objects"); + } + + + // Exception Stuff //////////////////////////////////////////////////////////////// + + static class EvaluatorException extends RuntimeException { public EvaluatorException(String s) { super(s); } } + EvaluatorException ee(String s) { throw new EvaluatorException(sourceName + ":" + line[pc] + " " + s); } + JS.Exn je(String s) { throw new JS.Exn(sourceName + ":" + line[pc] + " " + s); } + + + // FunctionScope ///////////////////////////////////////////////////////////////// + + private class FunctionScope extends JS.Scope { + String sourceName; + public FunctionScope(String sourceName, Scope parentScope) { super(parentScope); this.sourceName = sourceName; } + public String getSourceName() { return sourceName; } + public Object get(Object key) throws JS.Exn { + if (key.equals("trapee")) return org.xwt.Trap.currentTrapee(); + else if (key.equals("cascade")) return org.xwt.Trap.cascadeFunction; + return super.get(key); + } + } +} diff --git a/src/org/xwt/js/Context.java b/src/org/xwt/js/Context.java index 4fc5eaa..ee00fc8 100644 --- a/src/org/xwt/js/Context.java +++ b/src/org/xwt/js/Context.java @@ -3,18 +3,38 @@ package org.xwt.js; import org.xwt.util.*; import java.io.*; +import java.util.*; /** encapsulates a single JavaScript thread and its state */ public class Context { - private static Hash javaThreadToContextMap = new Hash(); + private static Hashtable javaThreadToContextMap = new Hashtable(); + static Hashtable currentFunction = new Hashtable(); public Vec stack = new Vec(); - /** at any point in time, one JS Context can be bound to each Java thread; this determines which context any call()s execute in */ + /** + * at any point in time, one JS Context can be bound to each Java thread; + * this determines which context any call()s execute in + */ public void bindToCurrentThread() { javaThreadToContextMap.put(Thread.currentThread(), this); } - public static Context getContextForCurrentThread() { - Context ret = (Context)javaThreadToContextMap.get(Thread.currentThread()); + + /** returns the current file and line number; intended for displaying to the user */ + public static String getCurrentSourceNameAndLine() { + return getContextForThread(Thread.currentThread()).getCurrentFunction().getSourceName() + ":??"; + } + + public static String getSourceNameAndLineForThread(Thread t) { + CompiledFunction cf = getContextForThread(t).getCurrentFunction(); + if (cf == null) return "null"; + return cf.getSourceName() + ":??"; + } + + /** fetches the currently-executing javascript function */ + public CompiledFunction getCurrentFunction() { return (CompiledFunction)currentFunction.get(Thread.currentThread()); } + + public static Context getContextForThread(Thread t) { + Context ret = (Context)javaThreadToContextMap.get(t); if (ret == null) { ret = new Context(); ret.bindToCurrentThread(); @@ -22,7 +42,15 @@ public class Context { return ret; } + public static class TryMarker { public int location; public TryMarker(int location) { this.location = location; } } public static class CallMarker { public CallMarker() { } } - public static class LoopMarker { public int location; public LoopMarker(int location) { this.location = location; } } + public static class LoopMarker { + public int location; + public String label; + public LoopMarker(int location, String label) { + this.location = location; + this.label = label; + } + } } diff --git a/src/org/xwt/js/Date.java b/src/org/xwt/js/Date.java new file mode 100644 index 0000000..a3d4b36 --- /dev/null +++ b/src/org/xwt/js/Date.java @@ -0,0 +1,11 @@ +// Copyright 2003 Adam Megacz, see the COPYING file for licensing [GPL] + +package org.xwt.js; +import org.xwt.util.*; +import java.io.*; +import java.util.*; + +/** a JavaScript Date object */ +public class Date extends JS.Obj { + +} diff --git a/src/org/xwt/js/JS.java b/src/org/xwt/js/JS.java index 11faeaa..85189b4 100644 --- a/src/org/xwt/js/JS.java +++ b/src/org/xwt/js/JS.java @@ -12,18 +12,7 @@ import java.util.*; */ public abstract class JS { - - // Static Methods ////////////////////////////////////////////////////////////////////// - - private static Hashtable currentFunction = new Hashtable(); - public static Function getCurrentFunction() { return (Function)currentFunction.get(Thread.currentThread()); } - public static String getCurrentFunctionSourceName() { return getCurrentFunctionSourceName(Thread.currentThread()); } - public static String getFileAndLine() { return getCurrentFunctionSourceName() + ":" + getCurrentFunction().getLine(); } - public static String getCurrentFunctionSourceName(Thread t) { - Function f = (Function)currentFunction.get(t); - if (f == null) return "null"; - return f.getSourceName(); - } + // Public Helper Methods ////////////////////////////////////////////////////////////////////// public static boolean toBoolean(Object o) { if (o == null) return false; @@ -78,127 +67,6 @@ public abstract class JS { public Object getObject() { return js; } } - /** A JavaScript Array */ - public static class Array extends Obj { - private Vec vec = new Vec(); - public Array() { } - public Array(int size) { vec.setSize(size); } - private static int intVal(Object o) { - if (o instanceof Number) { - int intVal = ((Number)o).intValue(); - if (intVal == ((Number)o).doubleValue()) return intVal; - return Integer.MIN_VALUE; - } - if (!(o instanceof String)) return Integer.MIN_VALUE; - String s = (String)o; - for(int i=0; i '9') return Integer.MIN_VALUE; - return Integer.parseInt(s); - } - public Object get(Object key) throws JS.Exn { - // FIXME: HACK! - if (key.equals("cascade")) return org.xwt.Trap.cascadeFunction; - if (key.equals("trapee")) return org.xwt.Trap.currentTrapee(); - if (key.equals("length")) return new Long(vec.size()); - int i = intVal(key); - if (i == Integer.MIN_VALUE) return super.get(key); - try { - return vec.elementAt(i); - } catch (ArrayIndexOutOfBoundsException e) { - return null; - } - } - public void put(Object key, Object val) { - if (key.equals("length")) vec.setSize(toNumber(val).intValue()); - int i = intVal(key); - if (i == Integer.MIN_VALUE) super.put(key, val); - else { - if (i >= vec.size()) vec.setSize(i+1); - vec.setElementAt(val, i); - } - } - public Object[] keys() { - Object[] sup = super.keys(); - Object[] ret = new Object[vec.size() + 1 + sup.length]; - System.arraycopy(sup, 0, ret, vec.size(), sup.length); - for(int i=0; i size) { - Log.log(this, "warning, stack grew by " + (cx.stack.size() - size) + - " elements during call to " + getSourceName() + ":" + getLine()); - Log.log(this, "top element is " + cx.stack.peek()); - } - return ret; - } - public final Object call(JS.Array args) throws JS.Exn { - Function saved = (Function)currentFunction.get(Thread.currentThread()); - if (!getSourceName().equals("java")) currentFunction.put(Thread.currentThread(), this); - try { - return _call(args); - } catch (ByteCodeBlock.ReturnException e) { // ignore - return e.retval; - } catch (ByteCodeBlock.ControlTransferException e) { - throw new RuntimeException(getSourceName() + ":" + getLine() + - " error, ControlTransferException tried to leave a function"); - } finally { - if (saved == null) currentFunction.remove(Thread.currentThread()); - else currentFunction.put(Thread.currentThread(), saved); - } - } - } - - public static Function parse(Reader r, String sourceName, int line) throws IOException { - ByteCodeBlock b = new ByteCodeBlock(line, sourceName); - Parser p = new Parser(r, sourceName, line); - try { - while(true) { - int size = b.size(); - p.parseStatement(false, b); - if (size == b.size()) break; - } - b.add(Tokens.RETURN); - return new Function(line, sourceName, b, null); - } catch (Exception e) { - if (Log.on) Log.log(Parser.class, e); - return null; - } - } - /** Any object which becomes part of the scope chain must support this interface */ public static class Scope extends Obj { private Scope parentScope; @@ -225,17 +93,16 @@ public abstract class JS { else super.put(s, NULL); } } + + public static CompiledFunction parse(String sourceName, int firstLine, Reader sourceCode, JS.Scope parentScope) throws IOException { + return new CompiledFunction(sourceName, firstLine, sourceCode, parentScope); + } - private class FunctionScope extends JS.Scope { - String sourceName; - public FunctionScope(String sourceName, Scope parentScope) { super(parentScope); this.sourceName = sourceName; } - public String getSourceName() { return sourceName; } - public Object get(Object key) throws JS.Exn { - if (key.equals("trapee")) return org.xwt.Trap.currentTrapee(); - else if (key.equals("cascade")) return org.xwt.Trap.cascadeFunction; - return super.get(key); - } + /** anything that is callable with the () operator */ + public static abstract class Callable extends JS.Obj { + public abstract Object call(Array args) throws JS.Exn; } + } diff --git a/src/org/xwt/js/Math.java b/src/org/xwt/js/Math.java new file mode 100644 index 0000000..4f8319a --- /dev/null +++ b/src/org/xwt/js/Math.java @@ -0,0 +1,50 @@ +// Copyright 2003 Adam Megacz, see the COPYING file for licensing [GPL] + +package org.xwt.js; +import org.xwt.util.*; +import java.io.*; +import java.util.*; + +/** The JavaScript Math object */ +public class Math extends JS.Obj { + public static Math singleton = new Math(); + private Math() { setSeal(true); } + + public Object get(Object name) { + if ("ceil".equals(name)) return new JS.Callable() { public Object call(Array args) + { if (args.elementAt(0) == null) return null; + return new Long((long)java.lang.Math.ceil(Double.parseDouble(args.elementAt(0).toString()))); } }; + else if ("floor".equals(name)) return new JS.Callable() { public Object call(Array args) + { if (args.elementAt(0) == null) return null; + return new Long((long)java.lang.Math.floor(Double.parseDouble(args.elementAt(0).toString()))); } }; + else if ("round".equals(name)) return new JS.Callable() { public Object call(Array args) + { if (args.elementAt(0) == null) return null; + return new Long((long)java.lang.Math.round(Double.parseDouble(args.elementAt(0).toString()))); } }; + else if ("abs".equals(name)) return new JS.Callable() { public Object call(Array args) + { if (args.elementAt(0) == null) return null; + return new Long((long)java.lang.Math.abs(Double.parseDouble(args.elementAt(0).toString()))); } }; + else if ("min".equals(name)) return new JS.Callable() { public Object call(Array args) { + if (args.length() < 2 || args.elementAt(0) == null || args.elementAt(1) == null) return args.elementAt(0); + return new Double(java.lang.Math.min(((Number)args.elementAt(0)).doubleValue(), + ((Number)args.elementAt(1)).doubleValue())); } }; + else if ("max".equals(name)) return new JS.Callable() { public Object call(Array args) { + if (args.length() < 2) return args.elementAt(0); + return new Double(java.lang.Math.max(((Number)args.elementAt(0)).doubleValue(), + ((Number)args.elementAt(1)).doubleValue())); } }; + else if ("cos".equals(name)) return new JS.Callable() { public Object call(Array args) { + return new Double(java.lang.Math.cos(((Number)args.elementAt(0)).doubleValue())); } }; + else if ("sin".equals(name)) return new JS.Callable() { public Object call(Array args) { + return new Double(java.lang.Math.sin(((Number)args.elementAt(0)).doubleValue())); } }; + else if ("tan".equals(name)) return new JS.Callable() { public Object call(Array args) { + return new Double(java.lang.Math.tan(((Number)args.elementAt(0)).doubleValue())); } }; + else if ("acos".equals(name)) return new JS.Callable() { public Object call(Array args) { + return new Double(java.lang.Math.acos(((Number)args.elementAt(0)).doubleValue())); } }; + else if ("asin".equals(name)) return new JS.Callable() { public Object call(Array args) { + return new Double(java.lang.Math.asin(((Number)args.elementAt(0)).doubleValue())); } }; + else if ("atan".equals(name)) return new JS.Callable() { public Object call(Array args) { + return new Double(java.lang.Math.atan(((Number)args.elementAt(0)).doubleValue())); } }; + else if ("sqrt".equals(name)) return new JS.Callable() { public Object call(Array args) { + return new Double(java.lang.Math.sqrt(((Number)args.elementAt(0)).doubleValue())); } }; + return null; + } +} diff --git a/src/org/xwt/js/Parser.java b/src/org/xwt/js/Parser.java index d4a2753..9aed73a 100644 --- a/src/org/xwt/js/Parser.java +++ b/src/org/xwt/js/Parser.java @@ -4,7 +4,7 @@ package org.xwt.js; import org.xwt.util.*; import java.io.*; -/** Parses a stream of lexed tokens into a tree of ByteCodeBlock's */ +/** Parses a stream of lexed tokens into a tree of CompiledFunction's */ class Parser extends Lexer implements ByteCodes { @@ -14,14 +14,9 @@ class Parser extends Lexer implements ByteCodes { /** for debugging */ public static void main(String[] s) throws Exception { - Parser p = new Parser(new InputStreamReader(System.in), "stdin", 0); - while(true) { - ByteCodeBlock block = new ByteCodeBlock(0, "stdin"); - p.parseStatement(false, block); - if (block == null) return; - System.out.println(block); - if (p.peekToken() == -1) return; - } + CompiledFunction block = new CompiledFunction("stdin", 0, new InputStreamReader(System.in), null); + if (block == null) return; + System.out.println(block); } @@ -56,7 +51,8 @@ class Parser extends Lexer implements ByteCodes { // Parsing Logic ///////////////////////////////////////////////////////// - private ByteCodeBlock newbb(int line) { return new ByteCodeBlock(line, sourceName); } + /** the label for the current statement */ + private String label = null; /** gets a token and throws an exception if it is not code */ public void consume(int code) throws IOException { @@ -65,47 +61,45 @@ class Parser extends Lexer implements ByteCodes { } /** append the largest expression beginning with prefix containing no operators of precedence below minPrecedence */ - public void startExpr(ByteCodeBlock block) throws IOException { startExpr(-1, block); } + public void startExpr(CompiledFunction block) throws IOException { startExpr(-1, block); } /* - public ByteCodeBlock startExpr(int minPrecedence) throws IOException { - ByteCodeBlock ret = new ByteCodeBlock(line, sourceName); + public CompiledFunction startExpr(int minPrecedence) throws IOException { + CompiledFunction ret = new CompiledFunction(line, sourceName); startExpr(minPrecedence, ret); return ret.size() == 0 ? null : ret; } */ - public void startExpr(int minPrecedence, ByteCodeBlock appendTo) throws IOException { + public void startExpr(int minPrecedence, CompiledFunction appendTo) throws IOException { int tok = getToken(); - int curLine = line; - if (tok == -1) return; - - ByteCodeBlock b = appendTo; - + int line = this.line; + CompiledFunction b = appendTo; switch (tok) { - case NUMBER: continueExpr(b.add(ByteCodeBlock.LITERAL, number), minPrecedence); return; - case STRING: continueExpr(b.add(ByteCodeBlock.LITERAL, string), minPrecedence); return; - case THIS: continueExpr(b.add(TOPSCOPE, null), minPrecedence); return; - case NULL: continueExpr(b.add(ByteCodeBlock.LITERAL, null), minPrecedence); return; - case TRUE: case FALSE: continueExpr(b.add(ByteCodeBlock.LITERAL, new Boolean(tok == TRUE)), minPrecedence); return; + case -1: return; + case NUMBER: continueExpr(b.add(line, CompiledFunction.LITERAL, number), minPrecedence); return; + case STRING: continueExpr(b.add(line, CompiledFunction.LITERAL, string), minPrecedence); return; + case THIS: continueExpr(b.add(line, TOPSCOPE, null), minPrecedence); return; + case NULL: continueExpr(b.add(line, CompiledFunction.LITERAL, null), minPrecedence); return; + case TRUE: case FALSE: continueExpr(b.add(line, CompiledFunction.LITERAL, new Boolean(tok == TRUE)), minPrecedence); return; case LB: { - b.add(ARRAY, new Integer(0)); + b.add(line, ARRAY, new Integer(0)); int i = 0; while(true) { int size = b.size(); startExpr(b); if (size == b.size()) if (peekToken() == RB) { consume(RB); continueExpr(b, minPrecedence); return; } - b.add(LITERAL, new Integer(i++)); - if (size == b.size()) b.add(LITERAL, null); - b.add(PUT); - b.add(POP); + b.add(line, LITERAL, new Integer(i++)); + if (size == b.size()) b.add(line, LITERAL, null); + b.add(line, PUT); + b.add(line, POP); if (peekToken() == RB) { consume(RB); continueExpr(b, minPrecedence); return; } consume(COMMA); } } case SUB: { consume(NUMBER); - b.add(ByteCodeBlock.LITERAL, new Double(number.doubleValue() * -1)); + b.add(line, CompiledFunction.LITERAL, new Double(number.doubleValue() * -1)); continueExpr(b, minPrecedence); return; } @@ -124,20 +118,20 @@ class Parser extends Lexer implements ByteCodes { } case BANG: case BITNOT: case TYPEOF: { startExpr(precedence[tok], b); - b.add(tok); + b.add(line, tok); continueExpr(b, minPrecedence); return; } case LC: { - b.add(OBJECT, null); + b.add(line, OBJECT, null); if (peekToken() != RC) while(true) { if (peekToken() != NAME && peekToken() != STRING) throw new Error("expected NAME or STRING"); getToken(); - b.add(LITERAL, string); + b.add(line, LITERAL, string); consume(COLON); startExpr(b); - b.add(PUT); - b.add(POP); + b.add(line, PUT); + b.add(line, POP); if (peekToken() == RC) break; consume(COMMA); if (peekToken() == RC) break; @@ -150,16 +144,16 @@ class Parser extends Lexer implements ByteCodes { String name = string; if (peekToken() == ASSIGN) { consume(ASSIGN); - b.add(TOPSCOPE); - b.add(ByteCodeBlock.LITERAL, name); + b.add(line, TOPSCOPE); + b.add(line, CompiledFunction.LITERAL, name); startExpr(minPrecedence, b); - b.add(ByteCodeBlock.PUT); - b.add(ByteCodeBlock.SWAP); - b.add(ByteCodeBlock.POP); + b.add(line, CompiledFunction.PUT); + b.add(line, CompiledFunction.SWAP); + b.add(line, CompiledFunction.POP); } else { - b.add(TOPSCOPE); - b.add(ByteCodeBlock.LITERAL, name); - b.add(ByteCodeBlock.GET); + b.add(line, TOPSCOPE); + b.add(line, CompiledFunction.LITERAL, name); + b.add(line, CompiledFunction.GET); } continueExpr(b, minPrecedence); return; @@ -167,16 +161,16 @@ class Parser extends Lexer implements ByteCodes { case FUNCTION: { consume(LP); int numArgs = 0; - ByteCodeBlock b2 = newbb(curLine); - b2.add(TOPSCOPE); - b2.add(SWAP); - b2.add(LITERAL, "arguments"); - b2.add(LITERAL, "arguments"); - b2.add(DECLARE); - b2.add(SWAP); - b2.add(PUT); - b2.add(SWAP); - b2.add(POP); + CompiledFunction b2 = new CompiledFunction(sourceName, line, null); + b2.add(line, TOPSCOPE); + b2.add(line, SWAP); + b2.add(line, LITERAL, "arguments"); + b2.add(line, LITERAL, "arguments"); + b2.add(line, DECLARE); + b2.add(line, SWAP); + b2.add(line, PUT); + b2.add(line, SWAP); + b2.add(line, POP); if (peekToken() == RP) consume(RP); else while(true) { if (peekToken() == COMMA) { @@ -186,25 +180,25 @@ class Parser extends Lexer implements ByteCodes { consume(NAME); // declare the name - b2.add(LITERAL, string); - b2.add(DECLARE); + b2.add(line, LITERAL, string); + b2.add(line, DECLARE); // retrieve it from the arguments array - b2.add(LITERAL, new Integer(numArgs)); - b2.add(GET_PRESERVE); - b2.add(SWAP); - b2.add(POP); + b2.add(line, LITERAL, new Integer(numArgs)); + b2.add(line, GET_PRESERVE); + b2.add(line, SWAP); + b2.add(line, POP); // put it to the current scope - b2.add(TOPSCOPE); - b2.add(SWAP); - b2.add(LITERAL, string); - b2.add(SWAP); - b2.add(PUT); + b2.add(line, TOPSCOPE); + b2.add(line, SWAP); + b2.add(line, LITERAL, string); + b2.add(line, SWAP); + b2.add(line, PUT); // clean the stack - b2.add(POP); - b2.add(POP); + b2.add(line, POP); + b2.add(line, POP); if (peekToken() == RP) { consume(RP); break; } consume(COMMA); @@ -212,27 +206,25 @@ class Parser extends Lexer implements ByteCodes { numArgs++; } // pop off the arguments array - b2.add(POP); + b2.add(line, POP); parseStatement(true, b2); - b2.add(LITERAL, null); - b2.add(RETURN); - continueExpr(b.add(NEWFUNCTION, b2), minPrecedence); + b2.add(line, LITERAL, null); + b2.add(line, RETURN); + continueExpr(b.add(line, NEWFUNCTION, b2), minPrecedence); return; } default: pushBackToken(); return; } } - public void continueExpr(ByteCodeBlock prefix, int minPrecedence) throws IOException { + public void continueExpr(CompiledFunction prefix, int minPrecedence) throws IOException { int tok = getToken(); - int curLine = line; if (tok == -1) return; if (minPrecedence > 0 && precedence[tok] != 0) if (precedence[tok] < minPrecedence || (precedence[tok] == minPrecedence && !isRightAssociative[tok])) { pushBackToken(); return; } - if (prefix == null) throw new Error("got null prefix"); - ByteCodeBlock b = prefix; + CompiledFunction b = prefix; switch (tok) { @@ -240,10 +232,10 @@ class Parser extends Lexer implements ByteCodes { case ASSIGN_ADD: case ASSIGN_SUB: case ASSIGN_MUL: case ASSIGN_DIV: case ASSIGN_MOD: { prefix.set(prefix.size() - 1, b.GET_PRESERVE, new Boolean(true)); startExpr(precedence[tok - 1], b); - prefix.add(tok - 1); - prefix.add(PUT); - prefix.add(SWAP); - prefix.add(POP); + prefix.add(line, tok - 1); + prefix.add(line, PUT); + prefix.add(line, SWAP); + prefix.add(line, POP); continueExpr(b, minPrecedence); return; } @@ -265,7 +257,7 @@ class Parser extends Lexer implements ByteCodes { consume(COMMA); } consume(RP); - b.add(CALL, new Integer(i)); + b.add(line, CALL, new Integer(i)); continueExpr(b, minPrecedence); return; } @@ -274,18 +266,18 @@ class Parser extends Lexer implements ByteCodes { case RSH: case URSH: case ADD: case MUL: case DIV: case MOD: case GT: case GE: case EQ: case NE: case LT: case LE: case SUB: { startExpr(precedence[tok], b); - b.add(tok); + b.add(line, tok); continueExpr(b, minPrecedence); return; } case OR: case AND: { - b.add(tok == AND ? b.JF : b.JT, new Integer(0)); + b.add(line, tok == AND ? b.JF : b.JT, new Integer(0)); int size = b.size(); startExpr(precedence[tok], b); b.set(size - 1, new Integer(b.size() - size + 2)); - b.add(JMP, new Integer(2)); - b.add(LITERAL, tok == AND ? new Boolean(false) : new Boolean(true)); + b.add(line, JMP, new Integer(2)); + b.add(line, LITERAL, tok == AND ? new Boolean(false) : new Boolean(true)); continueExpr(b, minPrecedence); return; } @@ -295,14 +287,14 @@ class Parser extends Lexer implements ByteCodes { String target = string; if (peekToken() == ASSIGN) { consume(ASSIGN); - b.add(LITERAL, target); + b.add(line, LITERAL, target); startExpr(b); - b.add(PUT); - b.add(SWAP); - b.add(POP); + b.add(line, PUT); + b.add(line, SWAP); + b.add(line, POP); } else { - b.add(LITERAL, target); - b.add(GET); + b.add(line, LITERAL, target); + b.add(line, GET); } continueExpr(b, minPrecedence); return; @@ -314,22 +306,22 @@ class Parser extends Lexer implements ByteCodes { if (peekToken() == ASSIGN) { consume(ASSIGN); startExpr(b); - b.add(PUT); - b.add(SWAP); - b.add(POP); + b.add(line, PUT); + b.add(line, SWAP); + b.add(line, POP); } else { - b.add(GET); + b.add(line, GET); } continueExpr(b, minPrecedence); return; } case HOOK: { - b.add(JF, new Integer(0)); + b.add(line, JF, new Integer(0)); int size = b.size(); startExpr(b); b.set(size - 1, new Integer(b.size() - size + 2)); - b.add(JMP, new Integer(0)); + b.add(line, JMP, new Integer(0)); consume(COLON); size = b.size(); startExpr(b); @@ -342,67 +334,70 @@ class Parser extends Lexer implements ByteCodes { } } - /** a block is either a single statement or a list of statements surrounded by curly braces; all expressions are also statements */ - - public ByteCodeBlock parseStatement() throws IOException { - ByteCodeBlock ret = new ByteCodeBlock(line, sourceName); - ret.add(ret.LITERAL, null); + /** + * a block is either a single statement or a list of statements surrounded by curly braces; + * all expressions are also statements + */ + public CompiledFunction parseStatement() throws IOException { + CompiledFunction ret = new CompiledFunction(sourceName, line, null); + ret.add(line, ret.LITERAL, null); parseStatement(false, ret); if (ret.size() == 1) return null; return ret; } - public void parseStatement(boolean requireBraces, ByteCodeBlock b) throws IOException { + public void parseStatement(boolean requireBraces, CompiledFunction b) throws IOException { int tok = peekToken(); if (tok == -1) return; + int line = this.line; boolean braced = tok == LC; if (requireBraces && !braced) throw new ParserException("expected {, got " + codeToString[tok]); if (braced) consume(LC); - int curLine = line; while(true) { switch(tok = peekToken()) { case THROW: case RETURN: case ASSERT: { getToken(); - if (tok == RETURN && peekToken() == SEMI) b.add(LITERAL, null); + if (tok == RETURN && peekToken() == SEMI) b.add(line, LITERAL, null); else startExpr(b); consume(SEMI); - b.add(tok); + b.add(line, tok); break; } case BREAK: case CONTINUE: { getToken(); if (peekToken() == NAME) consume(NAME); - b.add(tok, string); + b.add(line, tok, string); consume(SEMI); break; } - case SEMI: + case SEMI: { consume(SEMI); if (!braced) return; break; + } case VAR: { consume(VAR); - b.add(TOPSCOPE); // push the current scope + b.add(line, TOPSCOPE); // push the current scope while(true) { consume(NAME); String name = string; - b.add(LITERAL, name); // push the name to be declared - b.add(DECLARE); // declare it + b.add(line, LITERAL, name); // push the name to be declared + b.add(line, DECLARE); // declare it if (peekToken() == ASSIGN) { // if there is an '=' after the variable name - b.add(LITERAL, name); // put the var name back on the stack + b.add(line, LITERAL, name); // put the var name back on the stack consume(ASSIGN); startExpr(b); - b.add(PUT); - b.add(POP); + b.add(line, PUT); + b.add(line, POP); } if (peekToken() != COMMA) break; consume(COMMA); } - b.add(POP); + b.add(line, POP); if (peekToken() == SEMI) consume(SEMI); break; } @@ -413,14 +408,14 @@ class Parser extends Lexer implements ByteCodes { startExpr(b); consume(RP); - b.add(JF, new Integer(0)); + b.add(line, JF, new Integer(0)); int size = b.size(); parseStatement(false, b); if (peekToken() == ELSE) { consume(ELSE); b.set(size - 1, new Integer(2 + b.size() - size)); - b.add(JMP, new Integer(0)); + b.add(line, JMP, new Integer(0)); size = b.size(); parseStatement(false, b); } @@ -431,15 +426,16 @@ class Parser extends Lexer implements ByteCodes { case WHILE: { consume(WHILE); consume(LP); - b.add(LOOP); + if (label != null) b.add(line, LABEL, label); + b.add(line, LOOP); int size = b.size(); - b.add(POP); + b.add(line, POP); startExpr(b); - b.add(JT, new Integer(2)); - b.add(BREAK); + b.add(line, JT, new Integer(2)); + b.add(line, BREAK); consume(RP); parseStatement(false, b); - b.add(CONTINUE); // if we fall out of the end, definately continue + b.add(line, CONTINUE); // if we fall out of the end, definately continue b.set(size - 1, new Integer(b.size() - size + 1)); // end of the loop break; } @@ -447,7 +443,8 @@ class Parser extends Lexer implements ByteCodes { case SWITCH: { consume(SWITCH); consume(LP); - b.add(LOOP); + if (label != null) b.add(line, LABEL, label); + b.add(line, LOOP); int size0 = b.size(); startExpr(b); consume(RP); @@ -455,11 +452,11 @@ class Parser extends Lexer implements ByteCodes { while(true) if (peekToken() == CASE) { consume(CASE); - b.add(DUP); + b.add(line, DUP); startExpr(b); consume(COLON); - b.add(EQ); - b.add(JF, new Integer(0)); + b.add(line, EQ); + b.add(line, JF, new Integer(0)); int size = b.size(); while(peekToken() != CASE && peekToken() != DEFAULT && peekToken() != RC) { int size2 = b.size(); @@ -477,27 +474,28 @@ class Parser extends Lexer implements ByteCodes { } } else if (peekToken() == RC) { consume(RC); - b.add(BREAK); + b.add(line, BREAK); break; } else { throw new ParserException("expected CASE, DEFAULT, or RC; got " + codeToString[peekToken()]); } - b.add(BREAK); + b.add(line, BREAK); b.set(size0 - 1, new Integer(b.size() - size0 + 1)); // end of the loop break; } case DO: { consume(DO); - b.add(LOOP); + if (label != null) b.add(line, LABEL, label); + b.add(line, LOOP); int size = b.size(); parseStatement(false, b); consume(WHILE); consume(LP); startExpr(b); - b.add(JT, new Integer(2)); - b.add(BREAK); - b.add(CONTINUE); + b.add(line, JT, new Integer(2)); + b.add(line, BREAK); + b.add(line, CONTINUE); consume(RP); consume(SEMI); b.set(size - 1, new Integer(b.size() - size + 1)); // end of the loop @@ -507,16 +505,33 @@ class Parser extends Lexer implements ByteCodes { case TRY: { // We deliberately allow you to omit braces in catch{}/finally{} if they are single statements... consume(TRY); + b.add(line, TRY); + int size = b.size(); parseStatement(true, b); + b.add(line, POP); // pop the TryMarker + b.add(line, JMP); // jump forward to the end of the catch block + int size2 = b.size(); + b.set(size - 1, new Integer(b.size() - size + 1));// the TRY argument points at the start of the CATCH block if (peekToken() == CATCH) { getToken(); consume(LP); consume(NAME); consume(RP); - parseStatement(); // just discard the catchblock + // FIXME, we need an extra scope here + b.add(line, TOPSCOPE); // the exception is on top of the stack; put it to the variable + b.add(line, SWAP); + b.add(line, LITERAL); + b.add(line, SWAP); + b.add(line, PUT); + b.add(line, POP); + b.add(line, POP); + parseStatement(false, b); } + b.set(size2 - 1, new Integer(b.size() - size2 + 1)); // jump here if no exception was thrown + + // FIXME: not implemented correctly if (peekToken() == FINALLY) { consume(FINALLY); parseStatement(false, b); @@ -524,88 +539,92 @@ class Parser extends Lexer implements ByteCodes { break; } - case FOR: { - consume(FOR); - consume(LP); - - tok = getToken(); - boolean hadVar = false; - if (tok == VAR) { hadVar = true; tok = getToken(); } - String varName = string; - boolean forIn = peekToken() == IN; - pushBackToken(tok, varName); - - if (forIn) { - // FIXME: break needs to work in here - consume(NAME); - consume(IN); - startExpr(b); - b.add(PUSHKEYS); - b.add(LITERAL, "length"); - b.add(GET); - consume(RP); - ByteCodeBlock b2 = newbb(curLine); - - b.add(PUSHSCOPE); - - b.add(LITERAL, new Integer(1)); - b.add(SUB); - b.add(DUP); - b.add(LITERAL, new Integer(0)); - b.add(LT); - b.add(JT, new Integer(7)); - b.add(GET_PRESERVE); - b.add(LITERAL, varName); - b.add(LITERAL, varName); - b.add(DECLARE); - b.add(PUT); - parseStatement(false, b); - - b.add(POPSCOPE); - - break; + case FOR: { + consume(FOR); + consume(LP); - } else { - if (hadVar) pushBackToken(VAR, null); - b.add(PUSHSCOPE); - - parseStatement(false, b); - ByteCodeBlock e2 = new ByteCodeBlock(line, sourceName); - startExpr(e2); - consume(SEMI); - if (e2 == null) e2 = newbb(curLine).add(b.LITERAL, Boolean.TRUE); - - b.add(LOOP); - int size2 = b.size(); - - b.add(JT, new Integer(0)); - int size = b.size(); - startExpr(b); - if (b.size() > size) b.add(POP); - b.set(size - 1, new Integer(b.size() - size + 1)); - consume(RP); - - b.paste(e2); - b.add(JT, new Integer(2)); - b.add(BREAK); - parseStatement(false, b); - b.add(CONTINUE); - b.set(size2 - 1, new Integer(b.size() - size2 + 1)); // end of the loop - - b.add(POPSCOPE); - break; + tok = getToken(); + boolean hadVar = false; + if (tok == VAR) { hadVar = true; tok = getToken(); } + String varName = string; + boolean forIn = peekToken() == IN; + pushBackToken(tok, varName); + + if (forIn) { + // FIXME: break needs to work in here + consume(NAME); + consume(IN); + startExpr(b); + b.add(line, PUSHKEYS); + b.add(line, LITERAL, "length"); + b.add(line, GET); + consume(RP); + CompiledFunction b2 = new CompiledFunction(sourceName, line, null); + + b.add(line, NEWSCOPE); + + b.add(line, LITERAL, new Integer(1)); + b.add(line, SUB); + b.add(line, DUP); + b.add(line, LITERAL, new Integer(0)); + b.add(line, LT); + b.add(line, JT, new Integer(7)); + b.add(line, GET_PRESERVE); + b.add(line, LITERAL, varName); + b.add(line, LITERAL, varName); + b.add(line, DECLARE); + b.add(line, PUT); + parseStatement(false, b); + + b.add(line, OLDSCOPE); + + break; + + } else { + if (hadVar) pushBackToken(VAR, null); + b.add(line, NEWSCOPE); + + parseStatement(false, b); + CompiledFunction e2 = new CompiledFunction(sourceName, line, null); + startExpr(e2); + consume(SEMI); + if (e2 == null) e2 = new CompiledFunction(sourceName, line, null).add(line, b.LITERAL, Boolean.TRUE); + + if (label != null) b.add(line, LABEL, label); + b.add(line, LOOP); + int size2 = b.size(); + + b.add(line, JT, new Integer(0)); + int size = b.size(); + startExpr(b); + if (b.size() > size) b.add(line, POP); + b.set(size - 1, new Integer(b.size() - size + 1)); + consume(RP); + + b.paste(e2); + b.add(line, JT, new Integer(2)); + b.add(line, BREAK); + parseStatement(false, b); + b.add(line, CONTINUE); + b.set(size2 - 1, new Integer(b.size() - size2 + 1)); // end of the loop + + b.add(line, OLDSCOPE); + break; + } } - } - + case NAME: { consume(NAME); - String name = string; + String oldlabel = label; + label = string; if (peekToken() == COLON) { consume(COLON); - b.add(ByteCodeBlock.LABEL, string); + parseStatement(false, b); + label = oldlabel; break; } else { - pushBackToken(NAME, name); + pushBackToken(NAME, label); + label = oldlabel; // fall through to default case } } @@ -617,7 +636,7 @@ class Parser extends Lexer implements ByteCodes { int size = b.size(); startExpr(b); if (size == b.size()) return; - b.add(POP); + b.add(line, POP); if (peekToken() == SEMI) consume(SEMI); break; } @@ -627,7 +646,7 @@ class Parser extends Lexer implements ByteCodes { } } - class ParserException extends RuntimeException { + private class ParserException extends RuntimeException { public ParserException(String s) { super(sourceName + ":" + line + " " + s); } }