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()); }
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;
}
}
- 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));
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 {
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();
}
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
}
case RETURN: {
- JS retval = stack.pop();
+ JS retval = (JS)stack.pop();
while(!stack.empty()) {
Object o = stack.pop();
if (o instanceof TryMarker) {
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);
}
}
}
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;
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
}
}
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);
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) {
} 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();
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; }
}
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");
}
}
*/
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;
}
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)));
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<args.length;i++) sb.append(JS.toString(args[i]));
}
default: {
- JS right = stack.pop();
- JS left = stack.pop();
+ JS right = (JS)stack.pop();
+ JS left = (JS)stack.pop();
switch(op) {
case BITOR: stack.push(JS.N(JS.toLong(left) | JS.toLong(right))); break;
*/
void catchException(JSExn e) throws JSExn {
while(!stack.empty()) {
- JS o = stack.pop();
+ JS o = (JS)stack.pop();
if (o instanceof CatchMarker || o instanceof TryMarker) {
boolean inCatch = o instanceof CatchMarker;
if(inCatch) {
- o = stack.pop();
+ o = (JS)stack.pop();
if(((TryMarker)o).finallyLoc < 0) continue; // no finally block, keep going
}
if(!inCatch && ((TryMarker)o).catchLoc >= 0) {
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;
}
// 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;
}
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;
}
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;
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;
}
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) {
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
+ * <i>pausable</i> 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
+ * <i>unpausable</i> 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 {
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<s.length();i++)
- if(s.charAt(i) < '0' || s.charAt(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++); }
+ }
+ }
-}
+}
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<s.length(); i++) if (s.charAt(i) < '0' || s.charAt(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<nargs; i++) insertElementAt(i==0?a0:i==1?a1:i==2?a2:rest[i-3],oldSize+i);
- return N(oldSize + nargs);
- }
case "unshift":
- for(int i=0; i<nargs; i++) insertElementAt(i==0?a0:i==1?a1:i==2?a2:rest[i-3],i);
- return N(size());
- case "splice":
- JSArray array = new JSArray();
- for(int i=0; i<nargs; i++) array.addElement(i==0?a0:i==1?a1:i==2?a2:rest[i-3]);
- return splice(array);
- //#end
- return super.callMethod(method, a0, a1, a2, rest, nargs);
- }
-
- 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(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++) insertElementAt(null,i);
- for(int i=oldSize-1;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;
if(start > length) start = length;
if(end > length) end = length;
JSArray a = new JSArray(end-start);
- for(int i=0;i<end-start;i++)
- a.setElementAt(elementAt(start+i),i);
+ for(int i=0;i<end-start;i++) // FIXME: with ArrayStore do System.arraycopy()
+ a.set(i, get(start+i));
return a;
}
-
- private static final Vec.CompareFunc defaultSort = new Vec.CompareFunc() {
- public int compare(Object a, Object b) {
- try {
- return JS.toString((JS)a).compareTo(JS.toString((JS)b));
- } catch(JSExn e) { throw new JSExn.Wrapper(e); }
- }
- };
- private JS sort(JS tmp) throws JSExn {
- Vec vec = toVec();
- 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);
- }
- } catch(JSExn.Wrapper e) {
- throw e.refill();
- }
- setFromVec(vec);
- return this;
- }
-
- 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));
- int newCount = args.length() - 2;
+
+ private JS splice(JS[] args) throws JSExn {
+ int oldLength = size();
+ int start = Script.toInt(args.length < 1 ? null : args[0]);
+ int deleteCount = Script.toInt(args.length < 2 ? null : args[1]);
+ int newCount = args.length - 2;
if(newCount < 0) newCount = 0;
if(start < 0) start = oldLength+start;
if(start < 0) start = 0;
int newLength = oldLength - deleteCount + newCount;
int lengthChange = newLength - oldLength;
JSArray ret = new JSArray(deleteCount);
- for(int i=0;i<deleteCount;i++)
- ret.setElementAt(elementAt(start+i),i);
+ for(int i=0;i<deleteCount;i++) // FIXME: ArrayStore System.arraycopy()
+ ret.set(i, get(start+i));
if(lengthChange > 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<newLength;i++)
- setElementAt(elementAt(i-lengthChange),i);
+ set(i, get(i-lengthChange));
setSize(newLength);
}
for(int i=0;i<newCount;i++)
- setElementAt(args.elementAt(i+2),start+i);
+ set(start+i, args[i+2]);
return ret;
}
-
- protected Vec toVec() {
- int count = size();
- Vec vec = new Vec();
- vec.setSize(count);
- for(int i=0;i<count;i++) {
- Object o = bt.getNode(i);
- vec.setElementAt(o == NULL ? null : o,i);
- }
- return vec;
+
+ private JS sort(JS comparator) throws JSExn {
+ try {
+ if (comparator == null) Collections.sort(this);
+ else { sort = comparator; Collections.sort(this, this); }
+ return this;
+ } catch (JSExn.Wrapper w) { throw w.unwrap(); }
}
-
- protected void setFromVec(Vec vec) {
- int count = vec.size();
- bt.clear();
- for(int i=0;i<count;i++) {
- Object o = vec.elementAt(i);
- bt.insertNode(i,o==null ? NULL : o);
- }
+
+ private JS sort = null;
+ private final JS[] sortargs = new JS[2];
+ public int compare(java.lang.Object a, java.lang.Object b) throws JSExn.Wrapper {
+ try {
+ sortargs[0] = (JS)a; sortargs[1] = (JS)b;
+ return Script.toInt(sort.call(sortargs));
+ } catch (JSExn e) { throw new JSExn.Wrapper(e);
+ } finally { sortargs[0] = null; sortargs[1] = null; }
}
-
- String coerceToString() throws JSExn { return JS.toString(join(",")); }
+
}
* @author Mike McCabe
* @author Adam Megacz (many modifications
*/
-public class JSDate extends JS {
+public class JSDate extends JS.Immutable {
+ private static final JS.Method METHOD = new JS.Method();
public JSDate() {
if (thisTimeZone == null) {
}
}
- String coerceToString() { return date_format(date, FORMATSPEC_FULL); }
+ public String coerceToString() { return date_format(date, FORMATSPEC_FULL); }
- 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 0: {
//#switch(JS.toString(method))
case "toString": return JS.S(date_format(date, FORMATSPEC_FULL));
case "getUTCMilliseconds": return N(msFromTime(date));
case "getTimezoneOffset": return N(getTimezoneOffset(date));
//#end
- return super.callMethod(method, a0, a1, a2, rest, nargs);
+ return super.call(method, args);
}
case 1: {
//#switch(JS.toString(method))
//#end
}
}
- return super.callMethod(method, a0, a1, a2, rest, nargs);
+ return super.call(method, args);
}
public JS get(JS key) throws JSExn {
private static double _toNumber(JS[] o, int index) throws JSExn { return JS.toDouble(o[index]); }
private static double toDouble(double d) { return d; }
- public JSDate(JS a0, JS a1, JS a2, JS[] rest, int nargs) throws JSExn {
+ public JSDate(JS[] args) throws JSExn {
JSDate obj = this;
- switch (nargs) {
+ switch (args.length) {
case 0: {
obj.date = Now();
return;
if(isString(a0))
date = date_parseString(JS.toString(a0));
else
- date = _toNumber(a0);
+ date = _toNumber(args[0]);
obj.date = TimeClip(date);
return;
}
default: {
// multiple arguments; year, month, day etc.
double array[] = new double[MAXARGS];
- array[0] = toDouble(a0);
- array[1] = toDouble(a1);
- if (nargs >= 2) array[2] = toDouble(a2);
- for(int i=0; i<nargs; i++) {
- double d = _toNumber(i==0?a0:i==1?a1:i==2?a2:rest[i-3]);
+ array[0] = Script.toDouble(args[0]);
+ array[1] = Script.toDouble(args[1]);
+ if (args.length >= 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;
/** 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); }
}
public void printStackTrace() { printStackTrace(System.err); }
public void printStackTrace(PrintWriter pw) {
- for(int i=0; i<backtrace.size(); i++) pw.println(" at " + (String) backtrace.elementAt(i));
+ for(int i=0; i<backtrace.size(); i++) pw.println(" at " + (String) backtrace.get(i));
super.printStackTrace(pw);
}
public void printStackTrace(PrintStream ps) {
- for(int i=0; i<backtrace.size(); i++) ps.println(" at " + (String) backtrace.elementAt(i));
+ for(int i=0; i<backtrace.size(); i++) ps.println(" at " + (String) backtrace.get(i));
super.printStackTrace(ps);
}
- public String toString() { return "JSExn: " + JS.debugToString(js); }
+ public String toString() { return "JSExn: " + Script.str(js); }
public String getMessage() { return toString(); }
public JS getObject() { return js; }
- void addBacktrace(String line) { backtrace.addElement(line); }
+ void addBacktrace(String line) { backtrace.add(line); }
public static class Wrapper extends RuntimeException {
public final JSExn e;
public Wrapper(JSExn e) { this.e = e; }
- public JSExn refill() {
+ public JSExn unwrap() {
e.addBacktrace("[foreign code]");
e.fill(null);
return e;
public static class IO extends JSExn {
public IO(java.io.IOException ioe) {
super("ibex.io: " + ioe.toString());
- JS.warn(ioe);
+ Script.warn(ioe);
}
}
}
package org.ibex.js;
-import java.io.*;
-import org.ibex.util.*;
-
/** A JavaScript function, compiled into bytecode */
-// FIXME: This shouldn't be public, needed for public add/delTrap (which is needed for the Template.java hack)
-public class JSFunction extends JS implements ByteCodes, Tokens, Task {
-
+class JSFunction extends JS.Immutable implements ByteCodes, Tokens {
+ private static final JS[] emptyArgs = new JS[0];
// Fields and Accessors ///////////////////////////////////////////////
// Public //////////////////////////////////////////////////////////////////////////////
- // FEATURE: make sure that this can only be called from the Scheduler...
- /** if you enqueue a function, it gets invoked in its own pauseable context */
- public void perform() throws JSExn {
- Interpreter i = new Interpreter(this, true, new Interpreter.JSArgs(this));
- i.resume();
+ // FIXME: what needs to be syncrhonized (if anything)?
+ private Interpreter runner = null;
+ public Object run(Object o) throws JSExn {
+ if (runner == null) runner = new Interpreter(this, true, emptyArgs);
+ Object ret = runner.run(o);
+ if (runner.f == null) runner = null;
+ return ret;
+ }
+ public void pause() throws NotPausableException {
+ if (runner == null) throw new NotPausableException();
+ runner.pause();
}
public JSFunction _cloneWithNewParentScope(JSScope s) {
return ret;
}
- /** Note: code gets run in an <i>unpauseable</i> 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 ///////////////////////////////////////////////////
break;
}
}
- return super.callMethod(method, a0, a1, a2, rest, nargs);
+ return super.call(method, args);
}
public JS get(JS key) throws JSExn {
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");
}
case "concat": {
StringBuffer sb = new StringBuffer(slength*2).append(s);
- for(int i=0;i<alength;i++) sb.append(i==0?arg0:i==1?arg1:i==2?arg2:rest[i-3]);
- return JS.S(sb.toString());
+ for (int i=0; i < args.length; i++) sb.append(args[i]);
+ return Script.S(sb.toString());
}
case "indexOf": {
String search = alength >= 1 ? JS.toString(arg0) : "null";
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 {
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;
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;
}
}
- 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;
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);
}
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;
}
}
- String coerceToString() {
+ public String coerceToString() {
StringBuffer sb = new StringBuffer();
sb.append('/');
sb.append(pattern);
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;
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<matches.length;i++) ret.addElement(JS.S(matches[i].toString()));
+ JSArray ret = new JSArray(matches.length);
+ for(int i=0;i<matches.length;i++) ret.add(JS.S(matches[i].toString()));
regexp.lastIndex = matches.length > 0 ? matches[matches.length-1].getEndIndex() : s.length();
return ret;
}
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 {
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));
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;
throw new JSExn(e.toString());
}
}
-
}
package org.ibex.js;
/** Implementation of a JavaScript Scope */
-class JSScope extends JS.O {
+class JSScope {
private final int base;
private final JS[] vars;
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);
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);
}
}
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); }
// 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");
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 {
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;
}
}
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);
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);
} catch(NumberFormatException e) { }
end--;
}
- return JS.NaN;
+ return NaN;
}
- }
+ }*/
}
package org.ibex.js;
import org.ibex.util.*;
+import java.util.*;
class JSString extends JSPrimitive {
final String s;
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);
protected void finalize() { synchronized(internHash) { internHash.put(s,null); } }
}
- String coerceToString() { return s; }
+ public String coerceToString() { return s; }
}
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.
}
// 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--) {
--- /dev/null
+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<s.length();i++)
+ if(s.charAt(i) < '0' || s.charAt(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); }
+}
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 {
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;
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;
-// 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;
/**