// Public Helper Methods //////////////////////////////////////////////////////////////////////
/** parse and compile a function */
- public static CompiledFunction parse(String sourceName, int firstLine, Reader sourceCode) throws IOException {
- return new CompiledFunction(sourceName, firstLine, sourceCode, null);
+ public static Function parse(String sourceName, int firstLine, Reader sourceCode) throws IOException {
+ return new Function(sourceName, firstLine, sourceCode, null);
}
/** coerce an object to a Boolean */
// NOTE: There are about 3 pages of rules in ecma262 about string to number conversions
// We aren't even close to following all those rules. We probably never will be.
- if (o instanceof String) try { return new Double((String)o); } catch (NumberFormatException e) { return new Double(Double.NaN); }
+ if (o instanceof String)
+ try { return new Double((String)o); } catch (NumberFormatException e) { return new Double(Double.NaN); }
if (o instanceof Boolean) return ((Boolean)o).booleanValue() ? new Long(1) : new Long(0);
if (o instanceof JS) return ((JS)o).coerceToNumber();
throw new Error("toNumber() got object of type " + o.getClass().getName() + " which we don't know how to handle");
public Object callMethod(Object method, Array args, boolean checkOnly) throws JS.Exn {
if (checkOnly) return Boolean.FALSE;
Object o = get(method);
- if(o instanceof JS.Callable) {
- return ((JS.Callable)o).call(args);
- } else if(o == null) {
- throw new JS.Exn("Attempted to call non-existent method: " + method);
- } else {
- throw new JS.Exn("Attempted to call a non-method: " +method);
- }
+ if (o instanceof JS.Callable) return ((JS.Callable)o).call(args);
+ else if (o == null) throw new JS.Exn("Attempted to call non-existent method: " + method);
+ else throw new JS.Exn("Attempted to call a non-function: " +method);
}
- public Number coerceToNumber() { return new Integer(0); }
+ public Number coerceToNumber() { throw new JS.Exn("tried to coerce a JavaScript object to a Number"); }
public String coerceToString() { throw new JS.Exn("tried to coerce a JavaScript object to a String"); }
- public boolean coerceToBoolean() { return true; }
-
+ public boolean coerceToBoolean() { throw new JS.Exn("tried to coerce a JavaScript object to a Boolean"); }
public String typeName() { return "object"; }
public static class Scope extends ScopeImpl {
public Scope(Scope parentScope) { this(parentScope, false); }
public Scope(Scope parentScope, boolean sealed) { super(parentScope, sealed); }
- /** transparent scopes are not returned by THIS */
- public boolean isTransparent() { return super.isTransparent(); }
+ public boolean isTransparent() { return super.isTransparent(); } //< transparent scopes are not returned by THIS
public boolean has(Object key) { return super.has(key); }
public Object get(Object key) { return super._get(key); }
public Object put(Object key, Object val) { super._put(key, val); return null; }
}
}
- /** anything that is callable with the () operator */
+ /** anything that is callable with the () operator and wasn't compiled from JS code */
public static abstract class Callable extends JS.Obj {
public abstract Object call(JS.Array args) throws JS.Exn;
}
- /** a Callable which was compiled from JavaScript code */
- public static class CompiledFunction extends CompiledFunctionImpl {
- public int getNumFormalArgs() { return numFormalArgs; }
- CompiledFunction(String sourceName, int firstLine, Reader sourceCode, Scope scope) throws IOException {
- super(sourceName, firstLine, sourceCode, scope);
- }
- }
-
/** a scope that is populated with js objects and functions normally found in the global scope */
public static class GlobalScope extends GlobalScopeImpl {
public GlobalScope() { this(null); }
public GlobalScope(JS.Scope parent) { super(parent); }
}
- public static final JS Math = new org.xwt.js.Math();
-
public static class TailCall {
- CompiledFunction func = null;
+ Function func = null;
JS.Array args = null;
public TailCall() { }
- public TailCall set(CompiledFunction func) { this.func = func; this.args = new JS.Array(); return this; }
- public TailCall set(CompiledFunction func, JS.Array args) { this.func = func; this.args = args; return this; }
+ public TailCall set(Function func) { this.func = func; this.args = new JS.Array(); return this; }
+ public TailCall set(Function func, JS.Array args) { this.func = func; this.args = args; return this; }
}
- /** encapsulates a single JavaScript thread; the JS.Context->java.lang.Thread mapping is 1:1 */
+ /** encapsulates the state of a JavaScript "thread" (ie stack) */
public static class Context {
- CompiledFunction f = null;
- Vec stack = new Vec();
- public Scope scope = null;
- int pc = 0;
- /** return this to signal a pause */
+ // Statics //////////////////////////////////////////////////////////////////////
+
+ /**
+ * Return this from call/get/put in order to make the interpreter pause. The interpreter will expect a value
+ * (the return from the call or the get) to be pushed onto the stack when it is resumed.
+ */
public static Object pause = new Object();
- public static int getLine() { return current().f == null ? -1 : current().f.getLine(current()); }
- public static String getSourceName() { return current().f == null ? null : current().f.getSourceName(); }
+ private int getLine_() { return current().f == null ? -1 : (pc < 0 || pc >= f.size) ? -1 : f.line[pc]; }
+ public static int getLine() { return current().getLine_(); }
+ public static String getSourceName() { return current().f == null ? null : current().f.sourceName; }
+
+ /** fetches the currently-executing javascript function */
+ public static JS.Context current() { return (JS.Context)threadToContext.get(Thread.currentThread()); }
+ private static Hashtable threadToContext = new Hashtable();
- public Context(CompiledFunction function, Scope scope, JS.Array args) {
- if (scope == null) scope = new CompiledFunctionImpl.FunctionScope("unknown", function.parentScope);
- if (args == null) args = new JS.Array();
- stack.push(new CompiledFunctionImpl.CallMarker(this));
- stack.push(args);
+
+ // Instance members and methods //////////////////////////////////////////////////////////////////////
+
+ /** the currently-executing Function */
+ Function f = null;
+
+ /** the currently-executing scope */
+ public Scope scope = null; // FIXME: do we really need this? the function should contain this info
+
+ /** the object stack */
+ Vec stack = new Vec();
+
+ /** the program counter */
+ int pc = 0;
+
+ public Context(Function function, Scope scope) {
+ if (scope == null) scope = new Scope(function.parentScope);
+ stack.push(new Function.CallMarker(this));
+ stack.push(new JS.Array());
this.f = function;
this.scope = scope;
}
+ public Object resume() { return resume(null); }
public Object resume(Object o) {
+ Thread t = Thread.currentThread();
+ Context old = (Context)threadToContext.get(t);
try {
- javaContextToJSContext.put(Thread.currentThread(), this);
- return CompiledFunctionImpl.eval(this, o);
+ threadToContext.put(t, this);
+ stack.push(o);
+ return Function.eval(this);
} finally {
- javaContextToJSContext.remove(Thread.currentThread());
+ if (old == null) threadToContext.remove(t);
+ else threadToContext.put(t, old);
}
}
- /** fetches the currently-executing javascript function */
- public static JS.Context current() { return (JS.Context)javaContextToJSContext.get(Thread.currentThread()); }
- private static Hashtable javaContextToJSContext = new Hashtable();
}
}