From: adam Date: Mon, 27 Dec 2004 10:27:50 +0000 (+0000) Subject: [re]-merged in Brians stuff X-Git-Url: http://git.megacz.com/?p=org.ibex.js.git;a=commitdiff_plain;h=19d66e161db458135518efd3539048f44e1e5622 [re]-merged in Brians stuff darcs-hash:20041227102750-5007d-5b154a218c5f7c1def8f15fcce001fa2117255b6.gz --- diff --git a/src/org/ibex/js/ByteCodes.java b/src/org/ibex/js/ByteCodes.java index e8170f1..84c5926 100644 --- a/src/org/ibex/js/ByteCodes.java +++ b/src/org/ibex/js/ByteCodes.java @@ -24,10 +24,11 @@ interface ByteCodes { /** if given a non-null argument declare its argument in the current scope and push it to the stack, else, declares the element on the top of the stack and leaves it there */ - public static final byte DECLARE = -6; + //public static final byte DECLARE = -6; /** push a reference to the current scope onto the stack */ - public static final byte TOPSCOPE = -7; + // FIXME: Document this + public static final byte GLOBALSCOPE = -7; /** if given a null literal pop two elements off the stack; push stack[-1].get(stack[top]) else pop one element off the stack, push stack[top].get(literal) */ @@ -85,10 +86,14 @@ interface ByteCodes { /** finish a finally block and carry out whatever instruction initiated the finally block */ public static final byte MAKE_GRAMMAR = -25; + // FIXME: Document these and NEWSCOPE/OLDSCOPE/TOPSCOPE changes + public static final byte SCOPEGET = -26; + public static final byte SCOPEPUT = -27; + public static final String[] bytecodeToString = new String[] { - "", "", "LITERAL", "ARRAY", "OBJECT", "NEWFUNCTION", "DECLARE", "TOPSCOPE", + "", "", "LITERAL", "ARRAY", "OBJECT", "NEWFUNCTION", "DECLARE", "GLOBALSCOPE", "GET", "GET_PRESERVE", "PUT", "JT", "JF", "JMP", "POP", "CALL", "PUSHKEYS", "SWAP", "NEWSCOPE", "OLDSCOPE", "DUP", "LABEL", "LOOP", "CALLMETHOD", - "FINALLY_DONE", "MAKE_GRAMMAR" + "FINALLY_DONE", "MAKE_GRAMMAR", "SCOPEGET", "SCOPEPUT" }; } diff --git a/src/org/ibex/js/Directory.java b/src/org/ibex/js/Directory.java index 17adab7..98469d5 100644 --- a/src/org/ibex/js/Directory.java +++ b/src/org/ibex/js/Directory.java @@ -43,6 +43,7 @@ public class Directory extends JS { * Create the directory object. Existing directories will be * preserved; if a file is present it will be obliterated. */ + public Directory(File f) throws IOException { this.f = f; if (!f.exists()) new Directory(new File(f.getParent())); @@ -59,38 +60,34 @@ public class Directory extends JS { f.delete(); } - public void put(Object key0, Object val) throws JSExn { + public void put(JS key0, JS val) throws JSExn { try { if (key0 == null) return; String key = toString(key0); File f2 = new File(f.getAbsolutePath() + File.separatorChar + FileNameEncoder.encode(key)); destroy(f2); if (val == null) return; - if (val instanceof JS) { + if (val instanceof JSPrimitive) { + OutputStream out = new FileOutputStream(f2); + Writer w = new OutputStreamWriter(out); + w.write(toString(val)); + w.flush(); + out.close(); + } else { Directory d2 = new Directory(f2); - Enumeration e = ((JS)val).keys(); + JS.Enumeration e = val.keys(); while(e.hasMoreElements()) { - String k = (String)e.nextElement(); - Object v = ((JS)val).get(k); + JS k = e.nextElement(); + JS v = val.get(k); d2.put(k, v); } - } else { - OutputStream out = new FileOutputStream(f2); - try { - Writer w = new OutputStreamWriter(out); - w.write(toString(val)); - w.flush(); - } finally { - out.close(); - } } } catch (IOException ioe) { throw new JSExn.IO(ioe); } } - public Object get(Object key0) throws JSExn { - FileInputStream fis = null; + public JS get(JS key0) throws JSExn { try { if (key0 == null) return null; String key = toString(key0); @@ -99,30 +96,23 @@ public class Directory extends JS { if (f2.isDirectory()) return new Directory(f2); char[] chars = new char[((int)f2.length()) * 2]; int numchars = 0; - fis = new FileInputStream(f2); - Reader r = new InputStreamReader(fis); + Reader r = new InputStreamReader(new FileInputStream(f2)); while(true) { int numread = r.read(chars, numchars, chars.length - numchars); - if (numread == -1) return new String(chars, 0, numchars); + if (numread == -1) return JS.S(new String(chars, 0, numchars)); numchars += numread; } } catch (IOException ioe) { throw new JSExn.IO(ioe); - } finally { - try { - if (fis != null) fis.close(); - } catch (IOException ioe) { - throw new JSExn.IO(ioe); - } } } public Enumeration keys() { final String[] elements = f.list(); - return new Enumeration() { + return new Enumeration(null) { int i = 0; - public boolean hasMoreElements() { return i < elements.length; } - public Object nextElement() { return FileNameEncoder.decode(elements[i++]); } + public boolean _hasMoreElements() { return i < elements.length; } + public JS _nextElement() { return JS.S(FileNameEncoder.decode(elements[i++])); } }; } } diff --git a/src/org/ibex/js/Fountain.java b/src/org/ibex/js/Fountain.java index 264f2ff..acf617a 100644 --- a/src/org/ibex/js/Fountain.java +++ b/src/org/ibex/js/Fountain.java @@ -12,7 +12,7 @@ import org.ibex.net.*; * be totally independent of the others (ie separate stream position * and state) although they draw from the same data source. */ -public abstract class Fountain extends JS.Cloneable { +public abstract class Fountain extends JS.O implements JS.Cloneable { // Public Interface ////////////////////////////////////////////////////////////////////////////// @@ -37,7 +37,7 @@ public abstract class Fountain extends JS.Cloneable { /** HTTP or HTTPS resource */ public static class HTTP extends Fountain { private String url; - public String toString() { return "Stream.HTTP:" + url; } + //public String toString() { return "Stream.HTTP:" + url; } public HTTP(String url) { while (url.endsWith("/")) url = url.substring(0, url.length() - 1); this.url = url; } public Object _get(Object key) { return new HTTP(url + "/" + (String)key); } public String getCacheKey(Vec path) throws NotCacheableException { return url; } @@ -58,7 +58,7 @@ public abstract class Fountain extends JS.Cloneable { public static class File extends Fountain { private String path; public File(String path) { this.path = path; } - public String toString() { return "file:" + path; } + //public String toString() { return "file:" + path; } public String getCacheKey() throws NotCacheableException { throw new NotCacheableException(); /* already on disk */ } public InputStream getInputStream() throws IOException { return new FileInputStream(path); } public Object _get(Object key) { return new File(path + java.io.File.separatorChar + (String)key); } diff --git a/src/org/ibex/js/Interpreter.java b/src/org/ibex/js/Interpreter.java index 94ccab5..d19cd2e 100644 --- a/src/org/ibex/js/Interpreter.java +++ b/src/org/ibex/js/Interpreter.java @@ -6,8 +6,6 @@ import java.util.*; /** Encapsulates a single JS interpreter (ie call stack) */ class Interpreter implements ByteCodes, Tokens { - - // Thread-Interpreter Mapping ///////////////////////////////////////////////////////////////////////// static Interpreter current() { return (Interpreter)threadToInterpreter.get(Thread.currentThread()); } @@ -19,20 +17,34 @@ class Interpreter implements ByteCodes, Tokens { int pausecount; ///< the number of times pause() has been invoked; -1 indicates unpauseable JSFunction f = null; ///< the currently-executing JSFunction JSScope scope; ///< the current top-level scope (LIFO stack via NEWSCOPE/OLDSCOPE) - Vec stack = new Vec(); ///< the object stack + final Stack stack = new Stack(); ///< the object stack int pc = 0; ///< the program counter - Interpreter(JSFunction f, boolean pauseable, JSArray args) { this(f, pauseable, args, true); } - Interpreter(JSFunction f, boolean pauseable, JSArray args, boolean wrap) { - stack.push(new Interpreter.CallMarker(this)); // the "root function returned" marker -- f==null + Interpreter(JSFunction f, boolean pauseable, JSArgs args) { this.f = f; this.pausecount = pauseable ? 0 : -1; - this.scope = wrap ? new JSScope(f.parentScope) : f.parentScope; - stack.push(args); + this.scope = f.parentScope; + try { + stack.push(new CallMarker(null)); // the "root function returned" marker -- f==null + stack.push(args); + } catch(JSExn e) { + throw new Error("should never happen"); + } + } + + Interpreter(Trap t, JS val, boolean pauseOnPut) { + this.pausecount = -1; + try { + setupTrap(t,val,new TrapMarker(null,t,val,pauseOnPut)); + } catch(JSExn e) { + throw new Error("should never happen"); + } } /** this is the only synchronization point we need in order to be threadsafe */ - synchronized Object resume() throws JSExn { + synchronized JS resume() throws JSExn { + if(f == null) throw new RuntimeException("function already finished"); + if(scope == null) throw new RuntimeException("scope is null"); Thread t = Thread.currentThread(); Interpreter old = (Interpreter)threadToInterpreter.get(t); threadToInterpreter.put(t, this); @@ -56,15 +68,13 @@ class Interpreter implements ByteCodes, Tokens { private static JSExn je(String s) { return new JSExn(getSourceName() + ":" + getLine() + " " + s); } - // FIXME: double check the trap logic - private Object run() throws JSExn { + private JS run() throws JSExn { // if pausecount changes after a get/put/call, we know we've been paused final int initialPauseCount = pausecount; OUTER: for(;; pc++) { try { - if (f == null) return stack.pop(); int op = f.op[pc]; Object arg = f.arg[pc]; if(op == FINALLY_DONE) { @@ -75,28 +85,26 @@ class Interpreter implements ByteCodes, Tokens { arg = fd.arg; } switch(op) { - case LITERAL: stack.push(arg); break; - case OBJECT: stack.push(new JS()); break; - case ARRAY: stack.push(new JSArray(JS.toNumber(arg).intValue())); break; - case DECLARE: scope.declare((String)(arg==null ? stack.peek() : arg)); if(arg != null) stack.push(arg); break; - case TOPSCOPE: stack.push(scope); break; - case JT: if (JS.toBoolean(stack.pop())) pc += JS.toNumber(arg).intValue() - 1; break; - case JF: if (!JS.toBoolean(stack.pop())) pc += JS.toNumber(arg).intValue() - 1; break; - case JMP: pc += JS.toNumber(arg).intValue() - 1; break; + case LITERAL: stack.push((JS)arg); break; + case OBJECT: stack.push(new JS.O()); break; + case ARRAY: stack.push(new JSArray(JS.toInt((JS)arg))); break; + //case DECLARE: scope.declare((JS)(arg==null ? stack.peek() : arg)); if(arg != null) stack.push((JS)arg); break; + case JT: if (JS.toBoolean(stack.pop())) pc += JS.toInt((JS)arg) - 1; break; + case JF: if (!JS.toBoolean(stack.pop())) pc += JS.toInt((JS)arg) - 1; break; + case JMP: pc += JS.toInt((JS)arg) - 1; break; case POP: stack.pop(); break; - case SWAP: { - int depth = (arg == null ? 1 : JS.toInt(arg)); - Object save = stack.elementAt(stack.size() - 1); - for(int i=stack.size() - 1; i > stack.size() - 1 - depth; i--) - stack.setElementAt(stack.elementAt(i-1), i); - stack.setElementAt(save, stack.size() - depth - 1); - break; } + case SWAP: stack.swap(); break; case DUP: stack.push(stack.peek()); break; - case NEWSCOPE: scope = new JSScope(scope); break; - case OLDSCOPE: scope = scope.getParentScope(); break; - case ASSERT: - if (JS.checkAssertions && !JS.toBoolean(stack.pop())) - throw je("ibex.assertion.failed" /*FEATURE: line number*/); break; + case NEWSCOPE: { + int n = JS.toInt((JS)arg); + scope = new JSScope(scope,(n>>>16)&0xffff,(n>>>0)&0xffff); + break; + } + case OLDSCOPE: scope = scope.parent; break; + case GLOBALSCOPE: stack.push(scope.getGlobal()); break; + case SCOPEGET: stack.push(scope.get((JS)arg)); break; + case SCOPEPUT: scope.put((JS)arg,stack.peek()); break; + case ASSERT: if (!JS.toBoolean(stack.pop())) throw je("ibex.assertion.failed"); break; case BITNOT: stack.push(JS.N(~JS.toLong(stack.pop()))); break; case BANG: stack.push(JS.B(!JS.toBoolean(stack.pop()))); break; case NEWFUNCTION: stack.push(((JSFunction)arg)._cloneWithNewParentScope(scope)); break; @@ -105,32 +113,28 @@ class Interpreter implements ByteCodes, Tokens { case TYPEOF: { Object o = stack.pop(); if (o == null) stack.push(null); - else if (o instanceof JS) stack.push("object"); - else if (o instanceof String) stack.push("string"); - else if (o instanceof Number) stack.push("number"); - else if (o instanceof Boolean) stack.push("boolean"); - else throw new Error("this should not happen"); + else if (o instanceof JSString) stack.push(JS.S("string")); + else if (o instanceof JSNumber.B) stack.push(JS.S("boolean")); + else if (o instanceof JSNumber) stack.push(JS.S("number")); + else stack.push(JS.S("object")); break; } case PUSHKEYS: { - Object o = stack.peek(); - Enumeration e = ((JS)o).keys(); - JSArray a = new JSArray(); - while(e.hasMoreElements()) a.addElement(e.nextElement()); - stack.push(a); + JS o = stack.peek(); + stack.push(o == null ? null : o.keys()); break; } case LOOP: stack.push(new LoopMarker(pc, pc > 0 && f.op[pc - 1] == LABEL ? (String)f.arg[pc - 1] : (String)null, scope)); - stack.push(Boolean.TRUE); + stack.push(JS.T); break; case BREAK: case CONTINUE: - while(stack.size() > 0) { - Object o = stack.pop(); + while(!stack.empty()) { + JS o = stack.pop(); if (o instanceof CallMarker) je("break or continue not within a loop"); if (o instanceof TryMarker) { if(((TryMarker)o).finallyLoc < 0) continue; // no finally block, keep going @@ -142,9 +146,9 @@ class Interpreter implements ByteCodes, Tokens { if (o instanceof LoopMarker) { if (arg == null || arg.equals(((LoopMarker)o).label)) { int loopInstructionLocation = ((LoopMarker)o).location; - int endOfLoop = ((Integer)f.arg[loopInstructionLocation]).intValue() + loopInstructionLocation; + int endOfLoop = JS.toInt((JS)f.arg[loopInstructionLocation]) + loopInstructionLocation; scope = ((LoopMarker)o).scope; - if (op == CONTINUE) { stack.push(o); stack.push(Boolean.FALSE); } + if (op == CONTINUE) { stack.push(o); stack.push(JS.F); } pc = op == BREAK ? endOfLoop - 1 : loopInstructionLocation; continue OUTER; } @@ -162,8 +166,8 @@ class Interpreter implements ByteCodes, Tokens { } case RETURN: { - Object retval = stack.pop(); - while(stack.size() > 0) { + JS retval = stack.pop(); + while(!stack.empty()) { Object o = stack.pop(); if (o instanceof TryMarker) { if(((TryMarker)o).finallyLoc < 0) continue; @@ -173,209 +177,193 @@ class Interpreter implements ByteCodes, Tokens { pc = ((TryMarker)o).finallyLoc - 1; continue OUTER; } else if (o instanceof CallMarker) { - if (scope instanceof Trap.TrapScope) { // handles return component of a read trap - Trap.TrapScope ts = (Trap.TrapScope)scope; - if (retval != null && retval instanceof Boolean && ((Boolean)retval).booleanValue()) - ts.cascadeHappened = true; - if (!ts.cascadeHappened) { - ts.cascadeHappened = true; - Trap t = ts.t.next; - while (t != null && t.f.numFormalArgs == 0) t = t.next; - if (t == null) { - ((JS)ts.t.trapee).put(ts.t.name, ts.val); - if (pausecount > initialPauseCount) { pc++; return null; } // we were paused - } else { - stack.push(o); - JSArray args = new JSArray(); - args.addElement(ts.val); - stack.push(args); - f = t.f; - scope = new Trap.TrapScope(f.parentScope, t, ts.val); - pc = -1; + boolean didTrapPut = false; + if (o instanceof TrapMarker) { // handles return component of a write trap + TrapMarker tm = (TrapMarker) o; + boolean cascade = tm.t.isWriteTrap() && !tm.cascadeHappened && !JS.toBoolean(retval); + if(cascade) { + Trap t = tm.t.nextWriteTrap(); + if(t == null && tm.t.target instanceof JS.Clone) { + t = ((JS.Clone)tm.t.target).clonee.getTrap(tm.t.key); + if(t != null) t = t.writeTrap(); + } + if(t != null) { + tm.t = t; // we reuse the old trap marker + setupTrap(t,tm.val,tm); + pc--; // we increment it on the next iter continue OUTER; + } else { + didTrapPut = true; + if(!tm.pauseOnPut) tm.t.target.put(tm.t.key,tm.val); } } } - scope = ((CallMarker)o).scope; - pc = ((CallMarker)o).pc - 1; - f = (JSFunction)((CallMarker)o).f; - stack.push(retval); + CallMarker cm = (CallMarker) o; + scope = cm.scope; + pc = cm.pc - 1; + f = cm.f; + if (didTrapPut) { + if (((TrapMarker)cm).pauseOnPut) { pc++; return ((TrapMarker)cm).val; } + if (pausecount > initialPauseCount) { pc++; return null; } // we were paused + } else { + stack.push(retval); + } + if (f == null) return retval; continue OUTER; } } throw new Error("error: RETURN invoked but couldn't find a CallMarker!"); } - - case PUT: { - Object val = stack.pop(); - Object key = stack.pop(); - Object target = stack.peek(); - if (target == null) - throw je("tried to put a value to the " + key + " property on the null value"); - if (!(target instanceof JS)) - throw je("tried to put a value to the " + key + " property on a " + target.getClass().getName()); - if (key == null) - throw je("tried to assign \"" + (val==null?"(null)":val.toString()) + "\" to the null key"); - - Trap t = null; - if (target instanceof JSScope && key.equals("cascade")) { - Trap.TrapScope ts = null; - JSScope p = (JSScope)target; // search the scope-path for the trap - if (target instanceof Trap.TrapScope) { - ts = (Trap.TrapScope)target; - } - else { - while (ts == null && p.getParentScope() != null) { - p = p.getParentScope(); - if (p instanceof Trap.TrapScope) { - ts = (Trap.TrapScope)p; - } - } - } - t = ts.t.next; - ts.cascadeHappened = true; - while (t != null && t.f.numFormalArgs == 0) t = t.next; - if (t == null) { target = ts.t.trapee; key = ts.t.name; } - - } else if (target instanceof Trap.TrapScope && key.equals(((Trap.TrapScope)target).t.name)) { - throw je("tried to put to " + key + " inside a trap it owns; use cascade instead"); - - } else if (target instanceof JS) { - if (target instanceof JSScope) { - JSScope p = (JSScope)target; // search the scope-path for the trap - t = p.getTrap(key); - while (t == null && p.getParentScope() != null) { p = p.getParentScope(); t = p.getTrap(key); } + + case CASCADE: { + boolean write = JS.toBoolean((JS)arg); + JS val = write ? stack.pop() : null; + CallMarker o = stack.findCall(); + if(!(o instanceof TrapMarker)) throw new JSExn("tried to CASCADE while not in a trap"); + TrapMarker tm = (TrapMarker) o; + JS key = tm.t.key; + JS target = tm.t.target; + if(tm.t.isWriteTrap() != write) throw new JSExn("tried to do a " + (write?"write":"read") + " cascade in a " + (write?"read":"write") + " trap"); + Trap t = write ? tm.t.nextWriteTrap() : tm.t.nextReadTrap(); + // FIXME: Doesn't handle multiple levels of clone's (probably can just make this a while loop) + if(t == null && target instanceof JS.Clone) { + target = ((JS.Clone)target).clonee; + t = target.getTrap(key); + if(t != null) t = write ? t.writeTrap() : t.readTrap(); + } + if(write) { + tm.cascadeHappened = true; + stack.push(val); + } + if(t != null) { + setupTrap(t,val,new TrapMarker(this,t,val,tm.pauseOnPut)); + pc--; // we increment later + } else { + if(write) { + if (tm.pauseOnPut) { pc++; return val; } + target.put(key,val); } else { - t = ((JS)target).getTrap(key); + JS ret = target.get(key); + if (ret == JS.METHOD) ret = new Stub(target, key); + stack.push(ret); } - while (t != null && t.f.numFormalArgs == 0) t = t.next; // find the first write trap + if (pausecount > initialPauseCount) { pc++; return null; } // we were paused } - if (t != null) { - stack.push(new CallMarker(this)); - JSArray args = new JSArray(); - args.addElement(val); - stack.push(args); - f = t.f; - scope = new Trap.TrapScope(f.parentScope, t, val); - pc = -1; - break; + break; + } + + case PUT: { + JS val = stack.pop(); + JS key = stack.pop(); + JS target = stack.peek(); + if (target == null) throw je("tried to put " + JS.debugToString(val) + " to the " + JS.debugToString(key) + " property on the null value"); + if (key == null) throw je("tried to assign \"" + JS.debugToString(val) + "\" to the null key"); + + Trap t = target.getTrap(key); + if(t != null) t = t.writeTrap(); + + if(t == null && target instanceof JS.Clone) { + target = ((JS.Clone)target).clonee; + t = target.getTrap(key); + if(t != null) t = t.writeTrap(); } - ((JS)target).put(key, val); - if (pausecount > initialPauseCount) { pc++; return null; } // we were paused + stack.push(val); + + if(t != null) { + setupTrap(t,val,new TrapMarker(this,t,val)); + pc--; // we increment later + } else { + target.put(key,val); + if (pausecount > initialPauseCount) { pc++; return null; } // we were paused + } break; } case GET: case GET_PRESERVE: { - Object o, v; + JS target, key; if (op == GET) { - v = arg == null ? stack.pop() : arg; - o = stack.pop(); + key = arg == null ? stack.pop() : (JS)arg; + target = stack.pop(); } else { - v = stack.pop(); - o = stack.peek(); - stack.push(v); + key = stack.pop(); + target = stack.peek(); + stack.push(key); } - Object ret = null; - if (v == null) throw je("tried to get the null key from " + o); - if (o == null) throw je("tried to get property \"" + v + "\" from the null object"); - if (o instanceof String || o instanceof Number || o instanceof Boolean) { - ret = getFromPrimitive(o,v); - stack.push(ret); - break; - } else if (o instanceof JS) { - Trap t = null; - if (o instanceof Trap.TrapScope && v.equals("cascade")) { - t = ((Trap.TrapScope)o).t.next; - while (t != null && t.f.numFormalArgs != 0) t = t.next; - if (t == null) { v = ((Trap.TrapScope)o).t.name; o = ((Trap.TrapScope)o).t.trapee; } - - } else if (o instanceof JS) { - if (o instanceof JSScope) { - JSScope p = (JSScope)o; // search the scope-path for the trap - t = p.getTrap(v); - while (t == null && p.getParentScope() != null) { p = p.getParentScope(); t = p.getTrap(v); } - } else { - t = ((JS)o).getTrap(v); - } - while (t != null && t.f.numFormalArgs != 0) t = t.next; // get first read trap - } - if (t != null) { - stack.push(new CallMarker(this)); - JSArray args = new JSArray(); - stack.push(args); - f = t.f; - scope = new Trap.TrapScope(f.parentScope, t, null); - ((Trap.TrapScope)scope).cascadeHappened = true; - pc = -1; - break; - } - ret = ((JS)o).get(v); - if (ret == JS.METHOD) ret = new Stub((JS)o, v); + JS ret = null; + if (key == null) throw je("tried to get the null key from " + JS.debugToString(target)); + if (target == null) throw je("tried to get property \"" + JS.debugToString(key) + "\" from the null object"); + + Trap t = target.getTrap(key); + if(t != null) t = t.readTrap(); + + if(t == null && target instanceof JS.Clone) { + target = ((JS.Clone)target).clonee; + t = target.getTrap(key); + if(t != null) t = t.readTrap(); + } + + if(t != null) { + setupTrap(t,null,new TrapMarker(this,t,null)); + pc--; // we increment later + } else { + ret = target.get(key); if (pausecount > initialPauseCount) { pc++; return null; } // we were paused + if (ret == JS.METHOD) ret = new Stub(target, key); stack.push(ret); - break; } - throw je("tried to get property " + v + " from a " + o.getClass().getName()); + break; } case CALL: case CALLMETHOD: { - int numArgs = JS.toInt(arg); - Object method = null; - Object ret = null; - Object object = stack.pop(); + int numArgs = JS.toInt((JS)arg); + + JS[] rest = numArgs > 3 ? new JS[numArgs - 3] : null; + for(int i=numArgs - 1; i>2; i--) rest[i-3] = stack.pop(); + JS a2 = numArgs <= 2 ? null : stack.pop(); + JS a1 = numArgs <= 1 ? null : stack.pop(); + JS a0 = numArgs <= 0 ? null : stack.pop(); + + JS method = null; + JS ret = null; + JS object = stack.pop(); if (op == CALLMETHOD) { if (object == JS.METHOD) { method = stack.pop(); object = stack.pop(); } else if (object == null) { - Object name = stack.pop(); - stack.pop(); - throw new JSExn("function '"+name+"' not found"); + method = stack.pop(); + object = stack.pop(); + throw new JSExn("function '"+JS.debugToString(method)+"' not found in " + object.getClass().getName()); } else { stack.pop(); stack.pop(); } } - Object[] rest = numArgs > 3 ? new Object[numArgs - 3] : null; - for(int i=numArgs - 1; i>2; i--) rest[i-3] = stack.pop(); - Object a2 = numArgs <= 2 ? null : stack.pop(); - Object a1 = numArgs <= 1 ? null : stack.pop(); - Object a0 = numArgs <= 0 ? null : stack.pop(); - if (object instanceof String || object instanceof Number || object instanceof Boolean) { - ret = callMethodOnPrimitive(object, method, a0, a1, a2, null, numArgs); - - } else if (object instanceof JSFunction) { - // FIXME: use something similar to call0/call1/call2 here - JSArray arguments = new JSArray(); - for(int i=0; i initialPauseCount) { pc++; return null; } stack.push(ret); break; } case THROW: - throw new JSExn(stack.pop(), stack, f, pc, scope); + throw new JSExn(stack.pop(), this); - /* FIXME + /* FIXME GRAMMAR case MAKE_GRAMMAR: { final Grammar r = (Grammar)arg; final JSScope final_scope = scope; @@ -399,49 +387,44 @@ class Interpreter implements ByteCodes, Tokens { } */ case ADD_TRAP: case DEL_TRAP: { - Object val = stack.pop(); - Object key = stack.pop(); - Object obj = stack.peek(); + JS val = stack.pop(); + JS key = stack.pop(); + JS js = stack.peek(); // A trap addition/removal - JS js = obj instanceof JSScope ? ((JSScope)obj).top() : (JS) obj; + if(!(val instanceof JSFunction)) throw new JSExn("tried to add/remove a non-function trap"); if(op == ADD_TRAP) js.addTrap(key, (JSFunction)val); else js.delTrap(key, (JSFunction)val); break; } - case ASSIGN_SUB: case ASSIGN_ADD: { - Object val = stack.pop(); - Object key = stack.pop(); - Object obj = stack.peek(); - // The following setup is VERY important. The generated bytecode depends on the stack - // being setup like this (top to bottom) KEY, OBJ, VAL, KEY, OBJ - stack.push(key); - stack.push(val); - stack.push(obj); - stack.push(key); - break; - } - case ADD: { - int count = ((Number)arg).intValue(); + int count = ((JSNumber)arg).toInt(); if(count < 2) throw new Error("this should never happen"); if(count == 2) { // common case - Object right = stack.pop(); - Object left = stack.pop(); - if(left instanceof String || right instanceof String) - stack.push(JS.toString(left).concat(JS.toString(right))); - else stack.push(JS.N(JS.toDouble(left) + JS.toDouble(right))); + JS right = stack.pop(); + JS left = stack.pop(); + JS ret; + if(left instanceof JSString || right instanceof JSString) + ret = JS.S(JS.toString(left).concat(JS.toString(right))); + else if(left instanceof JSNumber.D || right instanceof JSNumber.D) + ret = JS.N(JS.toDouble(left) + JS.toDouble(right)); + else { + long l = JS.toLong(left) + JS.toLong(right); + if(l < Integer.MIN_VALUE || l > Integer.MAX_VALUE) ret = JS.N(l); + ret = JS.N((int)l); + } + stack.push(ret); } else { - Object[] args = new Object[count]; + JS[] args = new JS[count]; while(--count >= 0) args[count] = stack.pop(); - if(args[0] instanceof String) { + if(args[0] instanceof JSString) { StringBuffer sb = new StringBuffer(64); for(int i=0;i> JS.toLong(right))); break; case URSH: stack.push(JS.N(JS.toLong(left) >>> JS.toLong(right))); break; - case LT: case LE: case GT: case GE: { - if (left == null) left = JS.N(0); - if (right == null) right = JS.N(0); - int result = 0; - if (left instanceof String || right instanceof String) { - result = left.toString().compareTo(right.toString()); - } else { - result = (int)java.lang.Math.ceil(JS.toDouble(left) - JS.toDouble(right)); - } - stack.push(JS.B((op == LT && result < 0) || (op == LE && result <= 0) || - (op == GT && result > 0) || (op == GE && result >= 0))); - break; + //#repeat />= LT/LE/GT/GE + case LT: { + if(left instanceof JSString && right instanceof JSString) + stack.push(JS.B(JS.toString(left).compareTo(JS.toString(right)) < 0)); + else + stack.push(JS.B(JS.toDouble(left) < JS.toDouble(right))); } + //#end 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 (r == null) ret = false; // l != null, so its false - else if (l instanceof Boolean) ret = JS.B(JS.toBoolean(r)).equals(l); - else if (l instanceof Number) ret = JS.toNumber(r).doubleValue() == JS.toNumber(l).doubleValue(); - else if (l instanceof String) ret = r != null && l.equals(r.toString()); - else ret = l.equals(r); + if(left == null && right == null) ret = true; + else if(left == null || right == null) ret = false; + else ret = left.jsequals(right); stack.push(JS.B(op == EQ ? ret : !ret)); break; } @@ -516,66 +488,107 @@ class Interpreter implements ByteCodes, Tokens { } } catch(JSExn e) { - while(stack.size() > 0) { - Object o = stack.pop(); - if (o instanceof CatchMarker || o instanceof TryMarker) { - boolean inCatch = o instanceof CatchMarker; - if(inCatch) { - o = stack.pop(); - if(((TryMarker)o).finallyLoc < 0) continue; // no finally block, keep going - } - if(!inCatch && ((TryMarker)o).catchLoc >= 0) { - // run the catch block, this will implicitly run the finally block, if it exists - stack.push(o); - stack.push(catchMarker); - stack.push(e.getObject()); - f = ((TryMarker)o).f; - scope = ((TryMarker)o).scope; - pc = ((TryMarker)o).catchLoc - 1; - continue OUTER; - } else { - stack.push(new FinallyData(e)); - f = ((TryMarker)o).f; - scope = ((TryMarker)o).scope; - pc = ((TryMarker)o).finallyLoc - 1; - continue OUTER; - } - } - } - throw e; + catchException(e); + pc--; // it'll get incremented on the next iteration } // end try/catch } // end for } + + /** tries to find a handler withing the call chain for this exception + if a handler is found the interpreter is setup to call the exception handler + if a handler is not found the exception is thrown + */ + void catchException(JSExn e) throws JSExn { + while(!stack.empty()) { + JS o = stack.pop(); + if (o instanceof CatchMarker || o instanceof TryMarker) { + boolean inCatch = o instanceof CatchMarker; + if(inCatch) { + o = stack.pop(); + if(((TryMarker)o).finallyLoc < 0) continue; // no finally block, keep going + } + if(!inCatch && ((TryMarker)o).catchLoc >= 0) { + // run the catch block, this will implicitly run the finally block, if it exists + stack.push(o); + stack.push(catchMarker); + stack.push(e.getObject()); + f = ((TryMarker)o).f; + scope = ((TryMarker)o).scope; + pc = ((TryMarker)o).catchLoc; + return; + } else { + stack.push(new FinallyData(e)); + f = ((TryMarker)o).f; + scope = ((TryMarker)o).scope; + pc = ((TryMarker)o).finallyLoc; + return; + } + } + } + throw e; + } + void setupTrap(Trap t, JS val, CallMarker cm) throws JSExn { + stack.push(cm); + stack.push(new TrapArgs(t,val)); + f = t.f; + scope = f.parentScope; + pc = 0; + } // Markers ////////////////////////////////////////////////////////////////////// - public static class CallMarker { - int pc; - JSScope scope; - JSFunction f; - public CallMarker(Interpreter cx) { pc = cx.pc + 1; scope = cx.scope; f = cx.f; } + static class Marker extends JS { + public JS get(JS key) throws JSExn { throw new Error("this should not be accessible from a script"); } + public void put(JS key, JS val) throws JSExn { throw new Error("this should not be accessible from a script"); } + public String coerceToString() { throw new Error("this should not be accessible from a script"); } + public JS call(JS a0, JS a1, JS a2, JS[] rest, int nargs) throws JSExn { throw new Error("this should not be accessible from a script"); } + } + + static class CallMarker extends Marker { + final int pc; + final JSScope scope; + final JSFunction f; + public CallMarker(Interpreter cx) { + pc = cx == null ? -1 : cx.pc + 1; + scope = cx == null ? null : cx.scope; + f = cx == null ? null : cx.f; + } } - public static class CatchMarker { } + static class TrapMarker extends CallMarker { + Trap t; + JS val; + boolean cascadeHappened; + final boolean pauseOnPut; + public TrapMarker(Interpreter cx, Trap t, JS val) { this(cx,t,val,false); } + public TrapMarker(Interpreter cx, Trap t, JS val, boolean pauseOnPut) { + super(cx); + this.t = t; + this.val = val; + this.pauseOnPut = pauseOnPut; + } + } + + static class CatchMarker extends Marker { } private static CatchMarker catchMarker = new CatchMarker(); - public static class LoopMarker { - public int location; - public String label; - public JSScope scope; + static class LoopMarker extends Marker { + final public int location; + final public String label; + final public JSScope scope; public LoopMarker(int location, String label, JSScope scope) { this.location = location; this.label = label; this.scope = scope; } } - public static class TryMarker { - public int catchLoc; - public int finallyLoc; - public JSScope scope; - public JSFunction f; + static class TryMarker extends Marker { + final public int catchLoc; + final public int finallyLoc; + final public JSScope scope; + final public JSFunction f; public TryMarker(int catchLoc, int finallyLoc, Interpreter cx) { this.catchLoc = catchLoc; this.finallyLoc = finallyLoc; @@ -583,163 +596,110 @@ class Interpreter implements ByteCodes, Tokens { this.f = cx.f; } } - public static class FinallyData { - public int op; - public Object arg; - public JSExn exn; + static class FinallyData extends Marker { + final public int op; + final public Object arg; + final public JSExn exn; public FinallyData(int op) { this(op,null); } - public FinallyData(int op, Object arg) { this.op = op; this.arg = arg; } - public FinallyData(JSExn exn) { this.exn = exn; } // Just throw this exn + public FinallyData(int op, Object arg) { this.op = op; this.arg = arg; this.exn = null; } + public FinallyData(JSExn exn) { this.exn = exn; this.op = -1; this.arg = null; } // Just throw this exn } - - // Operations on Primitives ////////////////////////////////////////////////////////////////////// - - static Object callMethodOnPrimitive(Object o, Object method, Object arg0, Object arg1, Object arg2, Object[] rest, int alength) throws JSExn { - if (method == null || !(method instanceof String) || "".equals(method)) - throw new JSExn("attempt to call a non-existant method on a primitive"); - - if (o instanceof Number) { - //#switch(method) - case "toFixed": throw new JSExn("toFixed() not implemented"); - case "toExponential": throw new JSExn("toExponential() not implemented"); - case "toPrecision": throw new JSExn("toPrecision() not implemented"); - case "toString": { - int radix = alength >= 1 ? JS.toInt(arg0) : 10; - return Long.toString(((Number)o).longValue(),radix); - } + static class TrapArgs extends JS { + private Trap t; + private JS val; + public TrapArgs(Trap t, JS val) { this.t = t; this.val = val; } + public JS get(JS key) throws JSExn { + if(JS.isInt(key) && JS.toInt(key) == 0) return val; + //#switch(JS.toString(key)) + case "trapee": return t.target; + case "callee": return t.f; + case "trapname": return t.key; + case "length": return t.isWriteTrap() ? ONE : ZERO; //#end - } else if (o instanceof Boolean) { - // No methods for Booleans - throw new JSExn("attempt to call a method on a Boolean"); - } - - String s = JS.toString(o); - int slength = s.length(); - //#switch(method) - case "substring": { - int a = alength >= 1 ? JS.toInt(arg0) : 0; - int b = alength >= 2 ? JS.toInt(arg1) : slength; - if (a > slength) a = slength; - if (b > slength) b = slength; - if (a < 0) a = 0; - if (b < 0) b = 0; - if (a > b) { int tmp = a; a = b; b = tmp; } - return s.substring(a,b); - } - case "substr": { - int start = alength >= 1 ? JS.toInt(arg0) : 0; - int len = alength >= 2 ? JS.toInt(arg1) : Integer.MAX_VALUE; - if (start < 0) start = slength + start; - if (start < 0) start = 0; - if (len < 0) len = 0; - if (len > slength - start) len = slength - start; - if (len <= 0) return ""; - return s.substring(start,start+len); - } - case "charAt": { - int p = alength >= 1 ? JS.toInt(arg0) : 0; - if (p < 0 || p >= slength) return ""; - return s.substring(p,p+1); - } - case "charCodeAt": { - int p = alength >= 1 ? JS.toInt(arg0) : 0; - if (p < 0 || p >= slength) return JS.N(Double.NaN); - return JS.N(s.charAt(p)); - } - case "concat": { - StringBuffer sb = new StringBuffer(slength*2).append(s); - for(int i=0;i= 1 ? arg0.toString() : "null"; - int start = alength >= 2 ? JS.toInt(arg1) : 0; - // Java's indexOf handles an out of bounds start index, it'll return -1 - return JS.N(s.indexOf(search,start)); - } - case "lastIndexOf": { - String search = alength >= 1 ? arg0.toString() : "null"; - int start = alength >= 2 ? JS.toInt(arg1) : 0; - // Java's indexOf handles an out of bounds start index, it'll return -1 - return JS.N(s.lastIndexOf(search,start)); + return super.get(key); } - case "match": return JSRegexp.stringMatch(s,arg0); - case "replace": return JSRegexp.stringReplace(s,arg0,arg1); - case "search": return JSRegexp.stringSearch(s,arg0); - case "split": return JSRegexp.stringSplit(s,arg0,arg1,alength); - case "toLowerCase": return s.toLowerCase(); - case "toUpperCase": return s.toUpperCase(); - case "toString": return s; - case "slice": { - int a = alength >= 1 ? JS.toInt(arg0) : 0; - int b = alength >= 2 ? JS.toInt(arg1) : slength; - if (a < 0) a = slength + a; - if (b < 0) b = slength + b; - if (a < 0) a = 0; - if (b < 0) b = 0; - if (a > slength) a = slength; - if (b > slength) b = slength; - if (a > b) return ""; - return s.substring(a,b); - } - //#end - throw new JSExn("Attempted to call non-existent method: " + method); } - static Object getFromPrimitive(Object o, Object key) throws JSExn { - boolean returnJS = false; - if (o instanceof Boolean) { - throw new JSExn("Booleans do not have properties"); - } else if (o instanceof Number) { - if (key.equals("toPrecision") || key.equals("toExponential") || key.equals("toFixed")) - returnJS = true; - } - if (!returnJS) { - // the string stuff applies to everything - String s = o.toString(); - - // this is sort of ugly, but this list should never change - // These should provide a complete (enough) implementation of the ECMA-262 String object - - //#switch(key) - case "length": return JS.N(s.length()); - case "substring": returnJS = true; break; - case "charAt": returnJS = true; break; - case "charCodeAt": returnJS = true; break; - case "concat": returnJS = true; break; - case "indexOf": returnJS = true; break; - case "lastIndexOf": returnJS = true; break; - case "match": returnJS = true; break; - case "replace": returnJS = true; break; - case "search": returnJS = true; break; - case "slice": returnJS = true; break; - case "split": returnJS = true; break; - case "toLowerCase": returnJS = true; break; - case "toUpperCase": returnJS = true; break; - case "toString": returnJS = true; break; - case "substr": returnJS = true; break; - //#end + static class JSArgs extends JS { + private final JS a0; + private final JS a1; + private final JS a2; + private final JS[] rest; + private final int nargs; + private final JS callee; + + public JSArgs(JS callee) { this(null,null,null,null,0,callee); } + public JSArgs(JS a0, JS callee) { this(a0,null,null,null,1,callee); } + public JSArgs(JS a0, JS a1, JS a2, JS[] rest, int nargs, JS callee) { + this.a0 = a0; this.a1 = a1; this.a2 = a2; + this.rest = rest; this.nargs = nargs; + this.callee = callee; } - if (returnJS) { - final Object target = o; - final String method = key.toString(); - return new JS() { - public Object call(Object a0, Object a1, Object a2, Object[] rest, int nargs) throws JSExn { - if (nargs > 2) throw new JSExn("cannot call that method with that many arguments"); - return callMethodOnPrimitive(target, method, a0, a1, a2, rest, nargs); - } - }; + + public JS get(JS key) throws JSExn { + if(JS.isInt(key)) { + int n = JS.toInt(key); + switch(n) { + case 0: return a0; + case 1: return a1; + case 2: return a2; + default: return n>= 0 && n < nargs ? rest[n-3] : null; + } + } + //#switch(JS.toString(key)) + case "callee": return callee; + case "length": return JS.N(nargs); + //#end + return super.get(key); } - return null; } - private static class Stub extends JS { - private Object method; + static class Stub extends JS { + private JS method; JS obj; - public Stub(JS obj, Object method) { this.obj = obj; this.method = method; } - public Object call(Object a0, Object a1, Object a2, Object[] rest, int nargs) throws JSExn { + public Stub(JS obj, JS method) { this.obj = obj; this.method = method; } + public JS call(JS a0, JS a1, JS a2, JS[] rest, int nargs) throws JSExn { return ((JS)obj).callMethod(method, a0, a1, a2, rest, nargs); } } + + static class Stack { + private static final int MAX_STACK_SIZE = 512; + private JS[] stack = new JS[64]; + private int sp = 0; + + boolean empty() { return sp == 0; } + void push(JS o) throws JSExn { if(sp == stack.length) grow(); stack[sp++] = o; } + JS peek() { if(sp == 0) throw new RuntimeException("Stack underflow"); return stack[sp-1]; } + final JS pop() { if(sp == 0) throw new RuntimeException("Stack underflow"); return stack[--sp]; } + void swap() throws JSExn { + if(sp < 2) throw new JSExn("stack overflow"); + JS tmp = stack[sp-2]; + stack[sp-2] = stack[sp-1]; + stack[sp-1] = tmp; + } + CallMarker findCall() { + for(int i=sp-1;i>=0;i--) if(stack[i] instanceof CallMarker) return (CallMarker) stack[i]; + return null; + } + void grow() throws JSExn { + if(stack.length >= MAX_STACK_SIZE) throw new JSExn("Stack overflow"); + JS[] stack2 = new JS[stack.length * 2]; + System.arraycopy(stack,0,stack2,0,stack.length); + } + + void backtrace(JSExn e) { + for(int i=sp-1;i>=0;i--) { + if (stack[i] instanceof CallMarker) { + CallMarker cm = (CallMarker)stack[i]; + if(cm.f == null) break; + String s = cm.f.sourceName + ":" + cm.f.line[cm.pc-1]; + if(cm instanceof Interpreter.TrapMarker) + s += " (trap on " + JS.debugToString(((Interpreter.TrapMarker)cm).t.key) + ")"; + e.addBacktrace(s); + } + } + } + } } diff --git a/src/org/ibex/js/JS.java b/src/org/ibex/js/JS.java index 9aa9bd4..538fc1a 100644 --- a/src/org/ibex/js/JS.java +++ b/src/org/ibex/js/JS.java @@ -6,50 +6,115 @@ import java.io.*; import java.util.*; /** The minimum set of functionality required for objects which are manipulated by JavaScript */ -public class JS /*extends org.ibex.util.BalancedTree*/ implements Serializable { +public abstract class JS { + public static final JS METHOD = new JS() { }; - public static final long serialVersionUID = 572634985343962922L; - public static boolean checkAssertions = false; - - public static final Object METHOD = new Object(); - public final JS unclone() { return _unclone(); } - public Enumeration keys() throws JSExn { return entries == null ? emptyEnumeration : entries.keys(); } - public Object get(Object key) throws JSExn { return entries == null ? null : entries.get(key, null); } - public void put(Object key, Object val) throws JSExn { (entries==null?entries=new Hash():entries).put(key,null,val); } - public Object callMethod(Object method, Object a0, Object a1, Object a2, Object[] rest, int nargs) throws JSExn { - throw new JSExn("attempted to call the null value (method "+method+")"); - } - public Object call(Object a0, Object a1, Object a2, Object[] rest, int nargs) throws JSExn { + public JS.Enumeration keys() throws JSExn { throw new JSExn("you can't enumerate the keys of this object (class=" + getClass().getName() +")"); } + public JS get(JS key) throws JSExn { return null; } + public void put(JS key, JS val) throws JSExn { throw new JSExn("" + key + " is read only (class=" + getClass().getName() +")"); } + + public JS callMethod(JS method, JS a0, JS a1, JS a2, JS[] rest, int nargs) throws JSExn { + throw new JSExn("method not found (" + JS.debugToString(method) + ")"); + } + public JS call(JS a0, JS a1, JS a2, JS[] rest, int nargs) throws JSExn { throw new JSExn("you cannot call this object (class=" + this.getClass().getName() +")"); } - + public InputStream getInputStream() throws IOException { + throw new IOException("this object doesn't have a stream associated with it " + getClass().getName() + ")"); + } + + public final JS unclone() { return _unclone(); } + public final JS jsclone() throws JSExn { return new Clone(this); } + public final boolean hasTrap(JS key) { return getTrap(key) != null; } + public final boolean equals(Object o) { return this == o || ((o instanceof JS) && jsequals((JS)o)); } + // Discourage people from using toString() + public final String toString() { return "JS Object [class=" + getClass().getName() + "]"; } + + // Package private methods + Trap getTrap(JS key) { return null; } + void putTrap(JS key, Trap value) throws JSExn { throw new JSExn("traps cannot be placed on this object (class=" + this.getClass().getName() +")"); } + String coerceToString() throws JSExn { throw new JSExn("can't coerce to a string (class=" + getClass().getName() +")"); } + boolean jsequals(JS o) { return this == o; } JS _unclone() { return this; } - public static class Cloneable extends JS { - public Object jsclone() throws JSExn { - return new Clone(this); + + public static class O extends JS implements Cloneable { + private Hash entries; + + public Enumeration keys() throws JSExn { return entries == null ? (Enumeration)EMPTY_ENUMERATION : (Enumeration)new JavaEnumeration(null,entries.keys()); } + public JS get(JS key) throws JSExn { return entries == null ? null : (JS)entries.get(key, null); } + public void put(JS key, JS val) throws JSExn { (entries==null?entries=new Hash():entries).put(key,null,val); } + + /** retrieve a trap from the entries hash */ + final Trap getTrap(JS key) { + return entries == null ? null : (Trap)entries.get(key, Trap.class); } + + /** retrieve a trap from the entries hash */ + final void putTrap(JS key, Trap value) { + if (entries == null) entries = new Hash(); + entries.put(key, Trap.class, value); + } } - - public static class Clone extends JS.Cloneable { - protected JS.Cloneable clonee = null; - JS _unclone() { return clonee.unclone(); } - public JS.Cloneable getClonee() { return clonee; } - public Clone(JS.Cloneable clonee) { this.clonee = clonee; } - public boolean equals(Object o) { - if (!(o instanceof JS)) return false; - return unclone() == ((JS)o).unclone(); + + public interface Cloneable { } + + public static class Clone extends O { + protected final JS clonee; + public Clone(JS clonee) throws JSExn { + if(!(clonee instanceof Cloneable)) throw new JSExn("" + clonee.getClass().getName() + " isn't cloneable"); + this.clonee = clonee; } + JS _unclone() { return clonee.unclone(); } + boolean jsequals(JS o) { return clonee.jsequals(o); } + public Enumeration keys() throws JSExn { return clonee.keys(); } - public Object get(Object key) throws JSExn { return clonee.get(key); } - public void put(Object key, Object val) throws JSExn { clonee.put(key, val); } - public Object callMethod(Object method, Object a0, Object a1, Object a2, Object[] rest, int nargs) throws JSExn { - return clonee.callMethod(method, a0, a1, a2, rest, nargs); - } - public Object call(Object a0, Object a1, Object a2, Object[] rest, int nargs) throws JSExn { + public final JS get(JS key) throws JSExn { return clonee.get(key); } + public final void put(JS key, JS val) throws JSExn { clonee.put(key,val); } + public final JS callMethod(JS method, JS a0, JS a1, JS a2, JS[] rest, int nargs) throws JSExn { + return clonee.callMethod(method,a0,a1,a2,rest,nargs); + } + public JS call(JS a0, JS a1, JS a2, JS[] rest, int nargs) throws JSExn { return clonee.call(a0, a1, a2, rest, nargs); } + public InputStream getInputStream() throws IOException { return clonee.getInputStream(); } + // FIXME: This shouldn't be necessary (its for Ibex.Blessing) + public JS getClonee() { return clonee; } + } + + public static abstract class Enumeration extends JS { + final Enumeration parent; + boolean done; + public Enumeration(Enumeration parent) { this.parent = parent; } + protected abstract boolean _hasMoreElements(); + protected abstract JS _nextElement() throws JSExn; + + public final boolean hasMoreElements() { + if(!done && !_hasMoreElements()) done = true; + return !done ? true : parent != null ? parent.hasMoreElements() : false; + } + public final JS nextElement() throws JSExn { return !done ? _nextElement() : parent != null ? parent.nextElement() : null; } + + public JS get(JS key) throws JSExn { + //#switch(JS.toString(key)) + case "hasMoreElements": return B(hasMoreElements()); + case "nextElement": return nextElement(); + //#end + return super.get(key); + } } + public static class EmptyEnumeration extends Enumeration { + public EmptyEnumeration(Enumeration parent) { super(parent); } + protected boolean _hasMoreElements() { return false; } + protected JS _nextElement() { return null; } + } + public static class JavaEnumeration extends Enumeration { + private final java.util.Enumeration e; + public JavaEnumeration(Enumeration parent, java.util.Enumeration e) { super(parent); this.e = e; } + protected boolean _hasMoreElements() { return e.hasMoreElements(); } + protected JS _nextElement() { return (JS) e.nextElement(); } + } + // Static Interpreter Control Methods /////////////////////////////////////////////////////////////// /** log a message with the current JavaScript sourceName/line */ @@ -65,18 +130,30 @@ public class JS /*extends org.ibex.util.BalancedTree*/ implements Serializable { public static UnpauseCallback pause() throws NotPauseableException { Interpreter i = Interpreter.current(); if (i.pausecount == -1) throw new NotPauseableException(); + boolean get; + switch(i.f.op[i.pc]) { + case Tokens.RETURN: case ByteCodes.PUT: get = false; break; + case ByteCodes.GET: case ByteCodes.CALL: get = true; break; + default: throw new Error("should never happen"); + } i.pausecount++; - return new JS.UnpauseCallback(i); + return new JS.UnpauseCallback(i,get); } public static class UnpauseCallback implements Task { - Interpreter i; - UnpauseCallback(Interpreter i) { this.i = i; } - public void perform() throws JSExn { unpause(null); } - public void unpause(Object o) throws JSExn { - // FIXME: if o instanceof JSExn, throw it into the JSworld - i.stack.push(o); - i.resume(); + private Interpreter i; + private boolean get; + UnpauseCallback(Interpreter i, boolean get) { this.i = i; this.get = get; } + public void perform() throws JSExn { unpause(); } + public JS unpause() throws JSExn { return unpause((JS)null); } + public JS unpause(JS o) throws JSExn { + if (o == JS.METHOD) throw new JSExn("can't return a method to a paused context"); + if(get) i.stack.push(o); + return i.resume(); + } + public JS unpause(JSExn e) throws JSExn { + i.catchException(e); + return i.resume(); } } @@ -85,167 +162,156 @@ public class JS /*extends org.ibex.util.BalancedTree*/ implements Serializable { // Static Helper Methods /////////////////////////////////////////////////////////////////////////////////// /** coerce an object to a Boolean */ - public static boolean toBoolean(Object o) { - if (o == null) return false; - if (o instanceof Boolean) return ((Boolean)o).booleanValue(); - if (o instanceof Long) return ((Long)o).longValue() != 0; - if (o instanceof Integer) return ((Integer)o).intValue() != 0; - if (o instanceof Number) { - double d = ((Number) o).doubleValue(); - // NOTE: d == d is a test for NaN. It should be faster than Double.isNaN() - return d != 0.0 && d == d; - } - if (o instanceof String) return ((String)o).length() != 0; + public static boolean toBoolean(JS o) { + if(o == null) return false; + if(o instanceof JSNumber) return ((JSNumber)o).toBoolean(); + if(o instanceof JSString) return ((JSString)o).s.length() != 0; return true; } - - /** coerce an object to a Long */ - public static long toLong(Object o) { return toNumber(o).longValue(); } - - /** coerce an object to an Int */ - public static int toInt(Object o) { return toNumber(o).intValue(); } - - /** coerce an object to a Double */ - public static double toDouble(Object o) { return toNumber(o).doubleValue(); } - + //#repeat long/int/double/float toLong/toInt/toDouble/toFloat Long/Integer/Double/Float parseLong/parseInt/parseDouble/parseFloat /** coerce an object to a Number */ - public static Number toNumber(Object o) { - if (o == null) return ZERO; - if (o instanceof Number) return ((Number)o); - - // NOTE: There are about 3 pages of rules in ecma262 about string to number conversions - // We aren't even close to following all those rules. We probably never will be. - if (o instanceof String) try { return N((String)o); } catch (NumberFormatException e) { return N(Double.NaN); } - if (o instanceof Boolean) return ((Boolean)o).booleanValue() ? N(1) : ZERO; - throw new Error("toNumber() got object of type " + o.getClass().getName() + " which we don't know how to handle"); + public static long toLong(JS o) throws JSExn { + if(o == null) return 0; + if(o instanceof JSNumber) return ((JSNumber)o).toLong(); + if(o instanceof JSString) return Long.parseLong(o.coerceToString()); + throw new JSExn("can't coerce a " + o.getClass().getName() + " to a number"); } - - /** coerce an object to a String */ - public static String toString(Object o) { + //#end + + public static String toString(JS o) throws JSExn { if(o == null) return "null"; - if(o instanceof String) return (String) o; - if(o instanceof Integer || o instanceof Long || o instanceof Boolean) return o.toString(); - if(o instanceof JSArray) return o.toString(); - if(o instanceof JSDate) return o.toString(); - if(o instanceof Double || o instanceof Float) { - double d = ((Number)o).doubleValue(); - if((int)d == d) return Integer.toString((int)d); - return o.toString(); + return o.coerceToString(); + } + + public static String debugToString(JS o) { + try { return toString(o); } + catch(JSExn e) { return o.toString(); } + } + + public static boolean isInt(JS o) { + if(o == null) return true; + if(o instanceof JSNumber.I) return true; + if(o instanceof JSNumber.B) return false; + if(o instanceof JSNumber) { + JSNumber n = (JSNumber) o; + return n.toInt() == n.toDouble(); } - if (o instanceof JS) return ((JS)o).coerceToString(); // HACK for now, this will probably go away - throw new RuntimeException("can't coerce "+o+" [" + o.getClass().getName() + "] to type String."); + if(o instanceof JSString) { + String s = ((JSString)o).s; + for(int i=0;i '9') return false; + return true; + } + return false; } - - public String coerceToString() { - throw new RuntimeException("can't coerce "+this+" [" + getClass().getName() + "] to type String."); + + public static boolean isString(JS o) { + if(o instanceof JSString) return true; + return false; } - + // Instance Methods //////////////////////////////////////////////////////////////////// - - public static final Integer ZERO = new Integer(0); - // this gets around a wierd fluke in the Java type checking rules for ?..: - public static final Object T = Boolean.TRUE; - public static final Object F = Boolean.FALSE; - - public static final Boolean B(boolean b) { return b ? Boolean.TRUE : Boolean.FALSE; } - public static final Boolean B(int i) { return i==0 ? Boolean.FALSE : Boolean.TRUE; } - public static final Number N(String s) { return s.indexOf('.') == -1 ? N(Integer.parseInt(s)) : new Double(s); } - public static final Number N(double d) { return (int)d == d ? N((int)d) : new Double(d); } - public static final Number N(long l) { return N((int)l); } - - private static final Integer[] smallIntCache = new Integer[65535 / 4]; - private static final Integer[] largeIntCache = new Integer[65535 / 4]; - public static final Number N(int i) { - Integer ret = null; + public final static JS NaN = new JSNumber.D(Double.NaN); + public final static JS ZERO = new JSNumber.I(0); + public final static JS ONE = new JSNumber.I(1); + public final static JS MATH = new JSMath(); + + public static final JS T = new JSNumber.B(true); + public static final JS F = new JSNumber.B(false); + + public static final JS B(boolean b) { return b ? T : F; } + public static final JS B(int i) { return i==0 ? F : T; } + + private static final int CACHE_SIZE = 65536 / 4; // must be a power of two + private static final JSString[] stringCache = new JSString[CACHE_SIZE]; + public static final JS S(String s) { + if(s == null) return null; + int slot = s.hashCode()&(CACHE_SIZE-1); + JSString ret = stringCache[slot]; + if(ret == null || !ret.s.equals(s)) stringCache[slot] = ret = new JSString(s); + return ret; + } + public static final JS S(String s, boolean intern) { return intern ? JSString.intern(s) : S(s); } + + public static final JS N(double d) { return new JSNumber.D(d); } + public static final JS N(long l) { return new JSNumber.L(l); } + + public static final JS N(Number n) { + if(n instanceof Integer) return N(n.intValue()); + if(n instanceof Long) return N(n.longValue()); + return N(n.doubleValue()); + } + + private static final JSNumber.I[] smallIntCache = new JSNumber.I[CACHE_SIZE]; + private static final JSNumber.I[] largeIntCache = new JSNumber.I[CACHE_SIZE]; + public static final JS N(int i) { + JSNumber.I ret = null; int idx = i + smallIntCache.length / 2; - if (idx < smallIntCache.length && idx > 0) { + if (idx < CACHE_SIZE && idx > 0) { ret = smallIntCache[idx]; if (ret != null) return ret; } - else ret = largeIntCache[Math.abs(idx % largeIntCache.length)]; - if (ret == null || ret.intValue() != i) { - ret = new Integer(i); + else ret = largeIntCache[Math.abs(idx % CACHE_SIZE)]; + if (ret == null || ret.i != i) { + ret = new JSNumber.I(i); if (idx < smallIntCache.length && idx > 0) smallIntCache[idx] = ret; - else largeIntCache[Math.abs(idx % largeIntCache.length)] = ret; + else largeIntCache[Math.abs(idx % CACHE_SIZE)] = ret; } return ret; } - private static Enumeration emptyEnumeration = new Enumeration() { - public boolean hasMoreElements() { return false; } - public Object nextElement() { throw new NoSuchElementException(); } - }; + private static Enumeration EMPTY_ENUMERATION = new EmptyEnumeration(null); - private Hash entries = null; - public static JS fromReader(String sourceName, int firstLine, Reader sourceCode) throws IOException { - return JSFunction._fromReader(sourceName, firstLine, sourceCode); + return Parser.fromReader(sourceName, firstLine, sourceCode); } - // HACK: caller can't know if the argument is a JSFunction or not... - public static JS cloneWithNewParentScope(JS j, JSScope s) { - return ((JSFunction)j)._cloneWithNewParentScope(s); - } - - // HACK - public static Vec getFormalArgs(JS j) { return ((JSFunction)j).formalArgs; } - - // HACK - public static JSScope getParentScope(JS j) { return ((JSFunction)j).parentScope; } - - public static Object eval(JS j) throws JSExn { - Interpreter cx = new Interpreter((JSFunction)j, false, new JSArray(), false); - return cx.resume(); + public static JS cloneWithNewGlobalScope(JS js, JS s) { + if(js instanceof JSFunction) + return ((JSFunction)js)._cloneWithNewParentScope(new JSScope.Top(s)); + else + return js; } // Trap support ////////////////////////////////////////////////////////////////////////////// - /** override and return true to allow placing traps on this object. - * if isRead true, this is a read trap, otherwise write trap - **/ - protected boolean isTrappable(Object name, boolean isRead) { return true; } - /** performs a put, triggering traps if present; traps are run in an unpauseable interpreter */ - public void putAndTriggerTraps(Object key, Object value) throws JSExn { + public final void putAndTriggerTraps(JS key, JS value) throws JSExn { Trap t = getTrap(key); - if (t != null) t.invoke(value); - else put(key, value); + if(t == null || (t = t.writeTrap()) == null) put(key,value); + else new Interpreter(t,value,false).resume(); } /** performs a get, triggering traps if present; traps are run in an unpauseable interpreter */ - public Object getAndTriggerTraps(Object key) throws JSExn { + public final JS getAndTriggerTraps(JS key) throws JSExn { Trap t = getTrap(key); - if (t != null) return t.invoke(); - else return get(key); - } - - /** retrieve a trap from the entries hash */ - protected final Trap getTrap(Object key) { - return entries == null ? null : (Trap)entries.get(key, Trap.class); + if (t == null || (t = t.readTrap()) == null) return get(key); + else return new Interpreter(t,null,false).resume(); } - - /** retrieve a trap from the entries hash */ - protected final void putTrap(Object key, Trap value) { - if (entries == null) entries = new Hash(); - entries.put(key, Trap.class, value); + + public final JS justTriggerTraps(JS key, JS value) throws JSExn { + Trap t = getTrap(key); + if(t == null || (t = t.writeTrap()) == null) return value; + else return new Interpreter(t,value,true).resume(); } /** adds a trap, avoiding duplicates */ - protected final void addTrap(Object name, JSFunction f) throws JSExn { + // FIXME: This shouldn't be public, it is needed for a hack in Template.java + public void addTrap(JS key, JSFunction f) throws JSExn { if (f.numFormalArgs > 1) throw new JSExn("traps must take either one argument (write) or no arguments (read)"); boolean isRead = f.numFormalArgs == 0; - if (!isTrappable(name, isRead)) throw new JSExn("not allowed "+(isRead?"read":"write")+" trap on property: "+name); - for(Trap t = getTrap(name); t != null; t = t.next) if (t.f == f) return; - putTrap(name, new Trap(this, name.toString(), f, (Trap)getTrap(name))); + for(Trap t = getTrap(key); t != null; t = t.next) if (t.f == f) return; + putTrap(key, new Trap(this, key, f, (Trap)getTrap(key))); } /** deletes a trap, if present */ - protected final void delTrap(Object name, JSFunction f) { - Trap t = (Trap)getTrap(name); + // FIXME: This shouldn't be public, it is needed for a hack in Template.java + public void delTrap(JS key, JSFunction f) throws JSExn { + Trap t = (Trap)getTrap(key); if (t == null) return; - if (t.f == f) { putTrap(t.name, t.next); return; } + if (t.f == f) { putTrap(t.target, t.next); return; } for(; t.next != null; t = t.next) if (t.next.f == f) { t.next = t.next.next; return; } } diff --git a/src/org/ibex/js/JSArray.java b/src/org/ibex/js/JSArray.java index 07cdc40..7038726 100644 --- a/src/org/ibex/js/JSArray.java +++ b/src/org/ibex/js/JSArray.java @@ -5,15 +5,14 @@ import org.ibex.util.*; import java.util.*; /** A JavaScript JSArray */ -public class JSArray extends JS { - +public class JSArray extends JS.O { private static final Object NULL = new Object(); - private Vec vec = new Vec(); - + private final BalancedTree bt = new BalancedTree(); + public JSArray() { } public JSArray(int size) { setSize(size); } - private static int intVal(Object o) { + /*private static int intVal(Object o) { if (o instanceof Number) { int intVal = ((Number)o).intValue(); if (intVal == ((Number)o).doubleValue()) return intVal; @@ -23,10 +22,10 @@ public class JSArray extends JS { String s = (String)o; for(int i=0; i '9') return Integer.MIN_VALUE; return Integer.parseInt(s); - } + }*/ - public Object callMethod(Object method, Object a0, Object a1, Object a2, Object[] rest, int nargs) throws JSExn { - //#switch(method) + public JS callMethod(JS method, JS a0, JS a1, JS a2, JS[] rest, int nargs) throws JSExn { + //#switch(JS.toString(method)) case "pop": { int oldSize = size(); if(oldSize == 0) return null; @@ -61,13 +60,13 @@ public class JSArray extends JS { return super.callMethod(method, a0, a1, a2, rest, nargs); } - public Object get(Object key) throws JSExn { - int i = intVal(key); - if (i != Integer.MIN_VALUE) { + public JS get(JS key) throws JSExn { + if (isInt(key)) { + int i = toInt(key); if (i < 0 || i >= size()) return null; return elementAt(i); } - //#switch(key) + //#switch(JS.toString(key)) case "pop": return METHOD; case "reverse": return METHOD; case "toString": return METHOD; @@ -83,12 +82,9 @@ public class JSArray extends JS { return super.get(key); } - public void put(Object key, Object val) throws JSExn { - if (key.equals("length")) setSize(toInt(val)); - int i = intVal(key); - if (i == Integer.MIN_VALUE) - super.put(key, val); - else { + public void put(JS key, JS val) throws JSExn { + if (isInt(key)) { + int i = toInt(key); int oldSize = size(); if(i < oldSize) { setElementAt(val,i); @@ -96,16 +92,23 @@ public class JSArray extends JS { if(i > oldSize) setSize(i); insertElementAt(val,i); } + return; + } + if(isString(key)) { + if (JS.toString(key).equals("length")) { + setSize(JS.toInt(val)); + return; + } } + super.put(key,val); } - public Enumeration keys() { - return new Enumeration() { - private int n = size(); - public boolean hasMoreElements() { return n > 0; } - public Object nextElement() { - if(n == 0) throw new NoSuchElementException(); - return new Integer(--n); + public Enumeration keys() throws JSExn { + return new Enumeration(super.keys()) { + private int n = 0; + public boolean _hasMoreElements() { return n < size(); } + public JS _nextElement() { + return n >= size() ? null : JS.N(n++); } }; } @@ -118,57 +121,55 @@ public class JSArray extends JS { } public final int length() { return size(); } - public final Object elementAt(int i) { + public final JS elementAt(int i) { if(i < 0 || i >= size()) throw new ArrayIndexOutOfBoundsException(i); - Object o = vec.elementAt(i); - return o == NULL ? null : o; + Object o = bt.getNode(i); + return o == NULL ? (JS)null : (JS)o; } - public final void addElement(Object o) { - vec.insertElementAt(o==null ? NULL : o, size()); + public final void addElement(JS o) { + bt.insertNode(size(),o==null ? NULL : o); } - public final void setElementAt(Object o, int i) { + public final void setElementAt(JS o, int i) { if(i < 0 || i >= size()) throw new ArrayIndexOutOfBoundsException(i); - vec.setElementAt(o==null ? NULL : o,i); + bt.replaceNode(i,o==null ? NULL : o); } - public final void insertElementAt(Object o, int i) { + public final void insertElementAt(JS o, int i) { if(i < 0 || i > size()) throw new ArrayIndexOutOfBoundsException(i); - vec.insertElementAt(o==null ? NULL : o, i); + bt.insertNode(i,o==null ? NULL : o); } - public final Object removeElementAt(int i) { + public final JS removeElementAt(int i) { if(i < 0 || i >= size()) throw new ArrayIndexOutOfBoundsException(i); - Object o = vec.elementAt(i); - vec.removeElementAt(i); - return o == NULL ? null : o; + Object o = bt.deleteNode(i); + return o == NULL ? (JS)null : (JS)o; } - public final int size() { return vec.size(); } - public String typeName() { return "array"; } - - private Object join(String sep) { + public final int size() { return bt.treeSize(); } + + private JS join(String sep) throws JSExn { int length = size(); - if(length == 0) return ""; + if(length == 0) return JS.S(""); StringBuffer sb = new StringBuffer(64); int i=0; while(true) { - Object o = elementAt(i); + JS o = elementAt(i); if(o != null) sb.append(JS.toString(o)); if(++i == length) break; sb.append(sep); } - return sb.toString(); + return JS.S(sb.toString()); } // FEATURE: Implement this more efficiently - private Object reverse() { + private JS reverse() { int size = size(); if(size < 2) return this; Vec vec = toVec(); - vec.removeAllElements(); - for(int i=size-1,j=0;i>=0;i--,j++) insertElementAt(vec.elementAt(i),j); + bt.clear(); + for(int i=size-1,j=0;i>=0;i--,j++) insertElementAt((JS)vec.elementAt(i),j); return this; } - private Object slice(int start, int end) { + private JS slice(int start, int end) { int length = length(); if(start < 0) start = length+start; if(end < 0) end = length+end; @@ -181,37 +182,37 @@ public class JSArray extends JS { a.setElementAt(elementAt(start+i),i); return a; } - + private static final Vec.CompareFunc defaultSort = new Vec.CompareFunc() { public int compare(Object a, Object b) { - return JS.toString(a).compareTo(JS.toString(b)); + try { + return JS.toString((JS)a).compareTo(JS.toString((JS)b)); + } catch(JSExn e) { throw new JSExn.Wrapper(e); } } }; - private Object sort(Object tmp) throws JSExn { + private JS sort(JS tmp) throws JSExn { Vec vec = toVec(); - if(tmp instanceof JS) { - final JSArray funcArgs = new JSArray(2); - final JS jsFunc = (JS) tmp; - vec.sort(new Vec.CompareFunc() { - public int compare(Object a, Object b) { - try { - funcArgs.setElementAt(a,0); - funcArgs.setElementAt(b,1); - return JS.toInt(jsFunc.call(a, b, null, null, 2)); - } catch (Exception e) { - // FIXME - throw new JSRuntimeExn(e.toString()); + try { + if(tmp instanceof JS) { + final JS jsFunc = (JS) tmp; + vec.sort(new Vec.CompareFunc() { + public int compare(Object a, Object b) { + try { + return JS.toInt(jsFunc.call((JS)a, (JS)b, null, null, 2)); + } catch(JSExn e) { throw new JSExn.Wrapper(e); } } - } - }); - } else { - vec.sort(defaultSort); + }); + } else { + vec.sort(defaultSort); + } + } catch(JSExn.Wrapper e) { + throw e.refill(); } setFromVec(vec); return this; } - private Object splice(JSArray args) { + private JS splice(JSArray args) throws JSExn { int oldLength = length(); int start = JS.toInt(args.length() < 1 ? null : args.elementAt(0)); int deleteCount = JS.toInt(args.length() < 2 ? null : args.elementAt(1)); @@ -241,7 +242,25 @@ public class JSArray extends JS { return ret; } - public Vec toVec() { return (Vec)vec.clone(); } - public void setFromVec(Vec vec) { this.vec = (Vec)vec.clone(); } - public String toString() { return JS.toString(join(",")); } + protected Vec toVec() { + int count = size(); + Vec vec = new Vec(); + vec.setSize(count); + for(int i=0;i=0; i--) { - Object element = stack.elementAt(i); - if (element instanceof Interpreter.CallMarker) { - Interpreter.CallMarker cm = (Interpreter.CallMarker)element; - if (cm.f != null) - addBacktrace(cm.f.sourceName + ":" + cm.f.line[cm.pc-1]); - if (cm.scope != null && cm.scope instanceof Trap.TrapScope) - addBacktrace("trap on property \"" + ((Trap.TrapScope)cm.scope).t.name + "\""); - } - } + private JS js; + public JSExn(String s) { this(JS.S(s)); } + public JSExn(JS js) { this(js,null); } + public JSExn(JS js, Interpreter cx) { this.js = js; fill(cx); } + + private void fill(Interpreter cx) { + if(cx == null) cx = Interpreter.current(); + if(cx == null) return; + addBacktrace(cx.f.sourceName + ":" + cx.f.line[cx.pc]); + cx.stack.backtrace(this); } public void printStackTrace() { printStackTrace(System.err); } public void printStackTrace(PrintWriter pw) { @@ -38,12 +27,22 @@ public class JSExn extends Exception { for(int i=0; iunpauseable context. */ - public Object call(Object a0, Object a1, Object a2, Object[] rest, int nargs) throws JSExn { - JSArray args = new JSArray(); - if (nargs > 0) args.addElement(a0); - if (nargs > 1) args.addElement(a1); - if (nargs > 2) args.addElement(a2); - for(int i=3; i>>16) + " size: " + (n&0xffff)); } sb.append("\n"); } diff --git a/src/org/ibex/js/JSMath.java b/src/org/ibex/js/JSMath.java index 04828ed..6b442b6 100644 --- a/src/org/ibex/js/JSMath.java +++ b/src/org/ibex/js/JSMath.java @@ -3,51 +3,48 @@ package org.ibex.js; /** The JavaScript Math object */ -public class JSMath extends JS { +class JSMath extends JS { + private static final JS E = JS.N(java.lang.Math.E); + private static final JS PI = JS.N(java.lang.Math.PI); + private static final JS LN10 = JS.N(java.lang.Math.log(10)); + private static final JS LN2 = JS.N(java.lang.Math.log(2)); + private static final JS LOG10E = JS.N(1/java.lang.Math.log(10)); + private static final JS LOG2E = JS.N(1/java.lang.Math.log(2)); + private static final JS SQRT1_2 = JS.N(1/java.lang.Math.sqrt(2)); + private static final JS SQRT2 = JS.N(java.lang.Math.sqrt(2)); - public static JSMath singleton = new JSMath(); - - 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 Object callMethod(Object method, Object a0, Object a1, Object a2, Object[] rest, int nargs) throws JSExn { + public JS callMethod(JS method, JS a0, JS a1, JS a2, JS[] rest, int nargs) throws JSExn { switch(nargs) { case 0: { - //#switch(method) - case "random": return new Double(java.lang.Math.random()); + //#switch(JS.toString(method)) + case "random": return JS.N(java.lang.Math.random()); //#end break; } case 1: { - //#switch(method) - case "ceil": return new Long((long)java.lang.Math.ceil(toDouble(a0))); - case "floor": return new Long((long)java.lang.Math.floor(toDouble(a0))); - case "round": return new Long((long)java.lang.Math.round(toDouble(a0))); - case "abs": return new Double(java.lang.Math.abs(toDouble(a0))); - case "sin": return new Double(java.lang.Math.sin(toDouble(a0))); - case "cos": return new Double(java.lang.Math.cos(toDouble(a0))); - case "tan": return new Double(java.lang.Math.tan(toDouble(a0))); - case "asin": return new Double(java.lang.Math.asin(toDouble(a0))); - case "acos": return new Double(java.lang.Math.acos(toDouble(a0))); - case "atan": return new Double(java.lang.Math.atan(toDouble(a0))); - case "sqrt": return new Double(java.lang.Math.sqrt(toDouble(a0))); - case "exp": return new Double(java.lang.Math.exp(toDouble(a0))); - case "log": return new Double(java.lang.Math.log(toDouble(a0))); + //#switch(JS.toString(method)) + case "ceil": return JS.N((long)java.lang.Math.ceil(toDouble(a0))); + case "floor": return JS.N((long)java.lang.Math.floor(toDouble(a0))); + case "round": return JS.N((long)java.lang.Math.round(toDouble(a0))); + case "abs": return JS.N(java.lang.Math.abs(toDouble(a0))); + case "sin": return JS.N(java.lang.Math.sin(toDouble(a0))); + case "cos": return JS.N(java.lang.Math.cos(toDouble(a0))); + case "tan": return JS.N(java.lang.Math.tan(toDouble(a0))); + case "asin": return JS.N(java.lang.Math.asin(toDouble(a0))); + case "acos": return JS.N(java.lang.Math.acos(toDouble(a0))); + case "atan": return JS.N(java.lang.Math.atan(toDouble(a0))); + case "sqrt": return JS.N(java.lang.Math.sqrt(toDouble(a0))); + case "exp": return JS.N(java.lang.Math.exp(toDouble(a0))); + case "log": return JS.N(java.lang.Math.log(toDouble(a0))); //#end break; } case 2: { - //#switch(method) - case "min": return new Double(java.lang.Math.min(toDouble(a0), toDouble(a1))); - case "max": return new Double(java.lang.Math.max(toDouble(a0), toDouble(a1))); - case "pow": return new Double(java.lang.Math.pow(toDouble(a0), toDouble(a1))); - case "atan2": return new Double(java.lang.Math.atan2(toDouble(a0), toDouble(a1))); + //#switch(JS.toString(method)) + case "min": return JS.N(java.lang.Math.min(toDouble(a0), toDouble(a1))); + case "max": return JS.N(java.lang.Math.max(toDouble(a0), toDouble(a1))); + case "pow": return JS.N(java.lang.Math.pow(toDouble(a0), toDouble(a1))); + case "atan2": return JS.N(java.lang.Math.atan2(toDouble(a0), toDouble(a1))); //#end break; } @@ -55,10 +52,8 @@ public class JSMath extends JS { return super.callMethod(method, a0, a1, a2, rest, nargs); } - public void put(Object key, Object val) { } - - public Object get(Object key) throws JSExn { - //#switch(key) + public JS get(JS key) throws JSExn { + //#switch(JS.toString(key)) case "E": return E; case "LN10": return LN10; case "LN2": return LN2; diff --git a/src/org/ibex/js/JSReflection.java b/src/org/ibex/js/JSReflection.java index 364792d..52f7f9c 100644 --- a/src/org/ibex/js/JSReflection.java +++ b/src/org/ibex/js/JSReflection.java @@ -9,12 +9,12 @@ import java.lang.reflect.*; /** Automatic JS-ification via Reflection (not for use in the core) */ public class JSReflection extends JS { - public static Object wrap(Object o) throws JSExn { + public static JS wrap(Object o) throws JSExn { if (o == null) return null; - if (o instanceof String) return o; - if (o instanceof Boolean) return o; - if (o instanceof Number) return o; - if (o instanceof JS) return o; + if (o instanceof String) return JS.S((String)o); + if (o instanceof Boolean) return JS.B(((Boolean)o).booleanValue()); + if (o instanceof Number) return JS.N((Number)o); + if (o instanceof JS) return (JS)o; if (o instanceof Object[]) { // FIXME: get element type here } @@ -24,15 +24,24 @@ public class JSReflection extends JS { public static class Array extends JS { final Object[] arr; public Array(Object[] arr) { this.arr = arr; } - public Enumeration keys() throws JSExn { return new CounterEnumeration(arr.length); } - public Object get(Object key) throws JSExn { return wrap(arr[toInt(key)]); } - public void put(Object key, Object val) throws JSExn { throw new JSExn("can't write to org.ibex.js.Reflection.Array's"); } + // FEATURE: Add a JSCounterEnumeration + public Enumeration keys() throws JSExn { + return new Enumeration(null) { + private int n = 0; + public boolean _hasMoreElements() { return n < arr.length; } + public JS _nextElement() { + return n >= arr.length ? null : JS.N(n++); + } + }; + } + public JS get(JS key) throws JSExn { return wrap(arr[toInt(key)]); } + public void put(JS key, JS val) throws JSExn { throw new JSExn("can't write to org.ibex.js.Reflection.Array's"); } } // FIXME public static class Hash { } // FIXME public Enumeration keys() throws JSExn { } - public Object get(Object key) throws JSExn { + public JS get(JS key) throws JSExn { String k = toString(key); try { Field f = this.getClass().getField(k); @@ -40,6 +49,7 @@ public class JSReflection extends JS { } catch (NoSuchFieldException nfe) { } catch (IllegalAccessException nfe) { } catch (SecurityException nfe) { } + try { Method[] methods = this.getClass().getMethods(); for(int i=0; i= s.length()) { lastIndex = 0; return null; } GnuRegexp.REMatch match = re.getMatch(s,start); @@ -48,7 +51,7 @@ public class JSRegexp extends JS { return match == null ? null : matchToExecResult(match,re,s); } case "test": { - String s = (String)a0; + String s = JS.toString(a0); if (!global) return B(re.getMatch(s) != null); int start = global ? lastIndex : 0; if(start < 0 || start >= s.length()) { lastIndex = 0; return null; } @@ -56,14 +59,14 @@ public class JSRegexp extends JS { lastIndex = match != null ? s.length() : match.getEndIndex(); return B(match != null); } - case "toString": return toString(a0); + case "toString": return JS.S(a0.coerceToString()); case "stringMatch": return stringMatch(a0,a1); case "stringSearch": return stringSearch(a0,a1); //#end break; } case 2: { - //#switch(method) + //#switch(JS.toString(method)) case "stringReplace": return stringReplace(a0, a1,a2); //#end break; @@ -72,84 +75,89 @@ public class JSRegexp extends JS { return super.callMethod(method, a0, a1, a2, rest, nargs); } - public Object get(Object key) throws JSExn { - //#switch(key) + public JS get(JS key) throws JSExn { + //#switch(JS.toString(key)) case "exec": return METHOD; case "test": return METHOD; case "toString": return METHOD; case "lastIndex": return N(lastIndex); + case "source": return pattern; + case "global": return JS.B(global); + case "ignoreCase": return B(flags & GnuRegexp.RE.REG_ICASE); + case "multiline": return B(flags & GnuRegexp.RE.REG_MULTILINE); //#end return super.get(key); } - public void put(Object key, Object value) throws JSExn { - if(key.equals("lastIndex")) lastIndex = JS.toNumber(value).intValue(); + public void put(JS key, JS value) throws JSExn { + if(JS.isString(key)) { + if(JS.toString(key).equals("lastIndex")) { + lastIndex = JS.toInt(value); + return; + } + } super.put(key,value); } - private static Object matchToExecResult(GnuRegexp.REMatch match, GnuRegexp.RE re, String s) { + private static JS matchToExecResult(GnuRegexp.REMatch match, GnuRegexp.RE re, String s) { try { - JS ret = new JS(); - ret.put("index", N(match.getStartIndex())); - ret.put("input",s); + JS ret = new JS.O(); + ret.put(JS.S("index"), N(match.getStartIndex())); + ret.put(JS.S("input"),JS.S(s)); int n = re.getNumSubs(); - ret.put("length", N(n+1)); - ret.put("0",match.toString()); - for(int i=1;i<=n;i++) ret.put(Integer.toString(i),match.toString(i)); + ret.put(JS.S("length"), N(n+1)); + ret.put(ZERO,JS.S(match.toString())); + for(int i=1;i<=n;i++) ret.put(JS.N(i),JS.S(match.toString(i))); return ret; } catch (JSExn e) { throw new Error("this should never happen"); } } - public String toString() { - try { - 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(); - } catch (JSExn e) { - throw new Error("this should never happen"); - } + String coerceToString() { + StringBuffer sb = new StringBuffer(); + sb.append('/'); + sb.append(pattern); + sb.append('/'); + if(global) sb.append('g'); + if((flags & GnuRegexp.RE.REG_ICASE) != 0) sb.append('i'); + if((flags & GnuRegexp.RE.REG_MULTILINE) != 0) sb.append('m'); + return sb.toString(); } - public static Object stringMatch(Object o, Object arg0) throws JSExn { - String s = o.toString(); + static JS stringMatch(JS o, JS arg0) throws JSExn { + String s = JS.toString(o); GnuRegexp.RE re; JSRegexp regexp = null; if(arg0 instanceof JSRegexp) { regexp = (JSRegexp) arg0; re = regexp.re; } else { - re = newRE(arg0.toString(),0); + re = newRE(JS.toString(arg0),0); } if(regexp == null) { GnuRegexp.REMatch match = re.getMatch(s); return matchToExecResult(match,re,s); } - if(!regexp.global) return regexp.callMethod("exec", s, null, null, null, 1); + if(!regexp.global) return regexp.callMethod(JS.S("exec"), o, null, null, null, 1); JSArray ret = new JSArray(); GnuRegexp.REMatch[] matches = re.getAllMatches(s); - for(int i=0;i 0 ? matches[matches.length-1].getEndIndex() : s.length(); return ret; } - public static Object stringSearch(Object o, Object arg0) throws JSExn { - String s = o.toString(); - GnuRegexp.RE re = arg0 instanceof JSRegexp ? ((JSRegexp)arg0).re : newRE(arg0.toString(),0); + static JS stringSearch(JS o, JS arg0) throws JSExn { + String s = JS.toString(o); + GnuRegexp.RE re = arg0 instanceof JSRegexp ? ((JSRegexp)arg0).re : newRE(JS.toString(arg0),0); GnuRegexp.REMatch match = re.getMatch(s); return match == null ? N(-1) : N(match.getStartIndex()); } - public static Object stringReplace(Object o, Object arg0, Object arg1) throws JSExn { - String s = o.toString(); + static JS stringReplace(JS o, JS arg0, JS arg1) throws JSExn { + String s = JS.toString(o); GnuRegexp.RE re; JSFunction replaceFunc = null; String replaceString = null; @@ -163,7 +171,7 @@ public class JSRegexp extends JS { if(arg1 instanceof JSFunction) replaceFunc = (JSFunction) arg1; else - replaceString = JS.toString(arg1.toString()); + replaceString = JS.toString(arg1); GnuRegexp.REMatch[] matches; if(regexp != null && regexp.global) { matches = re.getAllMatches(s); @@ -191,32 +199,32 @@ public class JSRegexp extends JS { if(replaceFunc != null) { int n = (regexp == null ? 0 : re.getNumSubs()); int numArgs = 3 + n; - Object[] rest = new Object[numArgs - 3]; - Object a0 = match.toString(); - Object a1 = null; - Object a2 = null; + JS[] rest = new JS[numArgs - 3]; + JS a0 = JS.S(match.toString()); + JS a1 = null; + JS a2 = null; for(int j=1;j<=n;j++) switch(j) { - case 1: a1 = match.toString(j); break; - case 2: a2 = match.toString(j); break; - default: rest[j - 3] = match.toString(j); break; + case 1: a1 = JS.S(match.toString(j)); break; + case 2: a2 = JS.S(match.toString(j)); break; + default: rest[j - 3] = JS.S(match.toString(j)); break; } switch(numArgs) { case 3: a1 = N(match.getStartIndex()); - a2 = s; + a2 = JS.S(s); break; case 4: a2 = N(match.getStartIndex()); - rest[0] = s; + rest[0] = JS.S(s); break; default: rest[rest.length - 2] = N(match.getStartIndex()); - rest[rest.length - 1] = s; + rest[rest.length - 1] = JS.S(s); } // note: can't perform pausing operations in here - sb.append((String)replaceFunc.call(a0, a1, a2, rest, numArgs)); + sb.append(JS.toString(replaceFunc.call(a0, a1, a2, rest, numArgs))); } else { sb.append(mySubstitute(match,replaceString,s)); @@ -224,7 +232,7 @@ public class JSRegexp extends JS { } int end = matches.length == 0 ? 0 : matches[matches.length-1].getEndIndex(); sb.append(sa,end,sa.length-end); - return sb.toString(); + return JS.S(sb.toString()); } private static String mySubstitute(GnuRegexp.REMatch match, String s, String source) { @@ -269,7 +277,8 @@ public class JSRegexp extends JS { } - public static Object stringSplit(String s, Object arg0, Object arg1, int nargs) { + static JS stringSplit(JS s_, JS arg0, JS arg1, int nargs) throws JSExn { + String s = JS.toString(s_); int limit = nargs < 2 ? Integer.MAX_VALUE : JS.toInt(arg1); if(limit < 0) limit = Integer.MAX_VALUE; if(limit == 0) return new JSArray(); @@ -284,7 +293,7 @@ public class JSRegexp extends JS { regexp = (JSRegexp) arg0; re = regexp.re; } else { - sep = arg0.toString(); + sep = JS.toString(arg0); } // special case this for speed. additionally, the code below doesn't properly handle @@ -292,7 +301,7 @@ public class JSRegexp extends JS { if(sep != null && sep.length()==0) { int len = s.length(); for(int i=0;i 36)) return NaN; + if(radix != 0 && (radix < 2 || radix > 36)) return JS.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; @@ -115,7 +145,7 @@ public class JSScope extends JS { } } if(radix == 0) radix = 10; - if(length == start || Character.digit(s.charAt(start),radix) == -1) return NaN; + if(length == start || Character.digit(s.charAt(start),radix) == -1) return JS.NaN; // try the fast way first try { String s2 = start == 0 ? s : s.substring(start); @@ -126,14 +156,14 @@ public class JSScope extends JS { int digit = Character.digit(s.charAt(i),radix); if(digit < 0) break; n = n*radix + digit; - if(n < 0) return NaN; // overflow; + if(n < 0) return JS.NaN; // overflow; } if(n <= Integer.MAX_VALUE) return JS.N(sign*(int)n); return JS.N((long)sign*n); } - private Object parseFloat(Object arg) { - String s = (String)arg; + private JS parseFloat(JS arg) throws JSExn { + String s = JS.toString(arg); int start = 0; int length = s.length(); while(start < length && Character.isWhitespace(s.charAt(0))) start++; @@ -142,11 +172,11 @@ public class JSScope extends JS { // trailing garbage while(start < end) { try { - return JS.N(s.substring(start,length)); + return JS.N(new Double(s.substring(start,length))); } catch(NumberFormatException e) { } end--; } - return NaN; + return JS.NaN; } } } diff --git a/src/org/ibex/js/Lexer.java b/src/org/ibex/js/Lexer.java index ac6f6e1..42ec2c7 100644 --- a/src/org/ibex/js/Lexer.java +++ b/src/org/ibex/js/Lexer.java @@ -137,6 +137,7 @@ class Lexer implements Tokens { case "implements": return RESERVED; case "instanceof": return RESERVED; case "synchronized": return RESERVED; + case "cascade": return CASCADE; //#end return -1; } @@ -194,8 +195,9 @@ class Lexer implements Tokens { } } - if (!isInteger) this.number = JS.N(dval); - else this.number = JS.N(longval); + if (!isInteger) this.number = new Double(dval); + else if(longval >= Integer.MIN_VALUE && longval <= Integer.MAX_VALUE) this.number = new Integer((int)longval); + else this.number = new Long(longval); return NUMBER; } diff --git a/src/org/ibex/js/Parser.java b/src/org/ibex/js/Parser.java index 9f62439..6293107 100644 --- a/src/org/ibex/js/Parser.java +++ b/src/org/ibex/js/Parser.java @@ -69,7 +69,7 @@ class Parser extends Lexer implements ByteCodes { // Constructors ////////////////////////////////////////////////////// - public Parser(Reader r, String sourceName, int line) throws IOException { super(r, sourceName, line); } + private Parser(Reader r, String sourceName, int line) throws IOException { super(r, sourceName, line); } /** for debugging */ public static void main(String[] s) throws IOException { @@ -78,7 +78,6 @@ class Parser extends Lexer implements ByteCodes { System.out.println(block); } - // Statics //////////////////////////////////////////////////////////// static byte[] precedence = new byte[MAX_TOKEN + 1]; @@ -133,8 +132,68 @@ class Parser extends Lexer implements ByteCodes { precedence[DOT] = precedence[LB] = precedence[LP] = precedence[INC] = precedence[DEC] = 15; } - + // Local variable management + Vec scopeStack = new Vec(); + static class ScopeInfo { + int base; + int end; + int newScopeInsn; + Hash mapping = new Hash(); + } + Hash globalCache = new Hash(); + JS scopeKey(String name) { + if(globalCache.get(name) != null) return null; + for(int i=scopeStack.size()-1;i>=0;i--) { + JS key = (JS)((ScopeInfo) scopeStack.elementAt(i)).mapping.get(name); + if(key != null) return key; + } + globalCache.put(name,Boolean.TRUE); + return null; + } + void scopeDeclare(String name) throws IOException { + ScopeInfo si = (ScopeInfo) scopeStack.lastElement(); + if(si.mapping.get(name) != null) throw pe("" + name + " already declared in this scope"); + si.mapping.put(name,JS.N(si.end++)); + globalCache.put(name,null); + } + void scopePush(JSFunction b) { + ScopeInfo prev = (ScopeInfo) scopeStack.lastElement(); + ScopeInfo si = new ScopeInfo(); + si.base = prev.end; + si.end = si.base; + si.newScopeInsn = b.size; + scopeStack.push(si); + b.add(parserLine, NEWSCOPE); + } + void scopePop(JSFunction b) { + ScopeInfo si = (ScopeInfo) scopeStack.pop(); + b.add(parserLine, OLDSCOPE); + b.set(si.newScopeInsn,JS.N((si.base<<16)|((si.end-si.base)<<0))); + } + + // Parsing Logic ///////////////////////////////////////////////////////// + + /** parse and compile a function */ + public static JSFunction fromReader(String sourceName, int firstLine, Reader sourceCode) throws IOException { + JSFunction ret = new JSFunction(sourceName, firstLine, null); + if (sourceCode == null) return ret; + Parser p = new Parser(sourceCode, sourceName, firstLine); + p.scopeStack.setSize(0); + p.scopeStack.push(new ScopeInfo()); + p.scopePush(ret); + while(true) { + //int s = ret.size; + if(p.peekToken() == -1) break; // FIXME: Check this logic one more time + p.parseStatement(ret, null); + //if (s == ret.size) break; + } + p.scopePop(ret); + if(p.scopeStack.size() != 1) throw new Error("scopeStack height mismatch"); + ret.add(-1, LITERAL, null); + ret.add(-1, RETURN); + return ret; + } /** gets a token and throws an exception if it is not code */ private void consume(int code) throws IOException { @@ -169,20 +228,18 @@ class Parser extends Lexer implements ByteCodes { case -1: throw pe("expected expression"); // all of these simply push values onto the stack - case NUMBER: b.add(parserLine, LITERAL, number); break; - case STRING: b.add(parserLine, LITERAL, string); break; + case NUMBER: b.add(parserLine, LITERAL, JS.N(number)); break; + case STRING: b.add(parserLine, LITERAL, JSString.intern(string)); break; case NULL: b.add(parserLine, LITERAL, null); break; - case TRUE: case FALSE: b.add(parserLine, LITERAL, JS.B(tok == TRUE)); break; + case TRUE: case FALSE: b.add(parserLine, LITERAL, tok == TRUE ? JS.T : JS.F); break; // (.foo) syntax case DOT: { consume(NAME); - b.add(parserLine, TOPSCOPE); - b.add(parserLine, LITERAL, ""); - b.add(parserLine, GET); - b.add(parserLine, LITERAL, string); - b.add(parserLine, GET); - continueExpr(b, minPrecedence); + b.add(parserLine, GLOBALSCOPE); + b.add(parserLine, GET, JS.S("",true)); + b.add(parserLine, LITERAL, JS.S(string,true)); + continueExprAfterAssignable(b,minPrecedence,null); break; } @@ -206,9 +263,17 @@ class Parser extends Lexer implements ByteCodes { consume(RB); break; } - case SUB: { // negative literal (like "3 * -1") - consume(NUMBER); - b.add(parserLine, LITERAL, JS.N(number.doubleValue() * -1)); + case SUB: case ADD: { + if(peekToken() == NUMBER) { // literal + consume(NUMBER); + b.add(parserLine, LITERAL, JS.N(number.doubleValue() * (tok == SUB ? -1 : 1))); + } else { // unary +/- operator + if(tok == SUB) b.add(parserLine, LITERAL, JS.ZERO); + // BITNOT has the same precedence as the unary +/- operators + startExpr(b,precedence[BITNOT]); + if(tok == ADD) b.add(parserLine, LITERAL, JS.ZERO); // HACK to force expr into a numeric context + b.add(parserLine, SUB); + } break; } case LP: { // grouping (not calling) @@ -219,18 +284,23 @@ class Parser extends Lexer implements ByteCodes { case INC: case DEC: { // prefix (not postfix) startExpr(b, precedence[tok]); int prev = b.size - 1; + boolean sg = b.get(prev) == SCOPEGET; if (b.get(prev) == GET && b.getArg(prev) != null) b.set(prev, LITERAL, b.getArg(prev)); else if(b.get(prev) == GET) b.pop(); - else + else if(!sg) throw pe("prefixed increment/decrement can only be performed on a valid assignment target"); - b.add(parserLine, GET_PRESERVE, Boolean.TRUE); + if(!sg) b.add(parserLine, GET_PRESERVE, Boolean.TRUE); b.add(parserLine, LITERAL, JS.N(1)); b.add(parserLine, tok == INC ? ADD : SUB, JS.N(2)); - b.add(parserLine, PUT, null); - b.add(parserLine, SWAP, null); - b.add(parserLine, POP, null); + if(sg) { + b.add(parserLine, SCOPEPUT, b.getArg(prev)); + } else { + b.add(parserLine, PUT, null); + b.add(parserLine, SWAP, null); + b.add(parserLine, POP, null); + } break; } case BANG: case BITNOT: case TYPEOF: { @@ -245,7 +315,7 @@ class Parser extends Lexer implements ByteCodes { if (peekToken() != NAME && peekToken() != STRING) throw pe("expected NAME or STRING"); getToken(); - b.add(parserLine, LITERAL, string); // grab the key + b.add(parserLine, LITERAL, JSString.intern(string)); // grab the key consume(COLON); startExpr(b, NO_COMMA); // grab the value b.add(parserLine, PUT); // put the value into the object @@ -258,11 +328,25 @@ class Parser extends Lexer implements ByteCodes { break; } case NAME: { - b.add(parserLine, TOPSCOPE); - b.add(parserLine, LITERAL, string); - continueExprAfterAssignable(b,minPrecedence); + JS varKey = scopeKey(string); + if(varKey == null) { + b.add(parserLine, GLOBALSCOPE); + b.add(parserLine, LITERAL, JSString.intern(string)); + } + continueExprAfterAssignable(b,minPrecedence,varKey); + break; + } + case CASCADE: { + if(peekToken() == ASSIGN) { + consume(ASSIGN); + startExpr(b, precedence[ASSIGN]); + b.add(parserLine, CASCADE, JS.T); + } else { + b.add(parserLine, CASCADE, JS.F); + } break; } + case FUNCTION: { consume(LP); int numArgs = 0; @@ -270,27 +354,20 @@ class Parser extends Lexer implements ByteCodes { b.add(parserLine, NEWFUNCTION, b2); // function prelude; arguments array is already on the stack - b2.add(parserLine, TOPSCOPE); - b2.add(parserLine, SWAP); - b2.add(parserLine, DECLARE, "arguments"); // declare arguments (equivalent to 'var arguments;') - b2.add(parserLine, SWAP); // set this.arguments and leave the value on the stack - b2.add(parserLine, PUT); + scopePush(b2); + scopeDeclare("arguments"); + b2.add(parserLine, SCOPEPUT,scopeKey("arguments")); while(peekToken() != RP) { // run through the list of argument names numArgs++; if (peekToken() == NAME) { consume(NAME); // a named argument - String varName = string; - b2.formalArgs.push(varName); + b2.add(parserLine, DUP); // dup the args array b2.add(parserLine, GET, JS.N(numArgs - 1)); // retrieve it from the arguments array - b2.add(parserLine, TOPSCOPE); - b2.add(parserLine, SWAP); - b2.add(parserLine, DECLARE, varName); // declare the name - b2.add(parserLine, SWAP); - b2.add(parserLine, PUT); - b2.add(parserLine, POP); // pop the value - b2.add(parserLine, POP); // pop the scope + scopeDeclare(string); + b2.add(parserLine, SCOPEPUT, scopeKey(string)); + b2.add(parserLine, POP); } if (peekToken() == RP) break; consume(COMMA); @@ -299,13 +376,13 @@ class Parser extends Lexer implements ByteCodes { b2.numFormalArgs = numArgs; b2.add(parserLine, POP); // pop off the arguments array - b2.add(parserLine, POP); // pop off TOPSCOPE if(peekToken() != LC) throw pe("JSFunctions must have a block surrounded by curly brackets"); parseBlock(b2, null); // the function body - + + scopePop(b2); b2.add(parserLine, LITERAL, null); // in case we "fall out the bottom", return NULL b2.add(parserLine, RETURN); @@ -356,12 +433,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(JSFunction b,int minPrecedence) throws IOException { + private void continueExprAfterAssignable(JSFunction b,int minPrecedence, JS varKey) throws IOException { int saveParserLine = parserLine; - _continueExprAfterAssignable(b,minPrecedence); + _continueExprAfterAssignable(b,minPrecedence,varKey); parserLine = saveParserLine; } - private void _continueExprAfterAssignable(JSFunction b,int minPrecedence) throws IOException { + private void _continueExprAfterAssignable(JSFunction b,int minPrecedence, JS varKey) 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]))) @@ -385,52 +462,71 @@ class Parser extends Lexer implements ByteCodes { */ case ASSIGN_BITOR: case ASSIGN_BITXOR: case ASSIGN_BITAND: case ASSIGN_LSH: case ASSIGN_RSH: case ASSIGN_URSH: case ASSIGN_MUL: case ASSIGN_DIV: case ASSIGN_MOD: case ASSIGN_ADD: case ASSIGN_SUB: case ADD_TRAP: case DEL_TRAP: { - if (tok != ADD_TRAP && tok != DEL_TRAP) b.add(parserLine, GET_PRESERVE); + if (tok != ADD_TRAP && tok != DEL_TRAP) + b.add(parserLine, varKey == null ? GET_PRESERVE : SCOPEGET, varKey); startExpr(b, precedence[tok]); if (tok != ADD_TRAP && tok != DEL_TRAP) { // tok-1 is always s/^ASSIGN_// (0 is BITOR, 1 is ASSIGN_BITOR, etc) b.add(parserLine, tok - 1, tok-1==ADD ? JS.N(2) : null); - b.add(parserLine, PUT); - b.add(parserLine, SWAP); - b.add(parserLine, POP); + if(varKey == null) { + b.add(parserLine, PUT); + b.add(parserLine, SWAP); + b.add(parserLine, POP); + } else { + b.add(parserLine, SCOPEPUT, varKey); + } } else { + if(varKey != null) throw pe("cannot place traps on local variables"); b.add(parserLine, tok); } break; } case INC: case DEC: { // postfix - b.add(parserLine, GET_PRESERVE, Boolean.TRUE); - b.add(parserLine, LITERAL, JS.N(1)); - b.add(parserLine, tok == INC ? ADD : SUB, JS.N(2)); - b.add(parserLine, PUT, null); - b.add(parserLine, SWAP, null); - b.add(parserLine, POP, null); - b.add(parserLine, LITERAL, JS.N(1)); - b.add(parserLine, tok == INC ? SUB : ADD, JS.N(2)); // undo what we just did, since this is postfix + if(varKey == null) { + b.add(parserLine, GET_PRESERVE, Boolean.TRUE); + b.add(parserLine, LITERAL, JS.N(1)); + b.add(parserLine, tok == INC ? ADD : SUB, JS.N(2)); + b.add(parserLine, PUT, null); + b.add(parserLine, SWAP, null); + b.add(parserLine, POP, null); + b.add(parserLine, LITERAL, JS.N(1)); + b.add(parserLine, tok == INC ? SUB : ADD, JS.N(2)); // undo what we just did, since this is postfix + } else { + b.add(parserLine, SCOPEGET, varKey); + b.add(parserLine, DUP); + b.add(parserLine, LITERAL, JS.ONE); + b.add(parserLine, tok == INC ? ADD : SUB, JS.N(2)); + b.add(parserLine, SCOPEPUT, varKey); + } break; } case ASSIGN: { startExpr(b, precedence[tok]); - b.add(parserLine, PUT); - b.add(parserLine, SWAP); - b.add(parserLine, POP); + if(varKey == null) { + b.add(parserLine, PUT); + b.add(parserLine, SWAP); + b.add(parserLine, POP); + } else { + b.add(parserLine, SCOPEPUT, varKey); + } break; } case LP: { - // Method calls are implemented by doing a GET_PRESERVE // first. If the object supports method calls, it will // return JS.METHOD - int n = parseArgs(b, 2); - b.add(parserLine, GET_PRESERVE); - b.add(parserLine, CALLMETHOD, JS.N(n)); + b.add(parserLine, varKey == null ? GET_PRESERVE : SCOPEGET, varKey); + int n = parseArgs(b); + b.add(parserLine, varKey == null ? CALLMETHOD : CALL, JS.N(n)); break; } default: { pushBackToken(); - if(b.get(b.size-1) == LITERAL && b.getArg(b.size-1) != null) + if(varKey != null) + b.add(parserLine, SCOPEGET, varKey); + else if(b.get(b.size-1) == LITERAL && b.getArg(b.size-1) != null) b.set(b.size-1,GET,b.getArg(b.size-1)); else b.add(parserLine, GET); @@ -467,7 +563,7 @@ class Parser extends Lexer implements ByteCodes { switch (tok) { case LP: { // invocation (not grouping) - int n = parseArgs(b, 1); + int n = parseArgs(b); b.add(parserLine, CALL, JS.N(n)); break; } @@ -507,14 +603,14 @@ class Parser extends Lexer implements ByteCodes { } else { consume(NAME); } - b.add(parserLine, LITERAL, string); - continueExprAfterAssignable(b,minPrecedence); + b.add(parserLine, LITERAL, JSString.intern(string)); + continueExprAfterAssignable(b,minPrecedence,null); break; } case LB: { // subscripting (not array constructor) startExpr(b, -1); consume(RB); - continueExprAfterAssignable(b,minPrecedence); + continueExprAfterAssignable(b,minPrecedence,null); break; } case HOOK: { @@ -545,14 +641,12 @@ class Parser extends Lexer implements ByteCodes { } // parse a set of comma separated function arguments, assume LP has already been consumed - // 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, int pushdown) throws IOException { + private int parseArgs(JSFunction b) throws IOException { int i = 0; while(peekToken() != RP) { i++; if (peekToken() != COMMA) { startExpr(b, NO_COMMA); - b.add(parserLine, SWAP, JS.N(pushdown)); if (peekToken() == RP) break; } consume(COMMA); @@ -605,22 +699,19 @@ class Parser extends Lexer implements ByteCodes { break; } case VAR: { - b.add(parserLine, TOPSCOPE); // push the current scope while(true) { consume(NAME); - b.add(parserLine, DECLARE, string); // declare it + String var = string; + scopeDeclare(var); if (peekToken() == ASSIGN) { // if there is an '=' after the variable name consume(ASSIGN); startExpr(b, NO_COMMA); - b.add(parserLine, PUT); // assign it + b.add(parserLine, SCOPEPUT, scopeKey(var)); // assign it b.add(parserLine, POP); // clean the stack - } else { - b.add(parserLine, POP); // pop the string pushed by declare - } + } if (peekToken() != COMMA) break; consume(COMMA); } - b.add(parserLine, POP); // pop off the topscope if ((mostRecentlyReadToken != RC || peekToken() == SEMI) && peekToken() != -1 && mostRecentlyReadToken != SEMI) consume(SEMI); break; } @@ -741,7 +832,7 @@ class Parser extends Lexer implements ByteCodes { // extended Ibex catch block: catch(e faultCode "foo.bar.baz") consume(NAME); b.add(parserLine, DUP); - b.add(parserLine, LITERAL, string); + b.add(parserLine, LITERAL, JSString.intern(string)); b.add(parserLine, GET); b.add(parserLine, DUP); b.add(parserLine, LITERAL, null); @@ -774,17 +865,12 @@ class Parser extends Lexer implements ByteCodes { } consume(RP); // the exception is on top of the stack; put it to the chosen name - b.add(parserLine, NEWSCOPE); - b.add(parserLine, TOPSCOPE); - b.add(parserLine, SWAP); - b.add(parserLine, LITERAL,exceptionVar); - b.add(parserLine, DECLARE); - b.add(parserLine, SWAP); - b.add(parserLine, PUT); - b.add(parserLine, POP); + scopePush(b); + scopeDeclare(exceptionVar); + b.add(parserLine, SCOPEPUT, scopeKey(exceptionVar)); b.add(parserLine, POP); parseBlock(b, null); - b.add(parserLine, OLDSCOPE); + scopePop(b); b.add(parserLine, JMP); catchEnds.addElement(new Integer(b.size-1)); @@ -841,70 +927,51 @@ class Parser extends Lexer implements ByteCodes { consume(RP); b.add(parserLine, PUSHKEYS); - b.add(parserLine, DUP); - b.add(parserLine, LITERAL, "length"); - b.add(parserLine, GET); - // Stack is now: n, keys, obj, ... int size = b.size; b.add(parserLine, LOOP); b.add(parserLine, POP); - // Stack is now: LoopMarker, n, keys, obj, ... - // NOTE: This will break if the interpreter ever becomes more strict - // and prevents bytecode from messing with the Markers - b.add(parserLine, SWAP, JS.N(3)); - // Stack is now: Tn, keys, obj, LoopMarker, ... - b.add(parserLine, LITERAL, JS.N(1)); - b.add(parserLine, SUB); - b.add(parserLine, DUP); - // Stack is now: index, keys, obj, LoopMarker - b.add(parserLine, LITERAL, JS.ZERO); - b.add(parserLine, LT); - // Stack is now index<0, index, keys, obj, LoopMarker, ... + b.add(parserLine,SWAP); // get the keys enumeration object on top + b.add(parserLine,DUP); + b.add(parserLine,GET,JS.S("hasMoreElements")); + int size2 = b.size; + b.add(parserLine,JT); + b.add(parserLine,SWAP); + b.add(parserLine,BREAK); + b.set(size2, JS.N(b.size - size2)); + b.add(parserLine,DUP); + b.add(parserLine,GET,JS.S("nextElement")); + + scopePush(b); - b.add(parserLine, JF, JS.N(5)); // if we're >= 0 jump 5 down (to NEWSCOPE) - // Move the LoopMarker back into place - this is sort of ugly - b.add(parserLine, SWAP, JS.N(3)); - b.add(parserLine, SWAP, JS.N(3)); - b.add(parserLine, SWAP, JS.N(3)); - // Stack is now: LoopMarker, -1, keys, obj, ... - b.add(parserLine, BREAK); + if(hadVar) scopeDeclare(varName); + JS varKey = scopeKey(varName); - b.add(parserLine, NEWSCOPE); - if(hadVar) { - b.add(parserLine, DECLARE, varName); - b.add(parserLine, POP); + if(varKey == null) { + b.add(parserLine,GLOBALSCOPE); + b.add(parserLine,SWAP); + b.add(parserLine, LITERAL, JSString.intern(varName)); + b.add(parserLine,SWAP); + b.add(parserLine,PUT); + b.add(parserLine,POP); + } else { + b.add(parserLine, SCOPEPUT, varKey); } - - // Stack is now: index, keys, obj, LoopMarker, ... - b.add(parserLine, GET_PRESERVE); // key, index, keys, obj, LoopMarker, ... - b.add(parserLine, TOPSCOPE); // scope, key, index, keys, obj, LoopMarker, ... - b.add(parserLine, SWAP); // key, scope, index, keys, obj, LoopMarker, ... - b.add(parserLine, LITERAL, varName); // varName, key, scope, index, keys, obj, LoopMaker, ... - b.add(parserLine, SWAP); // key, varName, scope, index, keys, obj, LoopMarker, ... - b.add(parserLine, PUT); // key, scope, index, keys, obj, LoopMarker, ... - b.add(parserLine, POP); // scope, index, keys, obj, LoopMarker - b.add(parserLine, POP); // index, keys, obj, LoopMarker, ... - // Move the LoopMarker back into place - this is sort of ugly - b.add(parserLine, SWAP, JS.N(3)); - b.add(parserLine, SWAP, JS.N(3)); - b.add(parserLine, SWAP, JS.N(3)); + b.add(parserLine,POP); // pop the put'ed value + b.add(parserLine,SWAP); // put CallMarker back into place parseStatement(b, null); - b.add(parserLine, OLDSCOPE); + scopePop(b); b.add(parserLine, CONTINUE); // jump here on break b.set(size, JS.N(b.size - size)); - b.add(parserLine, POP); // N - b.add(parserLine, POP); // KEYS - b.add(parserLine, POP); // OBJ - + b.add(parserLine, POP); } else { if (hadVar) pushBackToken(VAR, null); // yeah, this actually matters - b.add(parserLine, NEWSCOPE); // grab a fresh scope + scopePush(b); // grab a fresh scope parseStatement(b, null); // initializer JSFunction e2 = // we need to put the incrementor before the test @@ -912,7 +979,7 @@ class Parser extends Lexer implements ByteCodes { if (peekToken() != SEMI) startExpr(e2, -1); else - e2.add(parserLine, JSFunction.LITERAL, Boolean.TRUE); // handle the for(foo;;foo) case + e2.add(parserLine, JSFunction.LITERAL, JS.T); // handle the for(foo;;foo) case consume(SEMI); if (label != null) b.add(parserLine, LABEL, label); b.add(parserLine, LOOP); @@ -934,7 +1001,7 @@ class Parser extends Lexer implements ByteCodes { b.add(parserLine, CONTINUE); // if we fall out the bottom, CONTINUE b.set(size2 - 1, JS.N(b.size - size2 + 1)); // end of the loop - b.add(parserLine, OLDSCOPE); // get our scope back + scopePop(b); // get our scope back } break; } @@ -958,9 +1025,9 @@ class Parser extends Lexer implements ByteCodes { case LC: { // blocks are statements too pushBackToken(); - b.add(parserLine, NEWSCOPE); + scopePush(b); parseBlock(b, label); - b.add(parserLine, OLDSCOPE); + scopePop(b); break; } diff --git a/src/org/ibex/js/PropertyFile.java b/src/org/ibex/js/PropertyFile.java index 3227922..ee7be2e 100644 --- a/src/org/ibex/js/PropertyFile.java +++ b/src/org/ibex/js/PropertyFile.java @@ -5,10 +5,12 @@ import org.ibex.util.*; import java.util.*; import java.io.*; +// FEATURE: Update for new api + /** A JS interface to a Java '.properties' file; very crude */ public class PropertyFile extends JS { - private final Properties p = new Properties(); + /*private final Properties p = new Properties(); private final Hash cache = new Hash(10, 3); private File f; @@ -47,5 +49,5 @@ public class PropertyFile extends JS { Object ret = p.get(toString(key)); if (ret != null) return ret; return new Minion(escape(toString(key))); - } + }*/ } diff --git a/src/org/ibex/js/SOAP.java b/src/org/ibex/js/SOAP.java index c2764d2..6843f53 100644 --- a/src/org/ibex/js/SOAP.java +++ b/src/org/ibex/js/SOAP.java @@ -41,7 +41,7 @@ public 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()); + objects.addElement(new JS.O()); for(int i=0; i 1 ? objects.elementAt(objects.size() - 2) : null; + JS parent = objects.size() > 1 ? (JS)objects.elementAt(objects.size() - 2) : (JS)null; // we want to fold stuff back into the fault object if (objects.size() < 2) return; @@ -152,7 +152,7 @@ public class SOAP extends XMLRPC { } else if (parent != null && parent instanceof JS) { objects.removeElementAt(objects.size() - 1); try { - ((JS)parent).put(name, me); + ((JS)parent).put(JS.S(name), me); } catch (JSExn e) { throw new Error("this should never happen"); } @@ -235,7 +235,7 @@ public class SOAP extends XMLRPC { Enumeration e = j.keys(); while(e.hasMoreElements()) { Object key = e.nextElement(); - appendObject((String)key, j.get(key), sb); + appendObject((String)key, j.get((JS)key), sb); } sb.append("\r\n"); @@ -260,8 +260,8 @@ public class SOAP extends XMLRPC { if (args.length() > 0) { Enumeration e = ((JS)args.elementAt(0)).keys(); while(e.hasMoreElements()) { - Object key = e.nextElement(); - appendObject((String)key, ((JS)args.elementAt(0)).get(key), content); + JS key = (JS)e.nextElement(); + appendObject(((JSString)key).coerceToString(), ((JS)args.elementAt(0)).get(key), content); } } content.append(" \r\n"); diff --git a/src/org/ibex/js/Tokens.java b/src/org/ibex/js/Tokens.java index 8970e14..126a10b 100644 --- a/src/org/ibex/js/Tokens.java +++ b/src/org/ibex/js/Tokens.java @@ -96,6 +96,7 @@ interface Tokens { public static final int GRAMMAR = 78; // the grammar-definition operator (::=) public static final int ADD_TRAP = 79; // the add-trap operator (++=) public static final int DEL_TRAP = 80; // the del-trap operator (--=) + public static final int CASCADE = 81; // cascade expression - arg==true for write cascade public static final int MAX_TOKEN = DEL_TRAP; @@ -112,7 +113,7 @@ interface Tokens { "HOOK", "COLON", "INC", "DEC", "DOT", "FUNCTION", "IF", "ELSE", "SWITCH", "CASE", "DEFAULT", "WHILE", "DO", "FOR", "VAR", "WITH", "CATCH", "FINALLY", "RESERVED", "GRAMMAR", - "ADD_TRAP", "DEL_TRAP" + "ADD_TRAP", "DEL_TRAP", "CASCADE" }; } diff --git a/src/org/ibex/js/Trap.java b/src/org/ibex/js/Trap.java index 77476f6..580ed69 100644 --- a/src/org/ibex/js/Trap.java +++ b/src/org/ibex/js/Trap.java @@ -7,55 +7,22 @@ package org.ibex.js; * linked list stack, with the most recently placed trap at the head * of the list. */ -class Trap { +final class Trap { - JS trapee = null; ///< the box on which this trap was placed - Object name = null; ///< the property that the trap was placed on + final JS target; ///< the box on which this trap was placed + final JS key; ///< the property that the trap was placed on - JSFunction f = null; ///< the function for this trap - Trap next = null; ///< the next trap down the trap stack + final JSFunction f; ///< the function for this trap + Trap next; ///< the next trap down the trap stack - Trap(JS b, String n, JSFunction f, Trap nx) { - trapee = b; name = n; this.f = f; this.next = nx; - } - - static final JSFunction putInvoker = new JSFunction("putInvoker", 0, null); - static final JSFunction getInvoker = new JSFunction("getInvoker", 0, null); - - static { - putInvoker.add(1, ByteCodes.PUT, null); - putInvoker.add(2, Tokens.RETURN, null); - getInvoker.add(1, ByteCodes.GET, null); - getInvoker.add(2, Tokens.RETURN, null); + Trap(JS b, JS n, JSFunction f, Trap nx) { + target = b; key = n; this.f = f; this.next = nx; } - void invoke(Object value) throws JSExn { - Interpreter i = new Interpreter(putInvoker, false, null); - i.stack.push(trapee); - i.stack.push(name); - i.stack.push(value); - i.resume(); - } - - Object invoke() throws JSExn { - Interpreter i = new Interpreter(getInvoker, false, null); - i.stack.push(trapee); - i.stack.push(name); - return i.resume(); - } - - // FIXME: review; is necessary? - static class TrapScope extends JSScope { - Trap t; - Object val = null; - boolean cascadeHappened = false; - public TrapScope(JSScope parent, Trap t, Object val) { super(parent); this.t = t; this.val = val; } - public Object get(Object key) throws JSExn { - if (key.equals("trapee")) return t.trapee; - if (key.equals("callee")) return t.f; - if (key.equals("trapname")) return t.name; - return super.get(key); - } - } + boolean isReadTrap() { return f.numFormalArgs == 0; } + boolean isWriteTrap() { return f.numFormalArgs != 0; } + Trap readTrap() { Trap t = this; while(t!=null && t.isWriteTrap()) t = t.next; return t; } + Trap writeTrap() { Trap t = this; while(t!=null && t.isReadTrap()) t = t.next; return t; } + Trap nextReadTrap() { return next == null ? null : next.readTrap(); } + Trap nextWriteTrap() { return next == null ? null : next.writeTrap(); } } - diff --git a/src/org/ibex/js/XMLRPC.java b/src/org/ibex/js/XMLRPC.java index 6f50328..6db685c 100644 --- a/src/org/ibex/js/XMLRPC.java +++ b/src/org/ibex/js/XMLRPC.java @@ -38,8 +38,8 @@ public class XMLRPC extends JS { } public XMLRPC(String url, String method, XMLRPC httpSource) { this.http = httpSource.http; this.url = url; this.method = method; } - public Object get(Object name) { - return new XMLRPC(url, (method.equals("") ? "" : method + ".") + name.toString(), this); } + public JS get(JS name) throws JSExn { + return new XMLRPC(url, (method.equals("") ? "" : method + ".") + JS.toString(name), this); } /** this holds character content as we read it in -- since there is only one per instance, we don't support mixed content */ @@ -84,7 +84,7 @@ public class XMLRPC extends JS { content.reset(); //#switch(c.getLocalName()) case "fault": fault = true; - case "struct": objects.setElementAt(new JS(), objects.size() - 1); + case "struct": objects.setElementAt(new JS.O(), objects.size() - 1); case "array": objects.setElementAt(null, objects.size() - 1); case "value": objects.addElement(""); //#end @@ -129,8 +129,8 @@ public class XMLRPC extends JS { "the server sent a tag which was malformed: " + s); } case "member": - Object memberValue = objects.elementAt(objects.size() - 1); - String memberName = (String)objects.elementAt(objects.size() - 2); + JS memberValue = (JS)objects.elementAt(objects.size() - 1); + JS memberName = (JS)objects.elementAt(objects.size() - 2); JS struct = (JS)objects.elementAt(objects.size() - 3); try { struct.put(memberName, memberValue); @@ -143,7 +143,7 @@ public class XMLRPC extends JS { for(i=objects.size() - 1; objects.elementAt(i) != null; i--); JSArray arr = new JSArray(); try { - for(int j = i + 1; j\n"); JSArray a = (JSArray)o; for(int i=0; i\n"); Enumeration e = j.keys(); while(e.hasMoreElements()) { Object key = e.nextElement(); sb.append(" " + key + "\n"); - appendObject(j.get(key), sb); + appendObject(j.get((JS)key), sb); sb.append(" \n"); } sb.append(" \n"); @@ -302,7 +302,7 @@ public class XMLRPC extends JS { // Call Sequence ////////////////////////////////////////////////////////////////////////// /* FIXME this has been disabled to make XMLRPC usable without Scheduler - public final Object call(Object a0, Object a1, Object a2, Object[] rest, int nargs) throws JSExn { + public final Object call(JS a0, JS a1, JS a2, JS[] rest, int nargs) throws JSExn { JSArray args = new JSArray(); for(int i=0; i