import java.util.*;
/** The minimum set of functionality required for objects which are manipulated by JavaScript */
-public class JS {
+public class JS extends org.xwt.util.BalancedTree {
// Static Interpreter Control Methods ///////////////////////////////////////////////////////////////
+ /** log a message with the current JavaScript sourceName/line */
+ public static void log(Object o, Object message) { log(message); }
+ public static void log(Object message) { Log.echo(JS.getSourceName() + ":" + JS.getLine(), message); }
+
public static int getLine() {
Interpreter c = Interpreter.current();
- return c.f == null || c.pc < 0 || c.pc >= c.f.size ? -1 : c.f.line[c.pc];
+ return c == null || c.f == null || c.pc < 0 || c.pc >= c.f.size ? -1 : c.f.line[c.pc];
}
public static String getSourceName() {
return c == null || c.f == null ? null : c.f.sourceName;
}
- public static class PausedException extends Exception { PausedException() { } }
-
- public static void invokePauseable(JSFunction function) throws JS.PausedException {
- Interpreter i = new Interpreter(function, true, new JSArray());
- int oldpausecount = i.pausecount;
- i.resume();
- if (i.pausecount > oldpausecount) throw new PausedException();
- }
-
public static class NotPauseableException extends Exception { NotPauseableException() { } }
/** returns a callback which will restart the context; expects a value to be pushed onto the stack when unpaused */
return new JS.UnpauseCallback(i);
}
- public static class UnpauseCallback {
+ public static class UnpauseCallback implements Scheduler.Task {
Interpreter i;
UnpauseCallback(Interpreter i) { this.i = i; }
- public void unpause(Object o) throws PausedException {
+ public void perform() throws JSExn { unpause(null); }
+ public void unpause(Object o) throws JSExn {
+ // FIXME: if o instanceof JSExn, throw it into the JSworld
i.stack.push(o);
i.resume();
}
public static final Object T = Boolean.TRUE;
public static final Object F = Boolean.FALSE;
- public static final Number N(int i) { return new Integer(i); }
- public static final Number N(long l) { return new Long(l); }
- public static final Number N(double d) { return new Double(d); }
- public static final Number N(String s) { return new Double(s); }
public static final Boolean B(boolean b) { return b ? Boolean.TRUE : Boolean.FALSE; }
public static final Boolean B(int i) { return i==0 ? Boolean.FALSE : Boolean.TRUE; }
+ public static final Number N(String s) { return s.indexOf('.') == -1 ? N(Integer.parseInt(s)) : new Double(s); }
+ public static final Number N(double d) { return (int)d == d ? N((int)d) : new Double(d); }
+ public static final Number N(long l) { return N((int)l); }
+
+ private static final Integer[] smallIntCache = new Integer[65535 / 4];
+ private static final Integer[] largeIntCache = new Integer[65535 / 4];
+ public static final Number N(int i) {
+ Integer ret = null;
+ int idx = i + smallIntCache.length / 2;
+ if (idx < smallIntCache.length && idx > 0) {
+ ret = smallIntCache[idx];
+ if (ret != null) return ret;
+ }
+ else ret = largeIntCache[Math.abs(idx % largeIntCache.length)];
+ if (ret == null || ret.intValue() != i) {
+ ret = new Integer(i);
+ if (idx < smallIntCache.length && idx > 0) smallIntCache[idx] = ret;
+ else largeIntCache[Math.abs(idx % largeIntCache.length)] = ret;
+ }
+ return ret;
+ }
private static Enumeration emptyEnumeration = new Enumeration() {
public boolean hasMoreElements() { return false; }
};
private Hash entries = null;
- public Enumeration keys() { return entries == null ? emptyEnumeration : entries.keys(); }
- public Object get(Object key) { return entries == null ? null : entries.get(key, null); }
- public void put(Object key, Object val) { if (entries == null) entries = new Hash(); entries.put(key, null, val); }
+ public Enumeration keys() throws JSExn {
+ return entries == null ? emptyEnumeration : entries.keys();
+ }
+ public Object get(Object key) throws JSExn { return entries == null ? null : entries.get(key, null); }
+ public void put(Object key, Object val) throws JSExn {
+ if (entries == null) entries = new Hash();
+ entries.put(key, null, val);
+ }
// Trap support //////////////////////////////////////////////////////////////////////////////
- /** override and return true to allow placing traps on this object */
- protected boolean isTrappable() { return false; }
+ /** override and return true to allow placing traps on this object.
+ * if isRead true, this is a read trap, otherwise write trap
+ **/
+ protected boolean isTrappable(Object name, boolean isRead) { return false; }
/** performs a put, triggering traps if present; traps are run in an unpauseable interpreter */
- public final void putAndTriggerTraps(Object key, Object value) {
+ public void putAndTriggerTraps(Object key, Object value) throws JSExn {
Trap t = getTrap(key);
- if (t != null) t.invoke(key, value);
+ if (t != null) t.invoke(value);
else put(key, value);
}
/** performs a get, triggering traps if present; traps are run in an unpauseable interpreter */
- public final Object getAndTriggerTraps(Object key) {
+ public Object getAndTriggerTraps(Object key) throws JSExn {
Trap t = getTrap(key);
- if (t != null) return t.invoke(key);
+ if (t != null) return t.invoke();
else return get(key);
}
/** retrieve a trap from the entries hash */
protected final Trap getTrap(Object key) {
- return !isTrappable() || entries == null ? null : (Trap)entries.get(key, Trap.class);
+ return entries == null ? null : (Trap)entries.get(key, Trap.class);
}
/** retrieve a trap from the entries hash */
protected final void putTrap(Object key, Trap value) {
- if (!isTrappable()) return;
if (entries == null) entries = new Hash();
entries.put(key, Trap.class, value);
}
/** adds a trap, avoiding duplicates */
- protected final void addTrap(Object name, JSFunction f) {
+ protected final void addTrap(Object name, JSFunction f) throws JSExn {
+ if (f.numFormalArgs > 1) throw new JSExn("traps must take either one argument (write) or no arguments (read)");
+ boolean isRead = f.numFormalArgs == 0;
+ if (!isTrappable(name, isRead)) throw new JSExn("not allowed "+(isRead?"read":"write")+" trap on property: "+name);
for(Trap t = getTrap(name); t != null; t = t.next) if (t.f == f) return;
putTrap(name, new Trap(this, name.toString(), f, (Trap)getTrap(name)));
}
// return this from get() if the key was actually a method.
public static final Object METHOD = new Object();
- public Object callMethod(Object method, Object a0, Object a1, Object a2, Object[] rest, int nargs) {
+ public Object callMethod(Object method, Object a0, Object a1, Object a2, Object[] rest, int nargs) throws JSExn {
throw new JSExn("attempted to call the null value (method "+method+")");
- }
- public Object call(Object a0, Object a1, Object a2, Object[] rest, int nargs) {
- throw new JSExn("you cannot call this object)");
+ }
+ public Object call(Object a0, Object a1, Object a2, Object[] rest, int nargs) throws JSExn {
+ throw new JSExn("you cannot call this object (class=" + this.getClass().getName() +")");
}
// Typing Support //////////////////////////////////////////////////////////////////////////////
- public Number coerceToNumber() { throw new JSExn("tried to coerce a JavaScript object to a Number"); }
- public String coerceToString() { throw new JSExn("tried to coerce a JavaScript object to a String"); }
- public boolean coerceToBoolean() { throw new JSExn("tried to coerce a JavaScript object to a Boolean"); }
+ public Number coerceToNumber() { throw new JSRuntimeExn("tried to coerce a JavaScript object to a Number"); }
+ public String coerceToString() { throw new JSRuntimeExn("tried to coerce a JavaScript object to a String"); }
+ public boolean coerceToBoolean() { throw new JSRuntimeExn("tried to coerce a JavaScript object to a Boolean"); }
public String typeName() { return "object"; }