From 2d38cf96f2ac17fe1961aadb8bf4f02e5a1b5294 Mon Sep 17 00:00:00 2001 From: crawshaw Date: Wed, 5 Jan 2005 10:17:21 +0000 Subject: [PATCH] move to JS interface darcs-hash:20050105101721-2eb37-a0a3bf4fa71f13b2025bf9bd6a241e1e160f1f8f.gz --- src/org/ibex/js/Interpreter.java | 252 ++++++++--------- src/org/ibex/js/JS.java | 582 ++++++++++++++++++++------------------ src/org/ibex/js/JSArray.java | 328 ++++++++------------- src/org/ibex/js/JSDate.java | 29 +- src/org/ibex/js/JSExn.java | 16 +- src/org/ibex/js/JSFunction.java | 32 +-- src/org/ibex/js/JSMath.java | 2 +- src/org/ibex/js/JSPrimitive.java | 14 +- src/org/ibex/js/JSRegexp.java | 98 +++---- src/org/ibex/js/JSScope.java | 88 +++--- src/org/ibex/js/JSString.java | 7 +- src/org/ibex/js/Parser.java | 9 +- src/org/ibex/js/Script.java | 129 +++++++++ src/org/ibex/js/Test.java | 40 +-- src/org/ibex/js/Trap.java | 5 +- 15 files changed, 843 insertions(+), 788 deletions(-) create mode 100644 src/org/ibex/js/Script.java diff --git a/src/org/ibex/js/Interpreter.java b/src/org/ibex/js/Interpreter.java index efbe2a9..7bb8bbe 100644 --- a/src/org/ibex/js/Interpreter.java +++ b/src/org/ibex/js/Interpreter.java @@ -8,7 +8,7 @@ import org.ibex.util.*; import java.util.*; /** Encapsulates a single JS interpreter (ie call stack) */ -class Interpreter implements ByteCodes, Tokens { +class Interpreter implements ByteCodes, Tokens, Pausable { // Thread-Interpreter Mapping ///////////////////////////////////////////////////////////////////////// static Interpreter current() { return (Interpreter)threadToInterpreter.get(Thread.currentThread()); } @@ -23,7 +23,7 @@ class Interpreter implements ByteCodes, Tokens { final Stack stack = new Stack(); ///< the object stack int pc = 0; ///< the program counter - Interpreter(JSFunction f, boolean pauseable, JSArgs args) { + Interpreter(JSFunction f, boolean pauseable, JS[] args) { this.f = f; this.pausecount = pauseable ? 0 : -1; this.scope = f.parentScope; @@ -35,7 +35,7 @@ class Interpreter implements ByteCodes, Tokens { } } - Interpreter(Trap t, JS val, boolean pauseOnPut) { + Interpreter(JS.Trap t, JS val, boolean pauseOnPut) { this.pausecount = -1; try { setupTrap(t,val,new TrapMarker(null,t,val,pauseOnPut)); @@ -43,14 +43,21 @@ class Interpreter implements ByteCodes, Tokens { throw new Error("should never happen"); } } - + + private boolean get = false; + // FIXME: split this stuff out into a Script instance control object + // so it's possible to make JS either single or multi threaded. /** this is the only synchronization point we need in order to be threadsafe */ - synchronized JS resume() throws JSExn { - if(f == null) throw new RuntimeException("function already finished"); - if(scope == null) throw new RuntimeException("scope is null"); + public synchronized Object run(Object o) throws JSExn { + if (f == null) throw new AlreadyRunningException("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); + + if (get) stack.push(o); + try { return run(); } finally { @@ -58,6 +65,16 @@ class Interpreter implements ByteCodes, Tokens { else threadToInterpreter.put(t, old); } } + + public void pause() throws NotPausableException { + if (pausecount == -1 || f == null) throw new NotPausableException(); + pausecount++; + switch(f.op[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"); + } + } static int getLine() { Interpreter c = Interpreter.current(); @@ -124,20 +141,20 @@ class Interpreter implements ByteCodes, Tokens { } case PUSHKEYS: { - JS o = stack.peek(); + JS o = (JS)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(JS.T); + stack.push(new LoopMarker(pc, (String)(pc > 0 && f.op[pc - 1] == LABEL ? f.arg[pc - 1] : null), scope)); + stack.push(Script.T); break; case BREAK: case CONTINUE: while(!stack.empty()) { - JS o = stack.pop(); + JS o = (JS)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 @@ -169,7 +186,7 @@ class Interpreter implements ByteCodes, Tokens { } case RETURN: { - JS retval = stack.pop(); + JS retval = (JS)stack.pop(); while(!stack.empty()) { Object o = stack.pop(); if (o instanceof TryMarker) { @@ -185,19 +202,19 @@ class Interpreter implements ByteCodes, Tokens { 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(); + JS.Trap t = tm.t.nextWrite(); + if(t == null && tm.t.target() instanceof JS.Clone) { + t = ((JS.Clone)tm.t.target()).clonee.getTrap(tm.t.key()); + if(t != null && !t.isWriteTrap()) t = t.nextWrite(); } if(t != null) { tm.t = t; // we reuse the old trap marker - setupTrap(t,tm.val,tm); + 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); + if(!tm.pauseOnPut) tm.t.target().put(tm.t.key(), tm.val); } } } @@ -224,15 +241,15 @@ class Interpreter implements ByteCodes, Tokens { 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; + 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(); + JS.Trap t = write ? tm.t.nextWrite() : tm.t.nextRead(); // 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(t != null) t = write ? t.write() : t.read(); } if(write) { tm.cascadeHappened = true; @@ -247,8 +264,8 @@ class Interpreter implements ByteCodes, Tokens { target.put(key,val); } else { JS ret = target.get(key); - if (ret == JS.METHOD) ret = new Stub(target, key); - stack.push(ret); + if (ret != null && ret instanceof JS.Method) ret = new Stub(target, key); + stack.push(ret); } if (pausecount > initialPauseCount) { pc++; return null; } // we were paused } @@ -256,19 +273,19 @@ class Interpreter implements ByteCodes, Tokens { } 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"); + JS val = (JS)stack.pop(); + JS key = (JS)stack.pop(); + JS target = (JS)stack.peek(); + if (target == null) throw je("tried to put " + Script.str(val) + " to the " + Script.str(key) + " property on the null value"); + if (key == null) throw je("tried to assign \"" + Script.str(val) + "\" to the null key"); - Trap t = target.getTrap(key); - if(t != null) t = t.writeTrap(); + JS.Trap t = target.getTrap(key); + if(t != null) t = t.write(); if(t == null && target instanceof JS.Clone) { target = ((JS.Clone)target).clonee; t = target.getTrap(key); - if(t != null) t = t.writeTrap(); + if(t != null) t = t.nextWrite(); } stack.push(val); @@ -287,24 +304,24 @@ class Interpreter implements ByteCodes, Tokens { case GET_PRESERVE: { JS target, key; if (op == GET) { - key = arg == null ? stack.pop() : (JS)arg; - target = stack.pop(); + key = arg == null ? (JS)stack.pop() : (JS)arg; + target = (JS)stack.pop(); } else { - key = stack.pop(); - target = stack.peek(); + key = (JS)stack.pop(); + target = (JS)stack.peek(); stack.push(key); } 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(); + JS.Trap t = target.getTrap(key); + if(t != null) t = t.read(); 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) t = t.nextRead(); } if(t != null) { @@ -313,33 +330,28 @@ class Interpreter implements ByteCodes, Tokens { } else { ret = target.get(key); if (pausecount > initialPauseCount) { pc++; return null; } // we were paused - if (ret == JS.METHOD) ret = new Stub(target, key); + + if (ret != null && ret instanceof JS.Method) ret = new Stub(target, key); stack.push(ret); } break; } case CALL: case CALLMETHOD: { - 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[] jsargs = (JS[])arg; + JS method = null; JS ret = null; - JS object = stack.pop(); + JS object = (JS)stack.pop(); if (op == CALLMETHOD) { - if (object == JS.METHOD) { - method = stack.pop(); - object = stack.pop(); - } else if (object == null) { - method = stack.pop(); - object = stack.pop(); - throw new JSExn("function '"+JS.debugToString(method)+"' not found in " + object.getClass().getName()); + if (object == null) { + method = (JS)stack.pop(); + object = (JS)stack.pop(); + throw new JSExn("function '"+Script.str(method)+"' not found in " + object.getClass().getName()); + } else if (object instanceof JS.Method) { + method = (JS)stack.pop(); + object = (JS)stack.pop(); } else { stack.pop(); stack.pop(); @@ -348,14 +360,14 @@ class Interpreter implements ByteCodes, Tokens { if (object instanceof JSFunction) { stack.push(new CallMarker(this)); - stack.push(new JSArgs(a0,a1,a2,rest,numArgs,object)); + stack.push(jsargs); f = (JSFunction)object; scope = f.parentScope; pc = -1; break; } else { JS c = (JS)object; - ret = method == null ? c.call(a0, a1, a2, rest, numArgs) : c.callMethod(method, a0, a1, a2, rest, numArgs); + ret = method == null ? c.call(jsargs) : c.call(method, jsargs); } if (pausecount > initialPauseCount) { pc++; return null; } @@ -364,21 +376,21 @@ class Interpreter implements ByteCodes, Tokens { } case THROW: - throw new JSExn(stack.pop(), this); + throw new JSExn((JS)stack.pop(), this); /* FIXME GRAMMAR case MAKE_GRAMMAR: { final Grammar r = (Grammar)arg; final JSScope final_scope = scope; Grammar r2 = new Grammar() { - public int match(String s, int start, Hash v, JSScope scope) throws JSExn { + public int match(String s, int start, Map v, JSScope scope) throws JSExn { return r.match(s, start, v, final_scope); } - public int matchAndWrite(String s, int start, Hash v, JSScope scope, String key) throws JSExn { + public int matchAndWrite(String s, int start, Map v, JSScope scope, String key) throws JSExn { return r.matchAndWrite(s, start, v, final_scope, key); } public Object call(Object a0, Object a1, Object a2, Object[] rest, int nargs) throws JSExn { - Hash v = new Hash(); + Map v = new Map(); r.matchAndWrite((String)a0, 0, v, final_scope, "foo"); return v.get("foo"); } @@ -390,13 +402,13 @@ class Interpreter implements ByteCodes, Tokens { } */ case ADD_TRAP: case DEL_TRAP: { - JS val = stack.pop(); - JS key = stack.pop(); - JS js = stack.peek(); + JS val = (JS)stack.pop(); + JS key = (JS)stack.pop(); + JS js = (JS)stack.peek(); // A trap addition/removal 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); + if(op == ADD_TRAP) js.addTrap(key, val); + else js.delTrap(key, val); break; } @@ -405,8 +417,8 @@ class Interpreter implements ByteCodes, Tokens { if(count < 2) throw new Error("this should never happen"); if(count == 2) { // common case - JS right = stack.pop(); - JS left = stack.pop(); + JS right = (JS)stack.pop(); + JS left = (JS)stack.pop(); JS ret; if(left instanceof JSString || right instanceof JSString) ret = JS.S(JS.toString(left).concat(JS.toString(right))); @@ -420,7 +432,7 @@ class Interpreter implements ByteCodes, Tokens { stack.push(ret); } else { JS[] args = new JS[count]; - while(--count >= 0) args[count] = stack.pop(); + while(--count >= 0) args[count] = (JS)stack.pop(); if(args[0] instanceof JSString) { StringBuffer sb = new StringBuffer(64); for(int i=0;i= 0) { @@ -531,10 +543,10 @@ class Interpreter implements ByteCodes, Tokens { throw e; } - void setupTrap(Trap t, JS val, CallMarker cm) throws JSExn { + void setupTrap(JS.Trap t, JS val, CallMarker cm) throws JSExn { stack.push(cm); - stack.push(new TrapArgs(t,val)); - f = t.f; + stack.push(new TrapArgs(t, val)); + f = (JSFunction)t.function(); // FIXME scope = f.parentScope; pc = 0; } @@ -542,12 +554,7 @@ class Interpreter implements ByteCodes, Tokens { // Markers ////////////////////////////////////////////////////////////////////// - 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 Marker {} static class CallMarker extends Marker { final int pc; @@ -561,12 +568,12 @@ class Interpreter implements ByteCodes, Tokens { } static class TrapMarker extends CallMarker { - Trap t; + JS.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) { + public TrapMarker(Interpreter cx, JS.Trap t, JS val) { this(cx,t,val,false); } + public TrapMarker(Interpreter cx, JS.Trap t, JS val, boolean pauseOnPut) { super(cx); this.t = t; this.val = val; @@ -575,7 +582,7 @@ class Interpreter implements ByteCodes, Tokens { } static class CatchMarker extends Marker { } - private static CatchMarker catchMarker = new CatchMarker(); + private static final CatchMarker catchMarker = new CatchMarker(); static class LoopMarker extends Marker { final public int location; @@ -608,77 +615,41 @@ class Interpreter implements ByteCodes, Tokens { public FinallyData(JSExn exn) { this.exn = exn; this.op = -1; this.arg = null; } // Just throw this exn } - static class TrapArgs extends JS { + static class TrapArgs extends JS.Immutable { 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; + if(Script.isInt(key) && Script.toInt(key) == 0) return val; + //#switch(Script.str(key)) + case "trapee": return t.target(); + case "callee": return t.function(); + case "trapname": return t.key(); + case "length": return t.isWriteTrap() ? Script.ONE : Script.ZERO; //#end return super.get(key); } } - 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; - } - - 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); - } - } - - static class Stub extends JS { + static class Stub extends JS.Immutable { private JS method; JS obj; 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); - } + public JS call(JS[] args) throws JSExn { return obj.call(method, args); } } - static class Stack { + static final class Stack { private static final int MAX_STACK_SIZE = 512; - private JS[] stack = new JS[64]; + private Object[] stack = new Object[8]; 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 push(Object o) throws JSExn { if(sp == stack.length) grow(); stack[sp++] = o; } + Object peek() { if(sp == 0) throw new RuntimeException("stack underflow"); return stack[sp-1]; } + final Object 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]; + Object tmp = stack[sp-2]; stack[sp-2] = stack[sp-1]; stack[sp-1] = tmp; } @@ -687,9 +658,10 @@ class Interpreter implements ByteCodes, Tokens { return null; } void grow() throws JSExn { - if(stack.length >= MAX_STACK_SIZE) throw new JSExn("Stack overflow"); - JS[] stack2 = new JS[stack.length * 2]; + if(stack.length >= MAX_STACK_SIZE) throw new JSExn("stack overflow"); + Object[] stack2 = new Object[stack.length * 2]; System.arraycopy(stack,0,stack2,0,stack.length); + stack = stack2; } void backtrace(JSExn e) { diff --git a/src/org/ibex/js/JS.java b/src/org/ibex/js/JS.java index cb6e680..0a3843c 100644 --- a/src/org/ibex/js/JS.java +++ b/src/org/ibex/js/JS.java @@ -9,125 +9,236 @@ import java.io.*; import java.util.*; /** The minimum set of functionality required for objects which are manipulated by JavaScript */ -public abstract class JS { - public static final JS METHOD = new JS() { }; - - 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 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 interface JS extends Pausable { + + /** Returns an enumeration of the keys in this object. */ + public JS.Enumeration keys() throws JSExn; + + /** Return the value associated with the given key. */ + public JS get(JS key) throws JSExn; + + /** Store a specific value against the given key. */ + public void put(JS key, JS value) throws JSExn; + + /** Executes or unpauses the task, running it in a + * pausable context. */ + public Object run(Object o) throws Exception, AlreadyRunningException; + + /** Pauses the running task at its convienience. */ + public void pause() throws NotPausableException; + + /** Calls a specific method with given arguments on this object. + * An exception is thrown if there is no such method or the + * arguments are invalid. */ + public JS call(JS method, JS[] args) throws JSExn; + + /** Calls this object with the given arguments, running it in an + * unpausable context. An exception is thrown if this + * object is not callable or the arguments are invalid. */ + public JS call(JS[] args) throws JSExn; + + /** Returns the names of the formal arguments, if any. + * This function must never return a null object. */ + public String[] getFormalArgs(); + + /** Returns true if the specific key is found in this object. */ + public boolean hasKey(JS key); + + /** Put a value to the given key, calling any write traps that have + * been placed on the key. + * + * This function may block for an indeterminate amount of time. + * + * Write traps may change the final value before it is put, thus + * be careful to read the latest value from this function's return + * before doing any more work with it. + */ + public JS putAndTriggerTraps(JS key, JS value) throws JSExn; + + /** Gets the value for a given key, calling any read traps that have + * been placed on the key. + * + * This function may block for an indeterminate amount of time. + */ + public JS getAndTriggerTraps(JS key) throws JSExn; + + /** Calls the write traps placed on a given key with the given value + * and returns the result without saving it to this object's key store. */ + public JS justTriggerTraps(JS key, JS value) throws JSExn; + + public void addTrap(JS key, JS function) throws JSExn; + public void delTrap(JS key, JS function) throws JSExn; + public Trap getTrap(JS key) throws JSExn; + + // FIXME: consider renaming/removing these + public InputStream getInputStream() throws IOException, JSExn; + public JS unclone(); + public String coerceToString(); + + + // Implementations //////////////////////////////////////////////////////// + + /** Provides the lightest possible implementation of JS, throwing + * exceptions on every mutable operation. */ + public static class Immutable implements JS { + private static final String[] emptystr = new String[0]; + + public JS unclone() { return this; } + public JS.Enumeration keys() throws JSExn { throw new JSExn( + "object has no key set, 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 on class ["+ getClass().getName() +"]"); } + public InputStream getInputStream() throws IOException, JSExn { throw new JSExn( + "object has not associated stream, class ["+ getClass().getName() +"]"); } + + public boolean hasKey(JS key) { return false; } + + public Object run(Object o) throws Exception { throw new JSExn( + "object cannot be called, class ["+ getClass().getName() +"]"); } + public void pause() { throw new NotPausableException(); } + + public JS call(JS[] args) throws JSExn { throw new JSExn( + "object cannot be called, class ["+ getClass().getName() +"]"); } + public JS call(JS method, JS[] args) throws JSExn { throw new JSExn( + "method not found: " + Script.str(method)); } + public String[] getFormalArgs() { return emptystr; } + + public void declare(JS key) throws JSExn { throw new JSExn( + "object cannot declare key: "+ Script.str(key)); } + public void undeclare(JS key) throws JSExn { } // FIXME throw error? + + public JS putAndTriggerTraps(JS key, JS val) throws JSExn { throw new JSExn( + "'" + key + "' is trap read only on class ["+ getClass().getName() +"]"); } + public JS getAndTriggerTraps(JS key) throws JSExn { return null; } // FIXME throw errors? + public JS justTriggerTraps(JS key, JS value) throws JSExn { return null; } + + public void addTrap(JS key, JS function) throws JSExn { throw new JSExn( + "'" + key + "' is not trappable on class ["+ getClass().getName() +"]"); } + public void delTrap(JS key, JS function) throws JSExn { throw new JSExn( + "'" + key + "' trap is read only on class ["+ getClass().getName() +"]"); } + public Trap getTrap(JS key) throws JSExn { throw new JSExn( + "'" + key + "' is not trappable on class ["+ getClass().getName() +"]"); } + + public String coerceToString() { return "object"; } } - - public interface Cloneable { } - - public static class Clone extends O { + + public interface Cloneable {} + + public static class Clone implements JS { protected final JS clonee; public Clone(JS clonee) throws JSExn { - if(!(clonee instanceof Cloneable)) throw new JSExn("" + clonee.getClass().getName() + " isn't cloneable"); + if (!(clonee instanceof Cloneable)) throw new JSExn( + clonee.getClass().getName() + " is not implement cloneable"); this.clonee = clonee; } - JS _unclone() { return clonee.unclone(); } - boolean jsequals(JS o) { return clonee.jsequals(o); } - + + public JS unclone() { return clonee.unclone(); } + public boolean equals(Object o) { return clonee.equals(o); } + public Enumeration keys() throws JSExn { return clonee.keys(); } - 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 JS get(JS k) throws JSExn { return clonee.get(k); } + public void put(JS k, JS v) throws JSExn { clonee.put(k, v); } + public InputStream getInputStream() throws IOException, JSExn { + return clonee.getInputStream(); } + + public Object run(Object o) throws Exception { return clonee.run(o); } + public void pause() { clonee.pause(); } + + public JS call(JS m, JS[] a) throws JSExn { return clonee.call(m, a); } + public JS call(JS[] a) throws JSExn { return clonee.call(a); } + public String[] getFormalArgs() { return clonee.getFormalArgs(); } + + public boolean hasKey(JS k) { return clonee.hasKey(k); } + + public JS putAndTriggerTraps(JS k, JS v) throws JSExn { + return clonee.putAndTriggerTraps(k, v); } + public JS getAndTriggerTraps(JS k) throws JSExn { + return clonee.getAndTriggerTraps(k); } + public JS justTriggerTraps(JS k, JS v) throws JSExn { + return clonee.justTriggerTraps(k, v); } + + public void addTrap(JS k, JS f) throws JSExn { clonee.addTrap(k, f); } + public void delTrap(JS k, JS f) throws JSExn { clonee.delTrap(k, f); } + public Trap getTrap(JS k) throws JSExn { return clonee.getTrap(k); } + + public String coerceToString() { return clonee.coerceToString(); } } - - 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 Obj extends LinearStore implements JS { + private static final String[] emptystr = new String[0]; + private static final Placeholder holder = new Placeholder(); + + /** entries[index + 0] // key + * entries[index + 1] // value + * entries[index + 2] // trap + */ + protected final int indexmultiple = 3; + + protected void entryAdded(int p) {} + protected void entryUpdated(int p) {} + protected void entryRemoved(int p) {} + + public Obj() { super(4, 0.75F); } + + public JS unclone() { return this; } + public InputStream getInputStream() throws IOException, JSExn { throw new JSExn( + "object has not associated stream, class ["+ getClass().getName() +"]"); } + + public Object run(Object o) throws Exception { throw new JSExn( + "object cannot be called, class ["+ getClass().getName() +"]"); } + public void pause() { throw new NotPausableException(); } + + public JS call(JS[] args) throws JSExn { throw new JSExn( + "object cannot be called, class ["+ getClass().getName() +"]"); } + public JS call(JS method, JS[] args) throws JSExn { throw new JSExn( + "method not found: " + Script.str(method)); } + public String[] getFormalArgs() { return emptystr; } + + public Enumeration keys() throws JSExn { + // FEATURE: replicate some code from a superclass to avoid double object creation + return new Enumeration.JavaIterator(null, new LinearStore.IndexIterator() { + public Object next() { return entries[nextIndex()]; } }); } - } + public JS get(JS key) throws JSExn { int i = indexOf(key); + return i < 0 ? null : entries[i + 1] instanceof Placeholder ? null : (JS)entries[i + 1]; } + public void put(JS key, JS val) throws JSExn { + // NOTE: only way value can be stored as null is using declare() + int dest = put(indexOf(key), key); + if (val == null) entries[dest + 1] = holder; + else entries[dest + 1] = val; } - 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 /////////////////////////////////////////////////////////////// + public boolean hasKey(JS key) { return indexOf(key) >= 0; } + /*public boolean hasValue(JS key, JS value) { + int i = indexOf(key); return i >= 0 && entries[i + 1] != null; } + public boolean hasTrap(JS key, JS trap) { + int i = indexOf(key); return i >= 0 && entries[i + 2] != null; }*/ - /** log a message with the current JavaScript sourceName/line */ - public static void log(Object message) { info(message); } - public static void debug(Object message) { Log.debug(Interpreter.getSourceName() + ":" + Interpreter.getLine(), message); } - public static void info(Object message) { Log.info(Interpreter.getSourceName() + ":" + Interpreter.getLine(), message); } - public static void warn(Object message) { Log.warn(Interpreter.getSourceName() + ":" + Interpreter.getLine(), message); } - public static void error(Object message) { Log.error(Interpreter.getSourceName() + ":" + Interpreter.getLine(), message); } + public void declare(JS key) throws JSExn { entries[put(indexOf(key), key) + 1] = null; } + public void undeclare(JS key) throws JSExn { remove(indexOf(key)); } - public static class NotPauseableException extends Exception { NotPauseableException() { } } + public JS putAndTriggerTraps(JS key, JS val) throws JSExn { + Trap t = null; int i = indexOf(key); + if (i >= 0) t = (Trap)entries[i + 2]; + if (t != null && (t = t.write()) != null) { + val = (JS)new Interpreter(t, val, false).run(null); + } + if (val == null) entries[i + 1] = holder; + else entries[i + 1] = val; + return val; + } + public JS getAndTriggerTraps(JS key) throws JSExn { + Trap t = null; int i = indexOf(key); + if (i < 0) return null; + t = (Trap)entries[i + 2]; + return t == null ? (JS)entries[i + 1] : (JS)new Interpreter(t, null, false).run(null); + } + public JS justTriggerTraps(JS key, JS val) throws JSExn { + Trap t = null; int i = indexOf(key); + if (i >= 0) t = (Trap)entries[i + 2]; + if (t == null || (t = t.write()) == null) return val; + return (JS)new Interpreter(t, val, true).run(null); + } /** returns a callback which will restart the context; expects a value to be pushed onto the stack when unpaused */ public static UnpauseCallback pause() throws NotPauseableException { @@ -136,187 +247,116 @@ public abstract class JS { boolean get; switch(i.f.op[i.pc]) { case Tokens.RETURN: case ByteCodes.PUT: get = false; break; - case ByteCodes.GET: case ByteCodes.CALL: case ByteCodes.CALLMETHOD: get = true; break; - default: throw new Error("should never happen: i.f.op[i.pc] == " + i.f.op[i.pc]); + case ByteCodes.GET: case ByteCodes.CALL: get = true; break; + default: throw new Error("should never happen"); } - i.pausecount++; - return new JS.UnpauseCallback(i,get); - } - public static class UnpauseCallback implements Task { - 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 void delTrap(JS key, JS f) throws JSExn { + int i = indexOf(key); if (i < 0) return; + Trap t = (Trap)entries[i + 2]; + if (t.function().equals(f)) { entries[i + 2] = t.next(); return; } + for (; t.next() != null; t = t.next()) + if (t.next().function().equals(f)) { ((TrapHolder)t).next = t.next().next(); return; } } - public JS unpause(JSExn e) throws JSExn { - i.catchException(e); - return i.resume(); + + public Trap getTrap(JS key) throws JSExn { + int i = indexOf(key); return i < 0 ? null : (Trap)entries[i + 2]; } - } + public String coerceToString() { return "object"; } + private static final class Placeholder implements Serializable {} - // Static Helper Methods /////////////////////////////////////////////////////////////////////////////////// + private static final class TrapHolder implements Trap { + private final JS target, key, function; + private Trap next; + TrapHolder(JS t, JS k, JS f, Trap n) { target = t; key = k; function = f; next = n; } - /** coerce an object to a Boolean */ - 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; - } - //#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 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"); - } - //#end - - public static String toString(JS o) throws JSExn { - if(o == null) return "null"; - 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 JSString) { - String s = ((JSString)o).s; - for(int i=0;i '9') return false; - return true; - } - return false; - } - - public static boolean isString(JS o) { - if(o instanceof JSString) return true; - return false; - } - - // Instance Methods //////////////////////////////////////////////////////////////////// - - 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()); - } + public JS key() { return key; } + public JS target() { return target; } + public JS function() { return function; } - 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 < CACHE_SIZE && idx > 0) { - ret = smallIntCache[idx]; - if (ret != null) return ret; - } - 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 % CACHE_SIZE)] = ret; + public boolean isReadTrap() { + return function.getFormalArgs() == null || function.getFormalArgs().length == 0; } + public boolean isWriteTrap() { + return function.getFormalArgs() != null && function.getFormalArgs().length != 0; } + + public Trap next() { return next; } + public Trap nextRead() { + Trap t = next; while (t != null && t.isWriteTrap()) t = t.next(); return t; } + public Trap nextWrite() { + Trap t = next; while (t != null && t.isReadTrap()) t = t.next(); return t; } + public Trap read() { return isReadTrap() ? this : nextRead(); } + public Trap write() { return isWriteTrap() ? this : nextWrite(); } } - return ret; - } - - private static Enumeration EMPTY_ENUMERATION = new EmptyEnumeration(null); - - public static JS fromReader(String sourceName, int firstLine, Reader sourceCode) throws IOException { - return Parser.fromReader(sourceName, firstLine, sourceCode); } - public static JS cloneWithNewGlobalScope(JS js, JS s) { - if(js instanceof JSFunction) - return ((JSFunction)js)._cloneWithNewParentScope(new JSScope.Top(s)); - else - return js; + public interface Trap { + public JS key(); + public JS target(); + public JS function(); + public boolean isReadTrap(); + public boolean isWriteTrap(); + public Trap next(); + public Trap nextRead(); + public Trap nextWrite(); + + public Trap read(); // FIXME reconsider these function names + public Trap write(); } + /** An empty class used for instanceof matching the result of JS.get() + * to decide if the key is a method (to be called with JS.callMethod(). */ + public static final class Method extends Immutable {} - // Trap support ////////////////////////////////////////////////////////////////////////////// - /** performs a put, triggering traps if present; traps are run in an unpauseable interpreter */ - public final void putAndTriggerTraps(JS key, JS value) throws JSExn { - Trap t = getTrap(key); - if(t == null || (t = t.writeTrap()) == null) put(key,value); - else new Interpreter(t,value,false).resume(); - } + public static abstract class Enumeration extends Immutable { + public static final Enumeration EMPTY = new Empty(null); - /** performs a get, triggering traps if present; traps are run in an unpauseable interpreter */ - public final JS getAndTriggerTraps(JS key) throws JSExn { - Trap t = getTrap(key); - if (t == null || (t = t.readTrap()) == null) return get(key); - else return new Interpreter(t,null,false).resume(); - } - - 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(); - } + private final Enumeration parent; - /** adds a trap, avoiding duplicates */ - // 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; - 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))); - } + public Enumeration(Enumeration parent) { this.parent = parent; } - /** deletes a trap, if present */ - // 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.target, t.next); return; } - for(; t.next != null; t = t.next) if (t.next.f == f) { t.next = t.next.next; return; } - } + protected abstract boolean _hasNext(); + protected abstract JS _next() throws JSExn; + + public final boolean hasNext() { + return _hasNext() || parent != null ? parent.hasNext() : false; } + public final JS next() throws JSExn { + if (_hasNext()) return _next(); + if (parent == null) throw new NoSuchElementException("reached end of set"); + return parent.next(); + } + + public JS get(JS key) throws JSExn { + //#switch(Script.str(key)) + case "hasNext": return Script.B(hasNext()); + case "next": return next(); + //#end + return super.get(key); + } + public static final class Empty extends Enumeration { + public Empty(Enumeration parent) { super(parent); } + protected boolean _hasNext() { return false; } + protected JS _next() { throw new NoSuchElementException(); } + } + + public static final class JavaIterator extends Enumeration { + private final Iterator i; + public JavaIterator(Enumeration parent, Iterator i) { super(parent); this.i = i; } + protected boolean _hasNext() { return i.hasNext(); } + protected JS _next() { return (JS)i.next(); } + } + + public static final class RandomAccessList extends Enumeration { + private final List l; + private int pos = 0, size; + public RandomAccessList(Enumeration parent, List list) { + super(parent); l = list; size = l.size(); } + protected boolean _hasNext() { return pos < size; } + protected JS _next() { return (JS)l.get(pos++); } + } + } -} +} diff --git a/src/org/ibex/js/JSArray.java b/src/org/ibex/js/JSArray.java index 8fe06a7..5d6738e 100644 --- a/src/org/ibex/js/JSArray.java +++ b/src/org/ibex/js/JSArray.java @@ -4,176 +4,124 @@ package org.ibex.js; -import org.ibex.util.*; +import java.io.InputStream; import java.util.*; +import org.ibex.util.*; +import org.ibex.util.Collections; /** A JavaScript JSArray */ -public class JSArray extends JS.O { - private static final Object NULL = new Object(); - private final BalancedTree bt = new BalancedTree(); - - public JSArray() { } - public JSArray(int size) { setSize(size); } - - /*private static int intVal(Object o) { - if (o instanceof Number) { - int intVal = ((Number)o).intValue(); - if (intVal == ((Number)o).doubleValue()) return intVal; - return Integer.MIN_VALUE; - } - if (!(o instanceof String)) return Integer.MIN_VALUE; - String s = (String)o; - for(int i=0; i '9') return Integer.MIN_VALUE; - return Integer.parseInt(s); - }*/ - - public 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; - return removeElementAt(oldSize-1); +class JSArray extends ArrayList implements JS, Comparator { + private static final JS.Method METHOD = new JS.Method(); + private static final String[] empty = new String[0]; + + JSArray() { } + JSArray(int size) { super(size); } + JSArray(JS[] args) { super(args.length); addAll(args); } + JSArray(JS arg) { super(1); add(arg); } + + public JS unclone() { return this; } + public JS.Enumeration keys() throws JSExn { return new Enumeration.RandomAccessList(null, this); } + public JS get(JS key) throws JSExn { + if (key == null || !(key instanceof JSNumber.I)) { + //#switch(Script.str(key)) + case "pop": return METHOD; + case "reverse": return METHOD; + case "toString": return METHOD; + case "shift": return METHOD; + case "join": return METHOD; + case "sort": return METHOD; + case "slice": return METHOD; + case "push": return METHOD; + case "unshift": return METHOD; + case "splice": return METHOD; + case "length": return Script.N(size()); + //#end + throw new JSExn("arrays only support positive integer keys, can not use: "+Script.str(key)); } - case "reverse": return reverse(); + return (JS)get(((JSNumber.I)key).toInt()); + } + public void put(JS key, JS val) throws JSExn { + if (Script.str(key).equals("length")) { setSize(Script.toInt(val)); } + + if (key == null || !(key instanceof JSNumber.I)) throw new JSExn( + "arrays only support positive integer keys, can not use: "+Script.str(key)); + int i = ((JSNumber.I)key).toInt(); + if (i < 0) throw new JSExn("arrays can not use negative integer keys "+i); + ensureCapacity(i + 1); while (size() < i) add(null); + set(i, val); + } + public InputStream getInputStream() { return null; } + + public String[] getFormalArgs() { return empty; } + public String coerceToString() { return "array"; } // FIXME + + public boolean hasKey(JS key) { + if (key == null || !(key instanceof JSNumber.I)) return false; + int i = ((JSNumber.I)key).toInt(); + return 0 <= i && i < size(); + } + + public Object run(Object o) throws JSExn { return call(null); } + public void pause() throws NotPausableException { throw new NotPausableException(); } + public JS call(JS[] args) throws JSExn { throw new JSExn("can not call an array as a function"); } + public JS call(JS method, JS[] args) throws JSExn { + //#switch(Script.str(method)) + case "pop": return size() == 0 ? null : (JS)remove(size() - 1); + case "push": addAll(args); return Script.N(size()); + case "reverse": Collections.reverse(this); return this; case "toString": return join(","); - case "shift": - if(length() == 0) return null; - return removeElementAt(0); - case "join": - return join(nargs == 0 ? "," : JS.toString(a0)); - case "sort": - return sort(nargs < 1 ? null : a0); + case "shift": return size() == 0 ? null : (JS)remove(0); + case "join": return join(args.length == 0 ? "," : Script.str(args[0])); + case "sort": return sort(args.length == 0 ? null : args[0]); case "slice": - int start = toInt(nargs < 1 ? null : a0); - int end = nargs < 2 ? length() : toInt(a1); + int start = Script.toInt(args.length < 1 ? null : args[0]); + int end = args.length < 2 ? size() : Script.toInt(args[1]); return slice(start, end); - case "push": { - int oldSize = size(); - for(int i=0; i= size()) return null; - return elementAt(i); - } - //#switch(JS.toString(key)) - case "pop": return METHOD; - case "reverse": return METHOD; - case "toString": return METHOD; - case "shift": return METHOD; - case "join": return METHOD; - case "sort": return METHOD; - case "slice": return METHOD; - case "push": return METHOD; - case "unshift": return METHOD; - case "splice": return METHOD; - case "length": return N(size()); + for (int i=0; i < args.length; i++) add(i, args[i]); + return Script.N(size()); + case "splice": return splice(args); //#end - return super.get(key); - } - 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); - } else { - 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); + throw new JSExn("arrays have no function: "+Script.str(method)); } - 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++); - } - }; - } + public JS putAndTriggerTraps(JS key, JS val) throws JSExn { put(key, val); return val; } + public JS getAndTriggerTraps(JS key) throws JSExn { return get(key); } + public JS justTriggerTraps(JS key, JS val) throws JSExn { return val; } - public final void setSize(int newSize) { - // FEATURE: This could be done a lot more efficiently in BalancedTree - int oldSize = size(); - for(int i=oldSize;i=newSize;i--) removeElementAt(i); - } - - public final int length() { return size(); } - public final JS elementAt(int i) { - if(i < 0 || i >= size()) throw new ArrayIndexOutOfBoundsException(i); - Object o = bt.getNode(i); - return o == NULL ? (JS)null : (JS)o; - } - public final void addElement(JS o) { - bt.insertNode(size(),o==null ? NULL : o); - } - public final void setElementAt(JS o, int i) { - if(i < 0 || i >= size()) throw new ArrayIndexOutOfBoundsException(i); - bt.replaceNode(i,o==null ? NULL : o); - } - public final void insertElementAt(JS o, int i) { - if(i < 0 || i > size()) throw new ArrayIndexOutOfBoundsException(i); - bt.insertNode(i,o==null ? NULL : o); - } - public final JS removeElementAt(int i) { - if(i < 0 || i >= size()) throw new ArrayIndexOutOfBoundsException(i); - Object o = bt.deleteNode(i); - return o == NULL ? (JS)null : (JS)o; + public void addTrap(JS k, JS f) throws JSExn { throw new JSExn("arrays do not support traps"); } + public void delTrap(JS k, JS f) throws JSExn { throw new JSExn("arrays do not support traps"); } + public JS.Trap getTrap(JS k) throws JSExn { throw new JSExn("arrays do not support traps"); } + + /** FIXME: move to specialised ArrayStore superclass. */ + public void addAll(JS[] entries) { for (int i=0; i < entries.length; i++) add(entries[i]); } + + public void setSize(int newSize) { + ensureCapacity(newSize); + for (int i=size(); i < newSize; i++) add(null); + for (int i=size() - 1; i >= newSize; i--) remove(i); } - - public final int size() { return bt.treeSize(); } - + + + // ECMA Implementation //////////////////////////////////////////////////// + private JS join(String sep) throws JSExn { int length = size(); if(length == 0) return JS.S(""); StringBuffer sb = new StringBuffer(64); int i=0; while(true) { - JS o = elementAt(i); - if(o != null) sb.append(JS.toString(o)); + JS o = (JS)get(i); + if(o != null) sb.append(Script.toString(o)); if(++i == length) break; sb.append(sep); } return JS.S(sb.toString()); } - - // FEATURE: Implement this more efficiently - private JS reverse() { - int size = size(); - if(size < 2) return this; - Vec vec = toVec(); - bt.clear(); - for(int i=size-1,j=0;i>=0;i--,j++) insertElementAt((JS)vec.elementAt(i),j); - return this; - } - + private JS slice(int start, int end) { - int length = length(); + int length = size(); if(start < 0) start = length+start; if(end < 0) end = length+end; if(start < 0) start = 0; @@ -181,45 +129,16 @@ public class JSArray extends JS.O { if(start > length) start = length; if(end > length) end = length; JSArray a = new JSArray(end-start); - for(int i=0;i 0) { setSize(newLength); for(int i=newLength-1;i>=start+newCount;i--) - setElementAt(elementAt(i-lengthChange),i); + set(i, get(i-lengthChange)); } else if(lengthChange < 0) { for(int i=start+newCount;i= 2) array[2] = toDouble(a2); - for(int i=0; i= 2) array[2] = Script.toDouble(args[2]); + for (int i=0; i < args.length; i++) { + double d = _toNumber(args[i]); if (d != d || Double.isInfinite(d)) { obj.date = Double.NaN; return; diff --git a/src/org/ibex/js/JSExn.java b/src/org/ibex/js/JSExn.java index df905e8..2251cef 100644 --- a/src/org/ibex/js/JSExn.java +++ b/src/org/ibex/js/JSExn.java @@ -9,9 +9,9 @@ import java.io.*; /** An exception which can be thrown and caught by JavaScript code */ public class JSExn extends Exception { - private Vec backtrace = new Vec(); + private List backtrace = new ArrayList(); private JS js; - public JSExn(String s) { this(JS.S(s)); } + public JSExn(String s) { this(Script.S(s)); } public JSExn(JS js) { this(js,null); } public JSExn(JS js, Interpreter cx) { this.js = js; fill(cx); } @@ -23,23 +23,23 @@ public class JSExn extends Exception { } public void printStackTrace() { printStackTrace(System.err); } public void printStackTrace(PrintWriter pw) { - for(int i=0; iunpauseable context. */ - public JS call(JS a0, JS a1, JS a2, JS[] rest, int nargs) throws JSExn { - Interpreter cx = new Interpreter(this, false, new Interpreter.JSArgs(a0,a1,a2,rest,nargs,this)); - return cx.resume(); - } + public JS call(JS[] args) throws JSExn { return (JS)new Interpreter(this, false, args).run(null); } - public JSScope getParentScope() { return parentScope; } + JSScope getParentScope() { return parentScope; } // Adding and Altering Bytecodes /////////////////////////////////////////////////// diff --git a/src/org/ibex/js/JSMath.java b/src/org/ibex/js/JSMath.java index 71f8b18..6b092e0 100644 --- a/src/org/ibex/js/JSMath.java +++ b/src/org/ibex/js/JSMath.java @@ -51,7 +51,7 @@ class JSMath extends JS { break; } } - return super.callMethod(method, a0, a1, a2, rest, nargs); + return super.call(method, args); } public JS get(JS key) throws JSExn { diff --git a/src/org/ibex/js/JSPrimitive.java b/src/org/ibex/js/JSPrimitive.java index 4419655..4d6bcba 100644 --- a/src/org/ibex/js/JSPrimitive.java +++ b/src/org/ibex/js/JSPrimitive.java @@ -4,9 +4,11 @@ package org.ibex.js; -class JSPrimitive extends JS { - public JS callMethod(JS method, JS arg0, JS arg1, JS arg2, JS[] rest, int alength) throws JSExn { - //#switch(JS.toString(method)) +class JSPrimitive extends JS.Immutable { + private static final JS.Method METHOD = new JS.Method(); + + public JS callMethod(JS method, JS[] args) throws JSExn { + //#switch(Script.str(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"); @@ -49,8 +51,8 @@ class JSPrimitive extends JS { } case "concat": { StringBuffer sb = new StringBuffer(slength*2).append(s); - for(int i=0;i= 1 ? JS.toString(arg0) : "null"; @@ -83,7 +85,7 @@ class JSPrimitive extends JS { return JS.S(s.substring(a,b)); } //#end - return super.callMethod(method,arg0,arg1,arg2,rest,alength); + return super.call(method, args); } public JS get(JS key) throws JSExn { diff --git a/src/org/ibex/js/JSRegexp.java b/src/org/ibex/js/JSRegexp.java index 617e99f..a987921 100644 --- a/src/org/ibex/js/JSRegexp.java +++ b/src/org/ibex/js/JSRegexp.java @@ -5,7 +5,9 @@ package org.ibex.js; /** A JavaScript regular expression object */ -public class JSRegexp extends JS { +public class JSRegexp extends JS.Immutable { + private static final JS.Method METHOD = new JS.Method(); + private boolean global; private GnuRegexp.RE re; private int lastIndex; @@ -19,8 +21,8 @@ public class JSRegexp extends JS { this.global = r.global; this.re = r.re; this.lastIndex = r.lastIndex; - this.pattern = pattern; - this.flags = flags; + this.pattern = r.pattern; + this.flags = r.flags; } else { String pattern = JS.toString(arg0); String sFlags = null; @@ -41,10 +43,10 @@ public class JSRegexp extends JS { } } - public JS callMethod(JS method, JS a0, JS a1, JS a2, JS[] rest, int nargs) throws JSExn { - switch(nargs) { + public JS call(JS method, JS[] args) throws JSExn { + switch(args.length) { case 1: { - //#switch(JS.toString(method)) + //#switch(JS.str(method)) case "exec": { String s = JS.toString(a0); int start = global ? lastIndex : 0; @@ -62,32 +64,37 @@ public class JSRegexp extends JS { lastIndex = match != null ? s.length() : match.getEndIndex(); return B(match != null); } - case "toString": return JS.S(a0.coerceToString()); - case "stringMatch": return stringMatch(a0,a1); - case "stringSearch": return stringSearch(a0,a1); + case "toString": return JS.S(args[0].coerceToString()); //#end break; } case 2: { - //#switch(JS.toString(method)) - case "stringReplace": return stringReplace(a0, a1,a2); + //#switch(JS.str(method)) + case "stringMatch": return stringMatch(args[0], args[1]); + case "stringSearch": return stringSearch(args[0], args[1]); + //#end + break; + } + case 3: { + //#switch(JS.str(method)) + case "stringReplace": return stringReplace(args[0], args[1], args[2]); //#end break; } } - return super.callMethod(method, a0, a1, a2, rest, nargs); + return super.call(method, args); } public JS get(JS key) throws JSExn { - //#switch(JS.toString(key)) + //#switch(JS.str(key)) case "exec": return METHOD; case "test": return METHOD; case "toString": return METHOD; - case "lastIndex": return N(lastIndex); + case "lastIndex": return JS.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); + case "ignoreCase": return JS.B(flags & GnuRegexp.RE.REG_ICASE); + case "multiline": return JS.B(flags & GnuRegexp.RE.REG_MULTILINE); //#end return super.get(key); } @@ -104,11 +111,11 @@ public class JSRegexp extends JS { private static JS matchToExecResult(GnuRegexp.REMatch match, GnuRegexp.RE re, String s) { try { - JS ret = new JS.O(); - ret.put(JS.S("index"), N(match.getStartIndex())); - ret.put(JS.S("input"),JS.S(s)); + JS ret = new JS.Obj(); + ret.put(JS.S("index"), JS.N(match.getStartIndex())); + ret.put(JS.S("input"), JS.S(s)); int n = re.getNumSubs(); - ret.put(JS.S("length"), N(n+1)); + ret.put(JS.S("length"), JS.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; @@ -117,7 +124,7 @@ public class JSRegexp extends JS { } } - String coerceToString() { + public String coerceToString() { StringBuffer sb = new StringBuffer(); sb.append('/'); sb.append(pattern); @@ -128,6 +135,7 @@ public class JSRegexp extends JS { return sb.toString(); } + private static final JS[] execarg = new JS[1]; static JS stringMatch(JS o, JS arg0) throws JSExn { String s = JS.toString(o); GnuRegexp.RE re; @@ -143,11 +151,14 @@ public class JSRegexp extends JS { GnuRegexp.REMatch match = re.getMatch(s); return matchToExecResult(match,re,s); } - if(!regexp.global) return regexp.callMethod(JS.S("exec"), o, null, null, null, 1); - - JSArray ret = new JSArray(); + try { + execarg[0] = o; + if(!regexp.global) return regexp.call(JS.S("exec"), execarg); + } finally { execarg[0] = null; } + GnuRegexp.REMatch[] matches = re.getAllMatches(s); - for(int i=0;i 0 ? matches[matches.length-1].getEndIndex() : s.length(); return ret; } @@ -156,7 +167,7 @@ public class JSRegexp extends JS { 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()); + return match == null ? JS.N(-1) : JS.N(match.getStartIndex()); } static JS stringReplace(JS o, JS arg0, JS arg1) throws JSExn { @@ -202,32 +213,16 @@ public class JSRegexp extends JS { if(replaceFunc != null) { int n = (regexp == null ? 0 : re.getNumSubs()); int numArgs = 3 + n; - 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 = 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 = JS.S(s); - break; - case 4: - a2 = N(match.getStartIndex()); - rest[0] = JS.S(s); - break; - default: - rest[rest.length - 2] = N(match.getStartIndex()); - rest[rest.length - 1] = JS.S(s); - } + JS[] args = new JS[3 + n]; + args[0] = JS.S(match.toString()); + args[1] = null; + args[2] = null; + for(int j=1;j<=n;j++) args[j] = JS.S(match.toString(j)); + args[args.length - 2] = JS.N(match.getStartIndex()); + args[args.length - 1] = JS.S(s); // note: can't perform pausing operations in here - sb.append(JS.toString(replaceFunc.call(a0, a1, a2, rest, numArgs))); + sb.append(JS.toString(replaceFunc.call(args))); } else { sb.append(mySubstitute(match,replaceString,s)); @@ -284,7 +279,7 @@ public class JSRegexp extends JS { 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(); + if(limit == 0) return new JSArray(0); GnuRegexp.RE re = null; JSRegexp regexp = null; @@ -341,5 +336,4 @@ public class JSRegexp extends JS { throw new JSExn(e.toString()); } } - } diff --git a/src/org/ibex/js/JSScope.java b/src/org/ibex/js/JSScope.java index 6570840..5dee438 100644 --- a/src/org/ibex/js/JSScope.java +++ b/src/org/ibex/js/JSScope.java @@ -5,7 +5,7 @@ package org.ibex.js; /** Implementation of a JavaScript Scope */ -class JSScope extends JS.O { +class JSScope { private final int base; private final JS[] vars; @@ -27,25 +27,36 @@ class JSScope extends JS.O { this.vars = new JS[size]; } + final JS get(JS i) throws JSExn { + if(i==null) throw new NullPointerException(); + try { + return get(Script.toInt(i)); + } catch(ArrayIndexOutOfBoundsException e) { + throw new JSExn("scope index out of range"); + } + } + final void put(JS i, JS o) throws JSExn { + if(i==null) throw new NullPointerException(); + try { + put(Script.toInt(i), o); + } catch(ArrayIndexOutOfBoundsException e) { + throw new JSExn("scope index out of range"); + } + } JS get(int i) throws JSExn { return i < base ? parent.get(i) : vars[i-base]; } void put(int i, JS o) throws JSExn { if(i < base) parent.put(i,o); else vars[i-base] = o; } JS getGlobal() { return parent.getGlobal(); } - private JSScope parentScope; + /*private JSScope parentScope; private static final JS NULL_PLACEHOLDER = new JS() { }; - public JSScope(JSScope parentScope) { this(parentScope, 0, 0); } + public JSScope(JSScope parentScope) { this.parentScope = parentScope; } public void declare(JS s) throws JSExn { super.put(s, NULL_PLACEHOLDER); } public JSScope getParentScope() { return parentScope; } public JS get(JS key) throws JSExn { - if (key instanceof JSNumber) try { - return get(JS.toInt(key)); - } catch(ArrayIndexOutOfBoundsException e) { - throw new JSExn("scope index out of range"); - } JS o = super.get(key); if (o != null) return o == NULL_PLACEHOLDER ? null : o; else return parentScope == null ? null : parentScope.get(key); @@ -53,11 +64,6 @@ class JSScope extends JS.O { public boolean has(JS key) throws JSExn { return super.get(key) != null; } public void put(JS key, JS val) throws JSExn { - if (key instanceof JSNumber) try { - put(JS.toInt(key),val); - } catch(ArrayIndexOutOfBoundsException e) { - throw new JSExn("scope index out of range"); - } if (parentScope != null && !has(key)) parentScope.put(key, val); else super.put(key, val == null ? NULL_PLACEHOLDER : val); } @@ -69,8 +75,8 @@ class JSScope extends JS.O { } public static class Global extends JSScope { - private final static JS NaN = JS.N(Double.NaN); - private final static JS POSITIVE_INFINITY = JS.N(Double.POSITIVE_INFINITY); + private final static JS NaN = N(Double.NaN); + private final static JS POSITIVE_INFINITY = N(Double.POSITIVE_INFINITY); public Global() { super(null); } public Global(JSScope p) { super(p); } @@ -81,37 +87,36 @@ class JSScope extends JS.O { // We'll have to do something better with this when Scope is rewritten public JS get(JS key) throws JSExn { JS ret = _get(key); - if(ret == JS.METHOD) return new Interpreter.Stub(this,key); + if(ret == METHOD) return new Interpreter.Stub(this,key); return ret; } public JS _get(JS key) throws JSExn { - //#switch(JS.toString(key)) - case "JS.NaN": return JS.NaN; + #switch(JS.str(key)) + case "NaN": return NaN; case "Infinity": return POSITIVE_INFINITY; case "undefined": return null; - case "stringFromCharCode": return JS.METHOD; - case "parseInt": return JS.METHOD; - case "parseFloat": return JS.METHOD; - case "isJS.NaN": return JS.METHOD; - case "isFinite": return JS.METHOD; - case "decodeURI": return JS.METHOD; - case "decodeURIComponent": return JS.METHOD; - case "encodeURI": return JS.METHOD; - case "encodeURIComponent": return JS.METHOD; - case "escape": return JS.METHOD; - case "unescape": return JS.METHOD; - default: return super.get(key); - //#end + case "stringFromCharCode": return METHOD; + case "parseInt": return METHOD; + case "parseFloat": return METHOD; + case "isNaN": return METHOD; + case "isFinite": return METHOD; + case "decodeURI": return METHOD; + case "decodeURIComponent": return METHOD; + case "encodeURI": return METHOD; + case "encodeURIComponent": return METHOD; + case "escape": return METHOD; + case "unescape": return METHOD; + #end return super.get(key); } public JS callMethod(JS method, JS a0, JS a1, JS a2, JS[] rest, int nargs) throws JSExn { - //#switch(JS.toString(method)) + #switch(JS.str(method)) case "parseInt": return parseInt(a0, N(0)); case "parseFloat": return parseFloat(a0); - case "isJS.NaN": { double d = JS.toDouble(a0); return d == d ? JS.F : JS.T; } - case "isFinite": { double d = JS.toDouble(a0); return (d == d && !Double.isInfinite(d)) ? JS.T : JS.F; } + case "isNaN": { double d = toDouble(a0); return d == d ? F : T; } + case "isFinite": { double d = toDouble(a0); return (d == d && !Double.isInfinite(d)) ? T : F; } case "decodeURI": throw new JSExn("unimplemented"); case "decodeURIComponent": throw new JSExn("unimplemented"); case "encodeURI": throw new JSExn("unimplemented"); @@ -119,9 +124,8 @@ class JSScope extends JS.O { case "escape": throw new JSExn("unimplemented"); case "unescape": throw new JSExn("unimplemented"); case "parseInt": return parseInt(a0, a1); - default: return super.callMethod(method, a0, a1, a2, rest, nargs); - //#end - return super.callMethod(method,a0,a1,a2,rest,nargs); + #end + return super.callMethod(method, a0, a1, a2, rest, nargs); } private JS parseInt(JS arg, JS r) throws JSExn { @@ -131,7 +135,7 @@ class JSScope extends JS.O { int length = s.length(); int sign = 1; long n = 0; - if(radix != 0 && (radix < 2 || radix > 36)) return JS.NaN; + if(radix != 0 && (radix < 2 || radix > 36)) return NaN; while(start < length && Character.isWhitespace(s.charAt(start))) start++; if((length >= start+1) && (s.charAt(start) == '+' || s.charAt(start) == '-')) { sign = s.charAt(start) == '+' ? 1 : -1; @@ -148,7 +152,7 @@ class JSScope extends JS.O { } } if(radix == 0) radix = 10; - if(length == start || Character.digit(s.charAt(start),radix) == -1) return JS.NaN; + if(length == start || Character.digit(s.charAt(start),radix) == -1) return NaN; // try the fast way first try { String s2 = start == 0 ? s : s.substring(start); @@ -159,7 +163,7 @@ class JSScope extends JS.O { int digit = Character.digit(s.charAt(i),radix); if(digit < 0) break; n = n*radix + digit; - if(n < 0) return JS.NaN; // overflow; + if(n < 0) return NaN; // overflow; } if(n <= Integer.MAX_VALUE) return JS.N(sign*(int)n); return JS.N((long)sign*n); @@ -179,8 +183,8 @@ class JSScope extends JS.O { } catch(NumberFormatException e) { } end--; } - return JS.NaN; + return NaN; } - } + }*/ } diff --git a/src/org/ibex/js/JSString.java b/src/org/ibex/js/JSString.java index c98a94e..2e40da8 100644 --- a/src/org/ibex/js/JSString.java +++ b/src/org/ibex/js/JSString.java @@ -5,6 +5,7 @@ package org.ibex.js; import org.ibex.util.*; +import java.util.*; class JSString extends JSPrimitive { final String s; @@ -16,13 +17,13 @@ class JSString extends JSPrimitive { if(o instanceof JSString) { return ((JSString)o).s.equals(s); } else if(o instanceof JSNumber) { - return o.jsequals(this); + return o.equals(this); } else { return false; } } - private final static Hash internHash = new Hash(); + private final static Map internHash = new HashMap(); static synchronized JS intern(String s) { synchronized(internHash) { JS js = (JS)internHash.get(s); @@ -35,5 +36,5 @@ class JSString extends JSPrimitive { protected void finalize() { synchronized(internHash) { internHash.put(s,null); } } } - String coerceToString() { return s; } + public String coerceToString() { return s; } } diff --git a/src/org/ibex/js/Parser.java b/src/org/ibex/js/Parser.java index 1931428..3bf7852 100644 --- a/src/org/ibex/js/Parser.java +++ b/src/org/ibex/js/Parser.java @@ -4,8 +4,9 @@ package org.ibex.js; -import org.ibex.util.*; import java.io.*; +import java.util.*; +import org.ibex.util.*; /** * Parses a stream of lexed tokens into a tree of JSFunction's. @@ -136,14 +137,14 @@ class Parser extends Lexer implements ByteCodes { } // Local variable management - Vec scopeStack = new Vec(); + List scopeStack = new ArrayList(); static class ScopeInfo { int base; int end; int newScopeInsn; - Hash mapping = new Hash(); + Map mapping = new HashMap(); } - Hash globalCache = new Hash(); + Map globalCache = new HashMap(); JS scopeKey(String name) { if(globalCache.get(name) != null) return null; for(int i=scopeStack.size()-1;i>=0;i--) { diff --git a/src/org/ibex/js/Script.java b/src/org/ibex/js/Script.java new file mode 100644 index 0000000..3fa9671 --- /dev/null +++ b/src/org/ibex/js/Script.java @@ -0,0 +1,129 @@ +package org.ibex.js; + +import java.io.Reader; +import java.io.IOException; +import org.ibex.util.*; + +public class Script { + /** returns a Pausable which will restart the context; + * expects a value to be pushed onto the stack when unpaused. */ + public static Pausable pause() throws Pausable.NotPausableException { + Interpreter i = Interpreter.current(); + i.pause(); + return i; + } + + /** Coerce a JS object into a boolean. */ + 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; + } + + //#repeat long/int/double/float toLong/toInt/toDouble/toFloat Long/Integer/Double/Float parseLong/parseInt/parseDouble/parseFloat + /** Coerce a JS object to a long. */ + 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"); + } + //#end + + /** Coerce a JS object to a String. */ + public static String toString(JS o) throws JSExn { + if(o == null) return "null"; + return o.coerceToString(); + } + + + 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 JSString) { + String s = ((JSString)o).s; + for(int i=0;i '9') return false; + return true; + } + return false; + } + + public static boolean isString(JS o) { + if(o instanceof JSString) return true; + return false; + } + + // Instance Methods //////////////////////////////////////////////////////////////////// + + 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 negone = new JSNumber.I(-1); + private static final JSNumber.I[] icache = new JSNumber.I[128]; + static { for (int i=0; i < icache.length; i++) icache[i] = new JSNumber.I(i); } + public static final JS N(int i) { + return i == -1 ? negone : i >= 0 && i < icache.length ? icache[i] : new JSNumber.I(i); + } + + /** Internal method for coercing to String without throwing a JSExn. */ + static String str(JS o) { + try { return toString(o); } + catch(JSExn e) { return o.toString(); } + } + + + // Static Interpreter Control Methods /////////////////////////////////////////////////////////////// + + public static JS fromReader(String sourceName, int firstLine, Reader source) throws IOException { + return Parser.fromReader(sourceName, firstLine, source); } + + // FIXME + public static JS cloneWithNewGlobalScope(JS js, JS s) { + if(js instanceof JSFunction) + return ((JSFunction)js)._cloneWithNewParentScope(new JSScope.Top(s)); + else + return js; + } + + /** log a message with the current JavaScript sourceName/line */ + public static void log(Object message) { info(message); } + public static void debug(Object message) { Log.debug(Interpreter.getSourceName() + ":" + Interpreter.getLine(), message); } + public static void info(Object message) { Log.info(Interpreter.getSourceName() + ":" + Interpreter.getLine(), message); } + public static void warn(Object message) { Log.warn(Interpreter.getSourceName() + ":" + Interpreter.getLine(), message); } + public static void error(Object message) { Log.error(Interpreter.getSourceName() + ":" + Interpreter.getLine(), message); } +} diff --git a/src/org/ibex/js/Test.java b/src/org/ibex/js/Test.java index 470bfc2..ea436b9 100644 --- a/src/org/ibex/js/Test.java +++ b/src/org/ibex/js/Test.java @@ -5,9 +5,10 @@ package org.ibex.js; import java.io.*; +import org.ibex.util.*; -public class Test extends JS { - static JS.UnpauseCallback up = null; +public class Test extends JS.Obj { + private static final JS.Method METHOD = new JS.Method(); static String action; public static void main(String[] args) throws Exception { @@ -18,30 +19,29 @@ public class Test extends JS { s.put(JS.S("sys"),new Test()); f = JS.cloneWithNewGlobalScope(f,s); //JS ret = f.call(null,null,null,null,0); - Interpreter i = new Interpreter((JSFunction)f, true, new Interpreter.JSArgs(f)); - JS ret = i.resume(); - while(up != null) { - JS.UnpauseCallback up = Test.up; Test.up = null; - if("throw".equals(action)) ret = up.unpause(new JSExn("this was thrown to a paused context")); - else if("bgget".equals(action)) ret = up.unpause(JS.S("I'm returning this from a get request")); + Interpreter i = new Interpreter((JSFunction)f, true, new JS[0]); + JS ret = (JS)i.run(null); + try { while(true) { + if("throw".equals(action)) ret = (JS)i.run(new JSExn("this was thrown to a paused context")); + else if("bgget".equals(action)) ret = (JS)i.run(Script.S("I'm returning this from a get request")); else { System.out.println("got a background put " + action); - ret = up.unpause(); + ret = (JS)i.run(null); } - } - System.out.println("Script returned: " + JS.toString(ret)); + } } catch (Pausable.AlreadyRunningException e) {} + System.out.println("Script returned: " + Script.toString(ret)); } public JS get(JS key) throws JSExn { - if(!JS.isString(key)) return null; - if("print".equals(JS.toString(key))) return METHOD; - if("clone".equals(JS.toString(key))) return METHOD; - if("firethis".equals(JS.toString(key))) return METHOD; - if("bgget".equals(JS.toString(key))) { + if(!Script.isString(key)) return null; + if("print".equals(Script.toString(key))) return METHOD; + if("clone".equals(Script.toString(key))) return METHOD; + if("firethis".equals(Script.toString(key))) return METHOD; + if("bgget".equals(Script.toString(key))) { action = "bgget"; try { - up = JS.pause(); - } catch(NotPauseableException e) { + Script.pause(); + } catch(Pausable.NotPausableException e) { throw new Error("should never happen"); } return null; @@ -53,8 +53,8 @@ public class Test extends JS { if("bgput".equals(JS.toString(key))) { action = JS.toString(val); try { - up = JS.pause(); - } catch(NotPauseableException e) { + Script.pause(); + } catch(Pausable.NotPausableException e) { throw new Error("should never happen"); } return; diff --git a/src/org/ibex/js/Trap.java b/src/org/ibex/js/Trap.java index f2200fb..580ed69 100644 --- a/src/org/ibex/js/Trap.java +++ b/src/org/ibex/js/Trap.java @@ -1,7 +1,4 @@ -// Copyright 2000-2005 the Contributors, as shown in the revision logs. -// Licensed under the Apache Public Source License 2.0 ("the License"). -// You may not use this file except in compliance with the License. - +// Copyright 2004 Adam Megacz, see the COPYING file for licensing [GPL] package org.ibex.js; /** -- 1.7.10.4