break;
} else {
ret = target.get(key);
+ if (pausecount > initialPauseCount) { pc++; return null; } // we were paused
if (ret == JS.METHOD) ret = new Stub(target, key);
stack.push(ret);
- if (pausecount > initialPauseCount) { pc++; return null; } // we were paused
break;
}
}
break;
}
- // FIXME: This was for the old trap syntax, remove it, shouldn't be needed anymore
- case ASSIGN_SUB: case ASSIGN_ADD: {
- JS val = stack.pop();
- JS key = stack.pop();
- JS obj = stack.peek();
- // The following setup is VERY important. The generated bytecode depends on the stack
- // being setup like this (top to bottom) KEY, OBJ, VAL, KEY, OBJ
- stack.push(key);
- stack.push(val);
- stack.push(obj);
- stack.push(key);
- break;
- }
-
case ADD: {
int count = ((JSNumber)arg).toInt();
if(count < 2) throw new Error("this should never happen");
}
} catch(JSExn e) {
- while(stack.size() > 0) {
- JS o = stack.pop();
- if (o instanceof CatchMarker || o instanceof TryMarker) {
- boolean inCatch = o instanceof CatchMarker;
- if(inCatch) {
- o = stack.pop();
- if(((TryMarker)o).finallyLoc < 0) continue; // no finally block, keep going
- }
- if(!inCatch && ((TryMarker)o).catchLoc >= 0) {
- // run the catch block, this will implicitly run the finally block, if it exists
- stack.push(o);
- stack.push(catchMarker);
- stack.push(e.getObject());
- f = ((TryMarker)o).f;
- scope = ((TryMarker)o).scope;
- pc = ((TryMarker)o).catchLoc - 1;
- continue OUTER;
- } else {
- stack.push(new FinallyData(e));
- f = ((TryMarker)o).f;
- scope = ((TryMarker)o).scope;
- pc = ((TryMarker)o).finallyLoc - 1;
- continue OUTER;
- }
- }
- }
- throw e;
+ catchException(e);
+ pc--; // it'll get incremented on the next iteration
} // end try/catch
} // end for
}
+
+ /** tries to find a handler withing the call chain for this exception
+ if a handler is found the interpreter is setup to call the exception handler
+ if a handler is not found the exception is thrown
+ */
+ void catchException(JSExn e) throws JSExn {
+ while(stack.size() > 0) {
+ JS o = stack.pop();
+ if (o instanceof CatchMarker || o instanceof TryMarker) {
+ boolean inCatch = o instanceof CatchMarker;
+ if(inCatch) {
+ o = stack.pop();
+ if(((TryMarker)o).finallyLoc < 0) continue; // no finally block, keep going
+ }
+ if(!inCatch && ((TryMarker)o).catchLoc >= 0) {
+ // run the catch block, this will implicitly run the finally block, if it exists
+ stack.push(o);
+ stack.push(catchMarker);
+ stack.push(e.getObject());
+ f = ((TryMarker)o).f;
+ scope = ((TryMarker)o).scope;
+ pc = ((TryMarker)o).catchLoc;
+ return;
+ } else {
+ stack.push(new FinallyData(e));
+ f = ((TryMarker)o).f;
+ scope = ((TryMarker)o).scope;
+ pc = ((TryMarker)o).finallyLoc;
+ return;
+ }
+ }
+ }
+ throw e;
+ }
}
}
- // Operations on Primitives //////////////////////////////////////////////////////////////////////
-
- // FIXME: Move these into JSString and JSNumber
- /*static Object callMethodOnPrimitive(Object o, Object method, Object arg0, Object arg1, Object arg2, Object[] rest, int alength) throws JSExn {
- if (method == null || !(method instanceof String) || "".equals(method))
- throw new JSExn("attempt to call a non-existant method on a primitive");
-
- if (o instanceof Number) {
- //#switch(method)
- case "toFixed": throw new JSExn("toFixed() not implemented");
- case "toExponential": throw new JSExn("toExponential() not implemented");
- case "toPrecision": throw new JSExn("toPrecision() not implemented");
- case "toString": {
- int radix = alength >= 1 ? JS.toInt(arg0) : 10;
- return Long.toString(((Number)o).longValue(),radix);
- }
- //#end
- } else if (o instanceof Boolean) {
- // No methods for Booleans
- throw new JSExn("attempt to call a method on a Boolean");
- }
-
- String s = JS.toString(o);
- int slength = s.length();
- //#switch(method)
- case "substring": {
- int a = alength >= 1 ? JS.toInt(arg0) : 0;
- int b = alength >= 2 ? JS.toInt(arg1) : slength;
- if (a > slength) a = slength;
- if (b > slength) b = slength;
- if (a < 0) a = 0;
- if (b < 0) b = 0;
- if (a > b) { int tmp = a; a = b; b = tmp; }
- return s.substring(a,b);
- }
- case "substr": {
- int start = alength >= 1 ? JS.toInt(arg0) : 0;
- int len = alength >= 2 ? JS.toInt(arg1) : Integer.MAX_VALUE;
- if (start < 0) start = slength + start;
- if (start < 0) start = 0;
- if (len < 0) len = 0;
- if (len > slength - start) len = slength - start;
- if (len <= 0) return "";
- return s.substring(start,start+len);
- }
- case "charAt": {
- int p = alength >= 1 ? JS.toInt(arg0) : 0;
- if (p < 0 || p >= slength) return "";
- return s.substring(p,p+1);
- }
- case "charCodeAt": {
- int p = alength >= 1 ? JS.toInt(arg0) : 0;
- if (p < 0 || p >= slength) return JS.N(Double.NaN);
- return JS.N(s.charAt(p));
- }
- case "concat": {
- StringBuffer sb = new StringBuffer(slength*2).append(s);
- for(int i=0;i<alength;i++) sb.append(i==0?arg0:i==1?arg1:i==2?arg2:rest[i-3]);
- return sb.toString();
- }
- case "indexOf": {
- String search = alength >= 1 ? JS.toString(arg0) : "null";
- int start = alength >= 2 ? JS.toInt(arg1) : 0;
- // Java's indexOf handles an out of bounds start index, it'll return -1
- return JS.N(s.indexOf(search,start));
- }
- case "lastIndexOf": {
- String search = alength >= 1 ? JS.toString(arg0) : "null";
- int start = alength >= 2 ? JS.toInt(arg1) : 0;
- // Java's indexOf handles an out of bounds start index, it'll return -1
- return JS.N(s.lastIndexOf(search,start));
- }
- case "match": return JSRegexp.stringMatch(s,arg0);
- case "replace": return JSRegexp.stringReplace(s,arg0,arg1);
- case "search": return JSRegexp.stringSearch(s,arg0);
- case "split": return JSRegexp.stringSplit(s,arg0,arg1,alength);
- case "toLowerCase": return s.toLowerCase();
- case "toUpperCase": return s.toUpperCase();
- case "toString": return s;
- case "slice": {
- int a = alength >= 1 ? JS.toInt(arg0) : 0;
- int b = alength >= 2 ? JS.toInt(arg1) : slength;
- if (a < 0) a = slength + a;
- if (b < 0) b = slength + b;
- if (a < 0) a = 0;
- if (b < 0) b = 0;
- if (a > slength) a = slength;
- if (b > slength) b = slength;
- if (a > b) return "";
- return s.substring(a,b);
- }
- //#end
- throw new JSExn("Attempted to call non-existent method: " + method);
- }
-
- static Object getFromPrimitive(Object o, Object key) throws JSExn {
- boolean returnJS = false;
- if (o instanceof Boolean) {
- throw new JSExn("Booleans do not have properties");
- } else if (o instanceof Number) {
- if (key.equals("toPrecision") || key.equals("toExponential") || key.equals("toFixed"))
- returnJS = true;
- }
- if (!returnJS) {
- // the string stuff applies to everything
- String s = JS.toString(o);
-
- // this is sort of ugly, but this list should never change
- // These should provide a complete (enough) implementation of the ECMA-262 String object
-
- //#switch(key)
- case "length": return JS.N(s.length());
- case "substring": returnJS = true; break;
- case "charAt": returnJS = true; break;
- case "charCodeAt": returnJS = true; break;
- case "concat": returnJS = true; break;
- case "indexOf": returnJS = true; break;
- case "lastIndexOf": returnJS = true; break;
- case "match": returnJS = true; break;
- case "replace": returnJS = true; break;
- case "search": returnJS = true; break;
- case "slice": returnJS = true; break;
- case "split": returnJS = true; break;
- case "toLowerCase": returnJS = true; break;
- case "toUpperCase": returnJS = true; break;
- case "toString": returnJS = true; break;
- case "substr": returnJS = true; break;
- //#end
- }
- if (returnJS) {
- final Object target = o;
- final String method = JS.toString(o);
- return new JS() {
- public Object call(Object a0, Object a1, Object a2, Object[] rest, int nargs) throws JSExn {
- if (nargs > 2) throw new JSExn("cannot call that method with that many arguments");
- return callMethodOnPrimitive(target, method, a0, a1, a2, rest, nargs);
- }
- };
- }
- return null;
- }*/
-
- private static class Stub extends JS {
+ static class Stub extends JS {
private JS method;
JS obj;
public Stub(JS obj, JS method) { this.obj = obj; this.method = method; }
public static UnpauseCallback pause() throws NotPauseableException {
Interpreter i = Interpreter.current();
if (i.pausecount == -1) throw new NotPauseableException();
+ boolean get;
+ switch(i.f.op[i.pc]) {
+ case Tokens.RETURN: case ByteCodes.PUT: get = false; break;
+ case ByteCodes.GET: get = true; break;
+ default: throw new Error("should never happen");
+ }
i.pausecount++;
- return new JS.UnpauseCallback(i);
+ return new JS.UnpauseCallback(i,get);
}
public static class UnpauseCallback implements Task {
- Interpreter i;
- UnpauseCallback(Interpreter i) { this.i = i; }
- public void perform() throws JSExn { unpause((JS)null); }
- public void unpause(JS o) throws JSExn {
- i.stack.push(o);
- i.resume();
+ private Interpreter i;
+ private boolean get;
+ UnpauseCallback(Interpreter i, boolean get) { this.i = i; this.get = get; }
+ public void perform() throws JSExn { unpause(); }
+ public JS unpause() throws JSExn { return unpause((JS)null); }
+ public JS unpause(JS o) throws JSExn {
+ if (o == JS.METHOD) throw new JSExn("can't return a method to a paused context");
+ if(get) i.stack.push(o);
+ return i.resume();
}
- public void unpause(JSExn e) {
- // FIXME: Throw the JSExn into the js world
- throw new Error("Exception " + e + " thrown from background task");
+ public JS unpause(JSExn e) throws JSExn {
+ i.catchException(e);
+ return i.resume();
}
}
public JSExn(JS js, Interpreter.Stack stack, JSFunction f, int pc, JSScope scope) { this.js = js; fill(stack, f, pc, scope); }
private void fill(Interpreter.Stack stack, JSFunction f, int pc, JSScope scope) {
addBacktrace(f.sourceName + ":" + f.line[pc]);
- // FIXME: "trap on property"
- /*if (scope != null && scope instanceof Trap.TrapScope)
- addBacktrace("trap on property \"" + ((Trap.TrapScope)scope).t.name + "\"");*/
for(int i=stack.size()-1; i>=0; i--) {
JS element = stack.elementAt(i);
if (element instanceof Interpreter.CallMarker) {
Interpreter.CallMarker cm = (Interpreter.CallMarker)element;
- if (cm.f != null)
- addBacktrace(cm.f.sourceName + ":" + cm.f.line[cm.pc-1]);
- /*if (cm.scope != null && cm.scope instanceof Trap.TrapScope)
- addBacktrace("trap on property \"" + ((Trap.TrapScope)cm.scope).t.name + "\"");*/
+ if(cm.f == null) break;
+ String s = cm.f.sourceName + ":" + cm.f.line[cm.pc-1];
+ if(cm instanceof Interpreter.TrapMarker)
+ s += " (trap on " + JS.debugToString(((Interpreter.TrapMarker)cm).key) + ")";
+ addBacktrace(s);
}
}
}
boolean toBoolean() { return toInt() != 0; }
double toDouble() { return toLong(); }
float toFloat() { return (float) toDouble(); }
- // FIXME: Number functions
-
+
final static class I extends JSNumber {
final int i;
I(int i) { this.i = i; }
return s;
}
- // FIXME: Update this for new API, it also has some other problems
- /*public static class Global extends JSScope {
- private final static Double NaN = new Double(Double.NaN);
- private final static Double POSITIVE_INFINITY = new Double(Double.POSITIVE_INFINITY);
+ public static class Global extends JSScope {
+ private final static JS NaN = N(Double.NaN);
+ private final static JS POSITIVE_INFINITY = N(Double.POSITIVE_INFINITY);
public Global() { super(null); }
- public Object get(Object key) throws JSExn {
- //#switch(key)
+ public Global(JSScope p) { super(p); }
+
+ public void declare(JS k) throws JSExn { throw new JSExn("can't declare variables in the global scope"); }
+
+ // HACK: "this" gets lost on the way back through the scope chain
+ // 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 == METHOD) return new Interpreter.Stub(this,key);
+ return ret;
+ }
+
+ public JS _get(JS key) throws JSExn {
+ if(!JS.isString(key)) return super.get(key);
+ //#switch(JS.toString(key))
case "NaN": return NaN;
case "Infinity": return POSITIVE_INFINITY;
case "undefined": return null;
return super.get(key);
}
- public Object callMethod(Object method, Object a0, Object a1, Object a2, Object[] rest, int nargs) throws JSExn {
- switch(nargs) {
- case 0: {
- //#switch(method)
- case "stringFromCharCode":
- char buf[] = new char[nargs];
- for(int i=0; i<nargs; i++) buf[i] = (char)(JS.toInt(i==0?a0:i==1?a1:i==2?a2:rest[i-3]) & 0xffff);
- return new String(buf);
- //#end
- break;
- }
- case 1: {
- //#switch(method)
- case "parseInt": return parseInt(a0, N(0));
- case "parseFloat": return parseFloat(a0);
- 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 "encodeURIComponent": throw new JSExn("unimplemented");
- case "escape": throw new JSExn("unimplemented");
- case "unescape": throw new JSExn("unimplemented");
- //#end
- break;
- }
- case 2: {
- //#switch(method)
- case "parseInt": return parseInt(a0, a1);
- //#end
- break;
- }
- }
+ public JS callMethod(JS method, JS a0, JS a1, JS a2, JS[] rest, int nargs) throws JSExn {
+ if(!JS.isString(method)) return super.callMethod(method, a0, a1, a2, rest, nargs);
+ //#switch(JS.toString(method))
+ case "parseInt": return parseInt(a0, N(0));
+ case "parseFloat": return parseFloat(a0);
+ 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 "encodeURIComponent": throw new JSExn("unimplemented");
+ case "escape": throw new JSExn("unimplemented");
+ case "unescape": throw new JSExn("unimplemented");
+ case "parseInt": return parseInt(a0, a1);
+ //#end
return super.callMethod(method, a0, a1, a2, rest, nargs);
}
- private Object parseInt(Object arg, Object r) throws JSExn {
+ private JS parseInt(JS arg, JS r) throws JSExn {
int radix = JS.toInt(r);
String s = JS.toString(arg);
int start = 0;
return JS.N((long)sign*n);
}
- private Object parseFloat(Object arg) throws JSExn {
+ private JS parseFloat(JS arg) throws JSExn {
String s = JS.toString(arg);
int start = 0;
int length = s.length();
// trailing garbage
while(start < end) {
try {
- return JS.N(s.substring(start,length));
+ return JS.N(new Double(s.substring(start,length)));
} catch(NumberFormatException e) { }
end--;
}
return NaN;
}
- }*/
+ }
}
import java.util.*;
import java.io.*;
-// FIXME: Update for new api
+// FEATURE: Update for new api
/** A JS interface to a Java '.properties' file; very crude */
public class PropertyFile extends JS {
import java.io.*;
public class Test extends JS {
+ static JS.UnpauseCallback up = null;
+ static String action;
+
public static void main(String[] args) throws Exception {
if(args.length == 0) { System.err.println("Usage Test filename"); System.exit(1); }
- JS f = JS.fromReader(args[0],0,new FileReader(args[0]));
+ JS f = JS.fromReader(args[0],1,new FileReader(args[0]));
System.out.println(((JSFunction)f).dump());
- JSScope s = new JSScope(null);
+ JSScope s = new JSScope(new JSScope.Global());
s.put(JS.S("sys"),new Test());
f = JS.cloneWithNewParentScope(f,s);
- JS ret = f.call(null,null,null,null,0);
+ //JS ret = f.call(null,null,null,null,0);
+ Interpreter i = new Interpreter((JSFunction)f, true, new JSArray());
+ 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"));
+ else {
+ System.out.println("got a background put " + action);
+ ret = up.unpause();
+ }
+ }
System.out.println("Script returned: " + JS.toString(ret));
}
public JS get(JS key) throws JSExn {
if(!JS.isString(key)) return null;
if("print".equals(JS.toString(key))) return METHOD;
+ if("bgget".equals(JS.toString(key))) {
+ action = "bgget";
+ try {
+ up = JS.pause();
+ } catch(NotPauseableException e) {
+ throw new Error("should never happen");
+ }
+ return null;
+ }
return super.get(key);
}
+
+ public void put(JS key, JS val) throws JSExn {
+ if("bgput".equals(JS.toString(key))) {
+ action = JS.toString(val);
+ try {
+ up = JS.pause();
+ } catch(NotPauseableException e) {
+ throw new Error("should never happen");
+ }
+ return;
+ }
+ super.put(key,val);
+ }
public JS callMethod(JS method, JS a0, JS a1, JS a2, JS[] rest, int nargs) throws JSExn {
if(!JS.isString(method)) return null;
i.stack.push(name);
return i.resume();
}
-
- // FIXME: review; is necessary?
- /*static class TrapScope extends JSScope {
- Trap t;
- Object val = null;
- boolean cascadeHappened = false;
- public TrapScope(JSScope parent, Trap t, Object val) { super(parent); this.t = t; this.val = val; }
- public Object get(Object key) throws JSExn {
- if (key.equals("trapee")) return t.trapee;
- if (key.equals("callee")) return t.f;
- if (key.equals("trapname")) return t.name;
- return super.get(key);
- }
- }*/
}
-